pokerogue/src/timed-event-manager.ts
damocleas bfc5aed0ce
fix
2025-10-30 19:23:33 -04:00

754 lines
24 KiB
TypeScript

import { globalScene } from "#app/global-scene";
import { SHINY_CATCH_RATE_MULTIPLIER } from "#balance/rates";
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "#balance/starters";
import type { PokemonSpeciesFilter } from "#data/pokemon-species";
import type { WeatherPoolEntry } from "#data/weather";
import { Challenges } from "#enums/challenges";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { WeatherType } from "#enums/weather-type";
import type { ModifierTypeKeys } from "#modifiers/modifier-type";
import type { nil } from "#types/common";
import { addTextObject } from "#ui/text";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import i18next from "i18next";
export enum EventType {
SHINY,
NO_TIMER_DISPLAY,
LUCK,
}
interface EventBanner {
readonly bannerKey?: string;
readonly xOffset?: number;
readonly yOffset?: number;
readonly scale?: number;
readonly availableLangs?: readonly string[];
}
export interface EventEncounter {
readonly species: SpeciesId;
readonly blockEvolution?: boolean;
readonly formIndex?: number;
}
interface EventMysteryEncounterTier {
readonly mysteryEncounter: MysteryEncounterType;
readonly tier?: MysteryEncounterTier;
readonly disable?: boolean;
}
interface EventWaveReward {
/**
* The wave at which the reward should be given.
* {@linkcode ClassicFixedBossWaves.RIVAL1} and {@linkcode ClassicFixedBossWaves.RIVAL2} are currently the only waves that give fixed rewards.
*/
readonly wave: number;
readonly type: ModifierTypeKeys;
}
type EventMusicReplacement = readonly [string, string];
interface EventChallenge {
readonly challenge: Challenges;
readonly value: number;
}
interface TimedEvent extends EventBanner {
readonly name: string;
readonly eventType: EventType;
readonly shinyEncounterMultiplier?: number;
readonly shinyCatchMultiplier?: number;
readonly classicFriendshipMultiplier?: number;
readonly luckBoost?: number;
readonly upgradeUnlockedVouchers?: boolean;
readonly startDate: Date;
readonly endDate: Date;
readonly eventEncounters?: readonly EventEncounter[];
readonly delibirdyBuff?: readonly string[];
readonly weather?: readonly WeatherPoolEntry[];
readonly mysteryEncounterTierChanges?: readonly EventMysteryEncounterTier[];
readonly luckBoostedSpecies?: readonly SpeciesId[];
readonly boostFusions?: boolean; //MODIFIER REWORK PLEASE
readonly classicWaveRewards?: readonly EventWaveReward[]; // Rival battle rewards
readonly trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny
readonly music?: readonly EventMusicReplacement[];
readonly dailyRunChallenges?: readonly EventChallenge[];
readonly dailyRunStartingItems?: readonly ModifierTypeKeys[];
}
const timedEvents: readonly TimedEvent[] = [
{
name: "Winter Holiday Update",
eventType: EventType.SHINY,
shinyEncounterMultiplier: 2,
upgradeUnlockedVouchers: true,
startDate: new Date(Date.UTC(2024, 11, 21, 0)),
endDate: new Date(Date.UTC(2025, 0, 4, 0)),
bannerKey: "winter_holidays2024-event",
scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-Hans"],
eventEncounters: [
{ species: SpeciesId.GIMMIGHOUL, blockEvolution: true },
{ species: SpeciesId.DELIBIRD },
{ species: SpeciesId.STANTLER },
{ species: SpeciesId.CYNDAQUIL },
{ species: SpeciesId.PIPLUP },
{ species: SpeciesId.CHESPIN },
{ species: SpeciesId.BALTOY },
{ species: SpeciesId.SNOVER },
{ species: SpeciesId.CHINGLING },
{ species: SpeciesId.LITWICK },
{ species: SpeciesId.CUBCHOO },
{ species: SpeciesId.SWIRLIX },
{ species: SpeciesId.AMAURA },
{ species: SpeciesId.MUDBRAY },
{ species: SpeciesId.ROLYCOLY },
{ species: SpeciesId.MILCERY },
{ species: SpeciesId.SMOLIV },
{ species: SpeciesId.ALOLA_VULPIX },
{ species: SpeciesId.GALAR_DARUMAKA },
{ species: SpeciesId.IRON_BUNDLE },
],
delibirdyBuff: ["CATCHING_CHARM", "SHINY_CHARM", "ABILITY_CHARM", "EXP_CHARM", "SUPER_EXP_CHARM", "HEALING_CHARM"],
weather: [{ weatherType: WeatherType.SNOW, weight: 1 }],
mysteryEncounterTierChanges: [
{
mysteryEncounter: MysteryEncounterType.DELIBIRDY,
tier: MysteryEncounterTier.COMMON,
},
{ mysteryEncounter: MysteryEncounterType.PART_TIMER, disable: true },
{
mysteryEncounter: MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
disable: true,
},
{ mysteryEncounter: MysteryEncounterType.FIELD_TRIP, disable: true },
{
mysteryEncounter: MysteryEncounterType.DEPARTMENT_STORE_SALE,
disable: true,
},
],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
],
},
{
name: "Year of the Snake",
eventType: EventType.LUCK,
luckBoost: 1,
startDate: new Date(Date.UTC(2025, 0, 29, 0)),
endDate: new Date(Date.UTC(2025, 1, 3, 0)),
bannerKey: "yearofthesnakeevent",
scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-Hans"],
eventEncounters: [
{ species: SpeciesId.EKANS },
{ species: SpeciesId.ONIX },
{ species: SpeciesId.DRATINI },
{ species: SpeciesId.CLEFFA },
{ species: SpeciesId.UMBREON },
{ species: SpeciesId.DUNSPARCE },
{ species: SpeciesId.TEDDIURSA },
{ species: SpeciesId.SEVIPER },
{ species: SpeciesId.LUNATONE },
{ species: SpeciesId.CHINGLING },
{ species: SpeciesId.SNIVY },
{ species: SpeciesId.DARUMAKA },
{ species: SpeciesId.DRAMPA },
{ species: SpeciesId.SILICOBRA },
{ species: SpeciesId.BLOODMOON_URSALUNA },
],
luckBoostedSpecies: [
SpeciesId.EKANS,
SpeciesId.ARBOK,
SpeciesId.ONIX,
SpeciesId.STEELIX,
SpeciesId.DRATINI,
SpeciesId.DRAGONAIR,
SpeciesId.DRAGONITE,
SpeciesId.CLEFFA,
SpeciesId.CLEFAIRY,
SpeciesId.CLEFABLE,
SpeciesId.UMBREON,
SpeciesId.DUNSPARCE,
SpeciesId.DUDUNSPARCE,
SpeciesId.TEDDIURSA,
SpeciesId.URSARING,
SpeciesId.URSALUNA,
SpeciesId.SEVIPER,
SpeciesId.LUNATONE,
SpeciesId.RAYQUAZA,
SpeciesId.CHINGLING,
SpeciesId.CHIMECHO,
SpeciesId.CRESSELIA,
SpeciesId.DARKRAI,
SpeciesId.SNIVY,
SpeciesId.SERVINE,
SpeciesId.SERPERIOR,
SpeciesId.DARUMAKA,
SpeciesId.DARMANITAN,
SpeciesId.ZYGARDE,
SpeciesId.DRAMPA,
SpeciesId.LUNALA,
SpeciesId.BLACEPHALON,
SpeciesId.SILICOBRA,
SpeciesId.SANDACONDA,
SpeciesId.ROARING_MOON,
SpeciesId.BLOODMOON_URSALUNA,
],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
],
},
{
name: "Valentine",
eventType: EventType.SHINY,
startDate: new Date(Date.UTC(2025, 1, 10)),
endDate: new Date(Date.UTC(2025, 1, 21)),
boostFusions: true,
shinyEncounterMultiplier: 2,
bannerKey: "valentines2025event",
scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-Hans"],
eventEncounters: [
{ species: SpeciesId.NIDORAN_F },
{ species: SpeciesId.NIDORAN_M },
{ species: SpeciesId.IGGLYBUFF },
{ species: SpeciesId.SMOOCHUM },
{ species: SpeciesId.VOLBEAT },
{ species: SpeciesId.ILLUMISE },
{ species: SpeciesId.ROSELIA },
{ species: SpeciesId.LUVDISC },
{ species: SpeciesId.WOOBAT },
{ species: SpeciesId.FRILLISH },
{ species: SpeciesId.ALOMOMOLA },
{ species: SpeciesId.FURFROU, formIndex: 1 }, // Heart Trim
{ species: SpeciesId.ESPURR },
{ species: SpeciesId.SPRITZEE },
{ species: SpeciesId.SWIRLIX },
{ species: SpeciesId.APPLIN },
{ species: SpeciesId.MILCERY },
{ species: SpeciesId.INDEEDEE },
{ species: SpeciesId.TANDEMAUS },
{ species: SpeciesId.ENAMORUS },
],
luckBoostedSpecies: [SpeciesId.LUVDISC],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
],
},
{
name: "PKMNDAY2025",
eventType: EventType.LUCK,
startDate: new Date(Date.UTC(2025, 1, 27)),
endDate: new Date(Date.UTC(2025, 2, 4)),
classicFriendshipMultiplier: 4,
bannerKey: "pkmnday2025event",
scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-Hans"],
eventEncounters: [
{ species: SpeciesId.PIKACHU, formIndex: 1, blockEvolution: true }, // Partner Form
{ species: SpeciesId.EEVEE, formIndex: 1, blockEvolution: true }, // Partner Form
{ species: SpeciesId.CHIKORITA },
{ species: SpeciesId.TOTODILE },
{ species: SpeciesId.TEPIG },
],
luckBoostedSpecies: [
SpeciesId.PICHU,
SpeciesId.PIKACHU,
SpeciesId.RAICHU,
SpeciesId.ALOLA_RAICHU,
SpeciesId.PSYDUCK,
SpeciesId.GOLDUCK,
SpeciesId.EEVEE,
SpeciesId.FLAREON,
SpeciesId.JOLTEON,
SpeciesId.VAPOREON,
SpeciesId.ESPEON,
SpeciesId.UMBREON,
SpeciesId.LEAFEON,
SpeciesId.GLACEON,
SpeciesId.SYLVEON,
SpeciesId.CHIKORITA,
SpeciesId.BAYLEEF,
SpeciesId.MEGANIUM,
SpeciesId.TOTODILE,
SpeciesId.CROCONAW,
SpeciesId.FERALIGATR,
SpeciesId.TEPIG,
SpeciesId.PIGNITE,
SpeciesId.EMBOAR,
SpeciesId.ZYGARDE,
SpeciesId.ETERNAL_FLOETTE,
],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
],
},
{
name: "April Fools 2025",
eventType: EventType.LUCK,
startDate: new Date(Date.UTC(2025, 2, 31)),
endDate: new Date(Date.UTC(2025, 3, 3)),
bannerKey: "aprf25",
scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans"],
trainerShinyChance: 13107, // 13107/65536 = 1/5
music: [
["title", "title_afd"],
["battle_rival_3", "battle_rival_3_afd"],
],
dailyRunChallenges: [
{
challenge: Challenges.INVERSE_BATTLE,
value: 1,
},
],
},
{
name: "Shining Spring",
eventType: EventType.SHINY,
startDate: new Date(Date.UTC(2025, 4, 3)),
endDate: new Date(Date.UTC(2025, 4, 13)),
bannerKey: "spr25event",
scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans"],
shinyEncounterMultiplier: 2,
upgradeUnlockedVouchers: true,
eventEncounters: [
{ species: SpeciesId.HOPPIP },
{ species: SpeciesId.CELEBI },
{ species: SpeciesId.VOLBEAT },
{ species: SpeciesId.ILLUMISE },
{ species: SpeciesId.SPOINK },
{ species: SpeciesId.LILEEP },
{ species: SpeciesId.SHINX },
{ species: SpeciesId.PACHIRISU },
{ species: SpeciesId.CHERUBI },
{ species: SpeciesId.MUNCHLAX },
{ species: SpeciesId.TEPIG },
{ species: SpeciesId.PANSAGE },
{ species: SpeciesId.PANSEAR },
{ species: SpeciesId.PANPOUR },
{ species: SpeciesId.DARUMAKA },
{ species: SpeciesId.ARCHEN },
{ species: SpeciesId.DEERLING, formIndex: 0 }, // Spring Deerling
{ species: SpeciesId.CLAUNCHER },
{ species: SpeciesId.WISHIWASHI },
{ species: SpeciesId.DRAMPA },
{ species: SpeciesId.JANGMO_O },
{ species: SpeciesId.APPLIN },
],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
],
},
{
name: "Pride 25",
eventType: EventType.SHINY,
startDate: new Date(Date.UTC(2025, 5, 18)),
endDate: new Date(Date.UTC(2025, 5, 30)),
bannerKey: "pride2025",
scale: 0.105,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans", "zh-Hant"],
shinyEncounterMultiplier: 2,
eventEncounters: [
{ species: SpeciesId.CHARMANDER },
{ species: SpeciesId.SANDILE },
{ species: SpeciesId.FERROSEED },
{ species: SpeciesId.FOONGUS },
{ species: SpeciesId.CUTIEFLY },
{ species: SpeciesId.DEWPIDER },
{ species: SpeciesId.TYPE_NULL },
{ species: SpeciesId.MINIOR },
{ species: SpeciesId.SOBBLE },
{ species: SpeciesId.INDEEDEE },
{ species: SpeciesId.CAPSAKID },
{ species: SpeciesId.ALOLA_MEOWTH },
],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
],
dailyRunStartingItems: ["SHINY_CHARM", "ABILITY_CHARM"],
},
{
name: "Halloween 25",
eventType: EventType.SHINY,
startDate: new Date(Date.UTC(2025, 9, 30)),
endDate: new Date(Date.UTC(2025, 10, 10)),
bannerKey: "halloween2025",
scale: 0.19,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans", "zh-Hant", "da", "ru"],
shinyEncounterMultiplier: 2,
shinyCatchMultiplier: 3,
eventEncounters: [
{ species: SpeciesId.CATERPIE },
{ species: SpeciesId.SPEAROW },
{ species: SpeciesId.PARAS },
{ species: SpeciesId.LICKITUNG },
{ species: SpeciesId.AERODACTYL },
{ species: SpeciesId.SMOOCHUM },
{ species: SpeciesId.RALTS },
{ species: SpeciesId.GULPIN },
{ species: SpeciesId.FEEBAS },
{ species: SpeciesId.WYNAUT },
{ species: SpeciesId.CLAMPERL },
{ species: SpeciesId.BUDEW },
{ species: SpeciesId.DEOXYS },
{ species: SpeciesId.CHINGLING },
{ species: SpeciesId.DWEBBLE },
{ species: SpeciesId.TIRTOUGA },
{ species: SpeciesId.LARVESTA },
{ species: SpeciesId.SPRITZEE },
{ species: SpeciesId.SWIRLIX },
{ species: SpeciesId.BINACLE },
{ species: SpeciesId.PUMPKABOO },
{ species: SpeciesId.SANDYGAST },
],
classicWaveRewards: [
{ wave: 8, type: "SHINY_CHARM" },
{ wave: 8, type: "ABILITY_CHARM" },
{ wave: 8, type: "CATCHING_CHARM" },
{ wave: 25, type: "SHINY_CHARM" },
{ wave: 25, type: "CANDY_JAR" },
],
dailyRunStartingItems: ["ABILITY_CHARM", "SHINY_CHARM", "CANDY_JAR"],
},
];
export class TimedEventManager {
isActive(event: TimedEvent) {
const now = new Date();
return event.startDate < now && now < event.endDate;
}
/**
* For getting the active event
* @returns The first active {@linkcode TimedEvent} or `undefined` if there are no active events
*/
activeEvent(): TimedEvent | undefined {
return timedEvents.find((te: TimedEvent) => this.isActive(te));
}
isEventActive(): boolean {
return timedEvents.some((te: TimedEvent) => this.isActive(te));
}
/**
* Check whether the current {@linkcode TimedEvent} is active and for April Fools.
* @returns Whether the April Fools event is currently active.
*/
isAprilFoolsActive(): boolean {
return this.activeEvent()?.bannerKey?.startsWith("aprf") ?? false;
}
activeEventHasBanner(): boolean {
return this.activeEvent()?.bannerKey != null;
}
/**
* Get the multiplier for shiny encounters during a shiny {@linkcode TimedEvent}
* @returns the shiny encounter multiplier
*/
getShinyEncounterMultiplier(): number {
return this.activeEvent()?.shinyEncounterMultiplier ?? 1;
}
/**
* Get the multiplier for shiny catches during a shiny {@linkcode TimedEvent}
* @returns the shiny catch multiplier
*/
getShinyCatchMultiplier(): number {
return this.activeEvent()?.shinyCatchMultiplier ?? SHINY_CATCH_RATE_MULTIPLIER;
}
getEventBannerFilename(): string {
return this.activeEvent()?.bannerKey ?? "";
}
getEventBannerLangs(): string[] {
return [...(this.activeEvent()?.availableLangs ?? [])];
}
getEventEncounters(): EventEncounter[] {
return [...(this.activeEvent()?.eventEncounters ?? [])];
}
getAllValidEventEncounters(
allowSubLegendary = true,
allowLegendary = true,
allowMythical = true,
speciesFilter: PokemonSpeciesFilter,
): EventEncounter[] {
return this.getEventEncounters().filter(enc => {
const species = getPokemonSpecies(enc.species);
return (
(allowSubLegendary || !species.subLegendary)
&& (allowLegendary || !species.legendary)
&& (allowMythical || !species.mythical)
&& speciesFilter(species)
);
});
}
/**
* For events that change the classic candy friendship multiplier
* @returns The classic friendship multiplier of the active {@linkcode TimedEvent}, or the default {@linkcode CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER}
*/
getClassicFriendshipMultiplier(): number {
return this.activeEvent()?.classicFriendshipMultiplier ?? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER;
}
/**
* For events where defeated bosses (Gym Leaders, E4 etc) give out Voucher Plus even if they were defeated before
* @returns Whether vouchers should be upgraded
*/
getUpgradeUnlockedVouchers(): boolean {
return this.activeEvent()?.upgradeUnlockedVouchers ?? false;
}
/**
* For events where Delibirdy gives extra items
* @returns list of ids of {@linkcode ModifierType}s that Delibirdy hands out as a bonus
*/
getDelibirdyBuff(): string[] {
return [...(this.activeEvent()?.delibirdyBuff ?? [])];
}
/**
* For events where there's a set weather for town biome (other biomes are hard)
* @returns Event weathers for town
*/
getWeather(): WeatherPoolEntry[] {
return [...(this.activeEvent()?.weather ?? [])];
}
getAllMysteryEncounterChanges(): EventMysteryEncounterTier[] {
const ret: EventMysteryEncounterTier[] = [];
for (const te of timedEvents) {
if (this.isActive(te) && te.mysteryEncounterTierChanges != null) {
ret.push(...te.mysteryEncounterTierChanges);
}
}
return ret;
}
getEventMysteryEncountersDisabled(): MysteryEncounterType[] {
const ret: MysteryEncounterType[] = [];
const metChanges = this.activeEvent()?.mysteryEncounterTierChanges ?? [];
for (const metc of metChanges) {
if (metc.disable) {
ret.push(metc.mysteryEncounter);
}
}
return ret;
}
getMysteryEncounterTierForEvent(
encounterType: MysteryEncounterType,
normal: MysteryEncounterTier,
): MysteryEncounterTier {
const metChanges = this.activeEvent()?.mysteryEncounterTierChanges ?? [];
for (const metc of metChanges) {
if (metc.mysteryEncounter === encounterType) {
return metc.tier ?? normal;
}
}
return normal;
}
getEventLuckBoost(): number {
return this.activeEvent()?.luckBoost ?? 0;
}
getEventLuckBoostedSpecies(): SpeciesId[] {
return [...(this.activeEvent()?.luckBoostedSpecies ?? [])];
}
areFusionsBoosted(): boolean {
return this.activeEvent()?.boostFusions ?? false;
}
/**
* Gets all the modifier types associated with a certain wave during an event
* @see EventWaveReward
* @param wave the wave to check for associated rewards
* @returns array of strings of the event modifier reward types
*/
getFixedBattleEventRewards(wave: number): ModifierTypeKeys[] {
return (
this.activeEvent()
?.classicWaveRewards?.filter(cwr => cwr.wave === wave)
.map(cwr => cwr.type) ?? []
);
}
/**
* Get the extra shiny chance for trainers due to event
*/
getClassicTrainerShinyChance(): number {
return this.activeEvent()?.trainerShinyChance ?? 0;
}
getEventBgmReplacement(bgm: string): string {
const eventMusicReplacements = this.activeEvent()?.music ?? [];
for (const emr of eventMusicReplacements) {
if (emr[0] === bgm) {
console.log(`it is ${this.activeEvent()?.name} so instead of ${emr[0]} we play ${emr[1]}`);
return emr[1];
}
}
return bgm;
}
/**
* Activate any challenges on {@linkcode globalScene.gameMode} for the currently active event
*/
startEventChallenges(): void {
for (const eventChal of this.activeEvent()?.dailyRunChallenges ?? []) {
globalScene.gameMode.setChallengeValue(eventChal.challenge, eventChal.value);
}
}
getEventDailyStartingItems(): readonly ModifierTypeKeys[] {
return this.activeEvent()?.dailyRunStartingItems ?? [];
}
}
export class TimedEventDisplay extends Phaser.GameObjects.Container {
private event: TimedEvent | nil;
private eventTimerText: Phaser.GameObjects.Text;
private banner: Phaser.GameObjects.Image;
private availableWidth: number;
private eventTimer: NodeJS.Timeout | null;
constructor(x: number, y: number, event?: TimedEvent) {
super(globalScene, x, y);
this.availableWidth = globalScene.scaledCanvas.width;
this.event = event;
this.setVisible(false);
}
/**
* Set the width that can be used to display the event timer and banner. By default
* these elements get centered horizontally in that space, in the bottom left of the screen
*/
setWidth(width: number) {
if (width !== this.availableWidth) {
this.availableWidth = width;
const xPosition = this.availableWidth / 2 + (this.event?.xOffset ?? 0);
if (this.banner) {
this.banner.x = xPosition;
}
if (this.eventTimerText) {
this.eventTimerText.x = xPosition;
}
}
}
setup() {
const lang = i18next.resolvedLanguage;
if (this.event?.bannerKey) {
let key = this.event.bannerKey;
if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) {
if (this.event.availableLangs.includes(lang)) {
key += "-" + lang;
} else {
key += "-en";
}
}
console.log(key);
console.log(this.event.bannerKey);
const padding = 5;
const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY;
const yPosition = globalScene.scaledCanvas.height - padding - (showTimer ? 10 : 0) - (this.event.yOffset ?? 0);
this.banner = new Phaser.GameObjects.Image(globalScene, this.availableWidth / 2, yPosition - padding, key);
this.banner.setName("img-event-banner");
this.banner.setOrigin(0.5, 1);
this.banner.setScale(this.event.scale ?? 0.18);
if (showTimer) {
this.eventTimerText = addTextObject(
this.banner.x,
this.banner.y + 2,
this.timeToGo(this.event.endDate),
TextStyle.WINDOW,
);
this.eventTimerText.setName("text-event-timer");
this.eventTimerText.setScale(0.15);
this.eventTimerText.setOrigin(0.5, 0);
this.add(this.eventTimerText);
}
this.add(this.banner);
}
}
show() {
this.setVisible(true);
this.updateCountdown();
this.eventTimer = setInterval(() => {
this.updateCountdown();
}, 1000);
}
clear() {
this.setVisible(false);
this.eventTimer && clearInterval(this.eventTimer);
this.eventTimer = null;
}
private timeToGo(date: Date) {
// Utility to add leading zero
function z(n) {
return (n < 10 ? "0" : "") + n;
}
const now = new Date();
let diff = Math.abs(date.getTime() - now.getTime());
// Allow for previous times
diff = Math.abs(diff);
// Get time components
const days = (diff / 8.64e7) | 0;
const hours = ((diff % 8.64e7) / 3.6e6) | 0;
const mins = ((diff % 3.6e6) / 6e4) | 0;
const secs = Math.round((diff % 6e4) / 1e3);
// Return formatted string
return i18next.t("menu:eventTimer", {
days: z(days),
hours: z(hours),
mins: z(mins),
secs: z(secs),
});
}
updateCountdown() {
if (this.event && this.event.eventType !== EventType.NO_TIMER_DISPLAY) {
this.eventTimerText.setText(this.timeToGo(this.event.endDate));
}
}
}