mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-17 15:25:22 +01:00
754 lines
24 KiB
TypeScript
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));
|
|
}
|
|
}
|
|
}
|