diff --git a/public/audio/bgm/battle_rival_3_afd.mp3 b/public/audio/bgm/battle_rival_3_afd.mp3 new file mode 100644 index 00000000000..6dec5c861c6 Binary files /dev/null and b/public/audio/bgm/battle_rival_3_afd.mp3 differ diff --git a/public/audio/bgm/title_afd.mp3 b/public/audio/bgm/title_afd.mp3 new file mode 100644 index 00000000000..c427d86b397 Binary files /dev/null and b/public/audio/bgm/title_afd.mp3 differ diff --git a/public/images/events/aprf25-de.png b/public/images/events/aprf25-de.png new file mode 100644 index 00000000000..d4bb7ebdc50 Binary files /dev/null and b/public/images/events/aprf25-de.png differ diff --git a/public/images/events/aprf25-en.png b/public/images/events/aprf25-en.png new file mode 100644 index 00000000000..8f7268b01b6 Binary files /dev/null and b/public/images/events/aprf25-en.png differ diff --git a/public/images/events/aprf25-es-ES.png b/public/images/events/aprf25-es-ES.png new file mode 100644 index 00000000000..a6136a2c8de Binary files /dev/null and b/public/images/events/aprf25-es-ES.png differ diff --git a/public/images/events/aprf25-es-MX.png b/public/images/events/aprf25-es-MX.png new file mode 100644 index 00000000000..a6136a2c8de Binary files /dev/null and b/public/images/events/aprf25-es-MX.png differ diff --git a/public/images/events/aprf25-fr.png b/public/images/events/aprf25-fr.png new file mode 100644 index 00000000000..c68264c75dd Binary files /dev/null and b/public/images/events/aprf25-fr.png differ diff --git a/public/images/events/aprf25-it.png b/public/images/events/aprf25-it.png new file mode 100644 index 00000000000..01bc0d2a1f0 Binary files /dev/null and b/public/images/events/aprf25-it.png differ diff --git a/public/images/events/aprf25-ja.png b/public/images/events/aprf25-ja.png new file mode 100644 index 00000000000..c6b62a3672e Binary files /dev/null and b/public/images/events/aprf25-ja.png differ diff --git a/public/images/events/aprf25-ko.png b/public/images/events/aprf25-ko.png new file mode 100644 index 00000000000..bcc87e33ac1 Binary files /dev/null and b/public/images/events/aprf25-ko.png differ diff --git a/public/images/events/aprf25-pt-BR.png b/public/images/events/aprf25-pt-BR.png new file mode 100644 index 00000000000..f56f5b5c1e9 Binary files /dev/null and b/public/images/events/aprf25-pt-BR.png differ diff --git a/public/images/events/aprf25-zh-CN.png b/public/images/events/aprf25-zh-CN.png new file mode 100644 index 00000000000..57b2c3ec5be Binary files /dev/null and b/public/images/events/aprf25-zh-CN.png differ diff --git a/public/locales b/public/locales index e599780a369..488c2c7d01c 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit e599780a369f87a96ab0469a8908cea86628145f +Subproject commit 488c2c7d01c3c888a1925a18ed0269e590c25675 diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5797fda5611..7ab96566ef5 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -167,9 +167,10 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { StatusEffect } from "#enums/status-effect"; -import { initGlobalScene } from "#app/global-scene"; +import { globalScene, initGlobalScene } from "#app/global-scene"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; +import { timedEventManager } from "./global-event-manager"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -2268,6 +2269,9 @@ export default class BattleScene extends SceneBase { if (bgmName === undefined) { bgmName = this.currentBattle?.getBgmOverride() || this.arena?.bgm; } + + bgmName = timedEventManager.getEventBgmReplacement(bgmName); + if (this.bgm && bgmName === this.bgm.key) { if (!this.bgm.isPlaying) { this.bgm.play({ @@ -2660,6 +2664,10 @@ export default class BattleScene extends SceneBase { return 41.42; case "mystery_encounter_delibirdy": // Firel Delibirdy return 82.28; + case "title_afd": // Andr06 - PokéRogue Title Remix (AFD) + return 47.660; + case "battle_rival_3_afd": // Andr06 - Final N Battle Remix (AFD) + return 49.147; } return 0; diff --git a/src/constants.ts b/src/constants.ts index 63f00b9f33f..927575c0a28 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,7 +2,7 @@ export const PLAYER_PARTY_MAX_SIZE: number = 6; /** Whether to use seasonal splash messages in general */ -export const USE_SEASONAL_SPLASH_MESSAGES: boolean = false; +export const USE_SEASONAL_SPLASH_MESSAGES: boolean = true; /** Name of the session ID cookie */ export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index c1486ff100b..364484cb511 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -37,6 +37,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; +import { timedEventManager } from "#app/global-event-manager"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/delibirdy"; @@ -56,7 +57,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [ const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; const doEventReward = () => { - const event_buff = globalScene.eventManager.getDelibirdyBuff(); + const event_buff = timedEventManager.getDelibirdyBuff(); if (event_buff.length > 0) { const candidates = event_buff.filter(c => { const mtype = generateModifierType(modifierTypes[c]); diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index c13501c4511..f2b7001f81b 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -46,6 +46,7 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en import type { PokeballType } from "#enums/pokeball"; import { doShinySparkleAnim } from "#app/field/anims"; import { TrainerType } from "#enums/trainer-type"; +import { timedEventManager } from "#app/global-event-manager"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -273,8 +274,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil // Extra shiny roll at 1/128 odds (boosted by events and charms) if (!tradePokemon.shiny) { const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE); - if (globalScene.eventManager.isEventActive()) { - shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); + if (timedEventManager.isEventActive()) { + shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 5c6acf43e26..76d07bf01ba 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -65,6 +65,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; +import { timedEventManager } from "#app/global-event-manager"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -1046,7 +1047,7 @@ export function handleMysteryEncounterTurnStartEffects(): boolean { export function getRandomEncounterSpecies(level: number, isBoss = false, rerollHidden = false): EnemyPokemon { let bossSpecies: PokemonSpecies; let isEventEncounter = false; - const eventEncounters = globalScene.eventManager.getEventEncounters(); + const eventEncounters = timedEventManager.getEventEncounters(); let formIndex: number | undefined; if (eventEncounters.length > 0 && randSeedInt(2) === 1) { diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 0e53cd71add..a5ba19290fe 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -32,6 +32,7 @@ import { TeraAIMode } from "#enums/tera-ai-mode"; import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerType } from "#enums/trainer-type"; +import { timedEventManager } from "#app/global-event-manager"; // Type imports import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; @@ -516,13 +517,13 @@ export class TrainerConfig { // return ret; // } - setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { - this.eventRewardFuncs = modifierTypeFuncs.map(func => () => { - const modifierTypeFunc = func(); - const modifierType = modifierTypeFunc(); - modifierType.withIdFromFunc(modifierTypeFunc); - return modifierType; - }); + /** + * Sets eventRewardFuncs to the active event rewards for the specified wave + * @param wave Associated with {@linkcode getFixedBattleEventRewards} + * @returns this + */ + setEventModifierRewardFuncs(wave: number): TrainerConfig { + this.eventRewardFuncs = timedEventManager.getFixedBattleEventRewards(wave).map(r => modifierTypes[r]); return this; } @@ -3696,11 +3697,7 @@ export const trainerConfigs: TrainerConfigs = { () => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE, ) - .setEventModifierRewardFuncs( - () => modifierTypes.SHINY_CHARM, - () => modifierTypes.ABILITY_CHARM, - () => modifierTypes.CATCHING_CHARM, - ) + .setEventModifierRewardFuncs(8) .setPartyMemberFunc( 0, getRandomPartyMemberFunc( @@ -3768,7 +3765,7 @@ export const trainerConfigs: TrainerConfigs = { .setMixedBattleBgm("battle_rival") .setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) - .setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM) + .setEventModifierRewardFuncs(25) .setPartyMemberFunc( 0, getRandomPartyMemberFunc( @@ -4077,7 +4074,7 @@ export const trainerConfigs: TrainerConfigs = { getRandomPartyMemberFunc([Species.RAYQUAZA], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 3); p.pokeball = PokeballType.MASTER_BALL; - p.shiny = true; + p.shiny = timedEventManager.getClassicTrainerShinyChance() === 0; p.variant = 1; }), ) @@ -4174,7 +4171,7 @@ export const trainerConfigs: TrainerConfigs = { p.setBoss(); p.generateAndPopulateMoveset(); p.pokeball = PokeballType.MASTER_BALL; - p.shiny = true; + p.shiny = timedEventManager.getClassicTrainerShinyChance() === 0; p.variant = 1; p.formIndex = 1; // Mega Rayquaza p.generateName(); diff --git a/src/data/weather.ts b/src/data/weather.ts index c2b0263c9f6..34978232377 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -11,6 +11,7 @@ import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import type { Arena } from "#app/field/arena"; +import { timedEventManager } from "#app/global-event-manager"; export class Weather { public weatherType: WeatherType; @@ -405,8 +406,8 @@ export function getRandomWeatherType(arena: Arena): WeatherType { break; } - if (arena.biomeType === Biome.TOWN && globalScene.eventManager.isEventActive()) { - globalScene.eventManager.getWeather()?.map(w => weatherPool.push(w)); + if (arena.biomeType === Biome.TOWN && timedEventManager.isEventActive()) { + timedEventManager.getWeather()?.map(w => weatherPool.push(w)); } if (weatherPool.length > 1) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b595d516f53..20a8855fa55 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -263,6 +263,7 @@ import { Nature } from "#enums/nature"; import { StatusEffect } from "#enums/status-effect"; import { doShinySparkleAnim } from "#app/field/anims"; import { MoveFlags } from "#enums/MoveFlags"; +import { timedEventManager } from "#app/global-event-manager"; export enum LearnMoveSituation { MISC, @@ -2983,8 +2984,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined) { - if (globalScene.eventManager.isEventActive()) { - shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); + if (timedEventManager.isEventActive()) { + const tchance = timedEventManager.getClassicTrainerShinyChance(); + shinyThreshold.value *= timedEventManager.getShinyMultiplier(); + if (this.hasTrainer() && tchance > 0) { + shinyThreshold.value = Math.max(tchance, shinyThreshold.value); // Choose the higher boost + } } if (!this.hasTrainer()) { globalScene.applyModifiers( @@ -3025,8 +3030,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (thresholdOverride !== undefined && applyModifiersToOverride) { shinyThreshold.value = thresholdOverride; } - if (globalScene.eventManager.isEventActive()) { - shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); + if (timedEventManager.isEventActive()) { + shinyThreshold.value *= timedEventManager.getShinyMultiplier(); } if (!this.hasTrainer()) { globalScene.applyModifiers( @@ -6469,10 +6474,10 @@ export class PlayerPokemon extends Pokemon { amount, ); const candyFriendshipMultiplier = globalScene.gameMode.isClassic - ? globalScene.eventManager.getClassicFriendshipMultiplier() + ? timedEventManager.getClassicFriendshipMultiplier() : 1; const fusionReduction = fusionStarterSpeciesId - ? globalScene.eventManager.areFusionsBoosted() + ? timedEventManager.areFusionsBoosted() ? 1.5 // Divide candy gain for fusions by 1.5 during events : 2 // 2 for fusions outside events : 1; // 1 for non-fused mons diff --git a/src/game-mode.ts b/src/game-mode.ts index 5e27c32f015..c340768ef77 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -68,6 +68,19 @@ export class GameMode implements GameModeConfig { this.battleConfig = battleConfig || {}; } + /** + * Enables challenges if they are disabled and sets the specified challenge's value + * @param challenge The challenge to set + * @param value The value to give the challenge. Impact depends on the specific challenge + */ + setChallengeValue(challenge: Challenges, value: number) { + if (!this.isChallenge) { + this.isChallenge = true; + this.challenges = allChallenges.map(c => copyChallenge(c)); + } + this.challenges.filter((chal: Challenge) => chal.id === challenge).map((chal: Challenge) => (chal.value = value)); + } + /** * Helper function to see if a GameMode has a specific challenge type * @param challenge the Challenges it looks for diff --git a/src/global-event-manager.ts b/src/global-event-manager.ts new file mode 100644 index 00000000000..3df3d17b5e9 --- /dev/null +++ b/src/global-event-manager.ts @@ -0,0 +1,3 @@ +import { TimedEventManager } from "./timed-event-manager"; + +export const timedEventManager = new TimedEventManager(); diff --git a/src/loading-scene.ts b/src/loading-scene.ts index d1f0b0ca242..f99831c53bc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -20,6 +20,7 @@ import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; import { initVouchers } from "#app/system/voucher"; import { Biome } from "#enums/biome"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; +import { timedEventManager } from "./global-event-manager"; export class LoadingScene extends SceneBase { public static readonly KEY = "loading"; @@ -250,11 +251,13 @@ export class LoadingScene extends SceneBase { this.loadAtlas("statuses", ""); this.loadAtlas("types", ""); } - const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-MX", "pt-BR", "zh-CN", "zh-TW", "ca-ES"]; - if (lang && availableLangs.includes(lang)) { - this.loadImage(`pkmnday2025event-${lang}`, "events"); - } else { - this.loadImage("pkmnday2025event-en", "events"); + if (timedEventManager.activeEventHasBanner()) { + const availableLangs = timedEventManager.getEventBannerLangs(); + if (lang && availableLangs.includes(lang)) { + this.loadImage(`${timedEventManager.getEventBannerFilename()}-${lang}`, "events"); + } else { + this.loadImage(`${timedEventManager.getEventBannerFilename()}-en`, "events"); + } } this.loadAtlas("statuses", ""); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f9770e9c6cc..c01d9be0953 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -127,6 +127,7 @@ import type { PermanentStat, TempBattleStat } from "#enums/stat"; import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { timedEventManager } from "#app/global-event-manager"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -2655,7 +2656,7 @@ const modifierPool: ModifierPool = { if (globalScene.gameMode.isSplicedOnly) { return 4; } - if (globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) { + if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) { return 2; } } @@ -2939,7 +2940,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType( modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => - !(globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) && + !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) && !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 @@ -3703,7 +3704,7 @@ export function getPartyLuckValue(party: Pokemon[]): number { ); return DailyLuck.value; } - const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies(); + const eventSpecies = timedEventManager.getEventLuckBoostedSpecies(); const luck = Phaser.Math.Clamp( party .map(p => (p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0)) @@ -3711,7 +3712,7 @@ export function getPartyLuckValue(party: Pokemon[]): number { 0, 14, ); - return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14); + return Math.min(timedEventManager.getEventLuckBoost() + (luck ?? 0), 14); } export function getLuckString(luckValue: number): string { diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 5b69f8db45c..dc455a0a62a 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -212,6 +212,8 @@ export class TitlePhase extends Phase { const generateDaily = (seed: string) => { globalScene.gameMode = getGameMode(GameModes.DAILY); + // Daily runs don't support all challenges yet (starter select restrictions aren't considered) + globalScene.eventManager.startEventChallenges(); globalScene.setSeed(seed); globalScene.resetSeed(0); diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index f7b2eb2bb66..a024885121f 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -11,6 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import { globalScene } from "#app/global-scene"; import { Biome } from "#app/enums/biome"; import { achvs } from "#app/system/achv"; +import { timedEventManager } from "#app/global-event-manager"; export class TrainerVictoryPhase extends BattlePhase { constructor() { @@ -29,7 +30,7 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); } - if (globalScene.eventManager.isEventActive()) { + if (timedEventManager.isEventActive()) { for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) { globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc)); } @@ -42,7 +43,7 @@ export class TrainerVictoryPhase extends BattlePhase { !globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) && globalScene.currentBattle.trainer?.config.isBoss ) { - if (globalScene.eventManager.getUpgradeUnlockedVouchers()) { + if (timedEventManager.getUpgradeUnlockedVouchers()) { globalScene.unshiftPhase( new ModifierRewardPhase( [ diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 80667b033ad..7bbd157948b 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -9,6 +9,7 @@ import { WeatherType } from "#enums/weather-type"; import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "./data/balance/starters"; import { MysteryEncounterType } from "./enums/mystery-encounter-type"; import { MysteryEncounterTier } from "./enums/mystery-encounter-tier"; +import { Challenges } from "#enums/challenges"; export enum EventType { SHINY, @@ -36,6 +37,18 @@ interface EventMysteryEncounterTier { disable?: boolean; } +interface EventWaveReward { + wave: number; + type: string; +} + +type EventMusicReplacement = [string, string]; + +interface EventChallenge { + challenge: Challenges; + value: number; +} + interface TimedEvent extends EventBanner { name: string; eventType: EventType; @@ -51,6 +64,10 @@ interface TimedEvent extends EventBanner { mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; luckBoostedSpecies?: Species[]; boostFusions?: boolean; //MODIFIER REWORK PLEASE + classicWaveRewards?: EventWaveReward[]; // Rival battle rewards + trainerShinyChance?: number; // Odds over 65536 of trainer mon generating as shiny + music?: EventMusicReplacement[]; + dailyRunChallenges?: EventChallenge[]; } const timedEvents: TimedEvent[] = [ @@ -61,7 +78,7 @@ const timedEvents: TimedEvent[] = [ upgradeUnlockedVouchers: true, startDate: new Date(Date.UTC(2024, 11, 21, 0)), endDate: new Date(Date.UTC(2025, 0, 4, 0)), - bannerKey: "winter_holidays2024-event-", + bannerKey: "winter_holidays2024-event", scale: 0.21, availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], eventEncounters: [ @@ -104,6 +121,12 @@ const timedEvents: TimedEvent[] = [ 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", @@ -111,7 +134,7 @@ const timedEvents: TimedEvent[] = [ luckBoost: 1, startDate: new Date(Date.UTC(2025, 0, 29, 0)), endDate: new Date(Date.UTC(2025, 1, 3, 0)), - bannerKey: "yearofthesnakeevent-", + bannerKey: "yearofthesnakeevent", scale: 0.21, availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], eventEncounters: [ @@ -169,6 +192,12 @@ const timedEvents: TimedEvent[] = [ Species.ROARING_MOON, Species.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", @@ -177,7 +206,7 @@ const timedEvents: TimedEvent[] = [ endDate: new Date(Date.UTC(2025, 1, 21)), boostFusions: true, shinyMultiplier: 2, - bannerKey: "valentines2025event-", + bannerKey: "valentines2025event", scale: 0.21, availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], eventEncounters: [ @@ -203,6 +232,12 @@ const timedEvents: TimedEvent[] = [ { species: Species.ENAMORUS }, ], luckBoostedSpecies: [Species.LUVDISC], + classicWaveRewards: [ + { wave: 8, type: "SHINY_CHARM" }, + { wave: 8, type: "ABILITY_CHARM" }, + { wave: 8, type: "CATCHING_CHARM" }, + { wave: 25, type: "SHINY_CHARM" }, + ], }, { name: "PKMNDAY2025", @@ -210,7 +245,7 @@ const timedEvents: TimedEvent[] = [ startDate: new Date(Date.UTC(2025, 1, 27)), endDate: new Date(Date.UTC(2025, 2, 4)), classicFriendshipMultiplier: 4, - bannerKey: "pkmnday2025event-", + bannerKey: "pkmnday2025event", scale: 0.21, availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], eventEncounters: [ @@ -248,6 +283,32 @@ const timedEvents: TimedEvent[] = [ Species.ZYGARDE, Species.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-MX", "pt-BR", "zh-CN"], + trainerShinyChance: 13107, // 13107/65536 = 1/5 + music: [ + ["title", "title_afd"], + ["battle_rival_3", "battle_rival_3_afd"], + ], + dailyRunChallenges: [ + { + challenge: Challenges.INVERSE_BATTLE, + value: 1, + }, + ], }, ]; @@ -265,7 +326,7 @@ export class TimedEventManager { } activeEventHasBanner(): boolean { - const activeEvents = timedEvents.filter(te => this.isActive(te) && te.hasOwnProperty("bannerFilename")); + const activeEvents = timedEvents.filter(te => this.isActive(te) && te.hasOwnProperty("bannerKey")); return activeEvents.length > 0; } @@ -283,6 +344,12 @@ export class TimedEventManager { return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerKey ?? ""; } + getEventBannerLangs(): string[] { + const ret: string[] = []; + ret.push(...timedEvents.find(te => this.isActive(te) && !isNullOrUndefined(te.availableLangs))?.availableLangs!); + return ret; + } + getEventEncounters(): EventEncounter[] { const ret: EventEncounter[] = []; timedEvents @@ -417,6 +484,55 @@ export class TimedEventManager { areFusionsBoosted(): boolean { return timedEvents.some(te => this.isActive(te) && te.boostFusions); } + + /** + * 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): string[] { + const ret: string[] = []; + timedEvents + .filter(te => this.isActive(te) && !isNullOrUndefined(te.classicWaveRewards)) + .map(te => { + ret.push(...te.classicWaveRewards!.filter(cwr => cwr.wave === wave).map(cwr => cwr.type)); + }); + return ret; + } + + // Gets the extra shiny chance for trainers due to event (odds/65536) + getClassicTrainerShinyChance(): number { + let ret = 0; + const tsEvents = timedEvents.filter(te => this.isActive(te) && !isNullOrUndefined(te.trainerShinyChance)); + tsEvents.map(t => (ret += t.trainerShinyChance!)); + return ret; + } + + getEventBgmReplacement(bgm: string): string { + let ret = bgm; + timedEvents.map(te => { + if (this.isActive(te) && !isNullOrUndefined(te.music)) { + te.music.map(mr => { + if (mr[0] === bgm) { + console.log(`it is ${te.name} so instead of ${mr[0]} we play ${mr[1]}`); + ret = mr[1]; + } + }); + } + }); + return ret; + } + + /** + * Activates any challenges on {@linkcode globalScene.gameMode} for the currently active event + */ + startEventChallenges(): void { + const challenges = this.activeEvent()?.dailyRunChallenges; + challenges?.forEach((eventChal: EventChallenge) => + globalScene.gameMode.setChallengeValue(eventChal.challenge, eventChal.value), + ); + } } export class TimedEventDisplay extends Phaser.GameObjects.Container { @@ -456,11 +572,12 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container { let key = this.event.bannerKey; if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) { if (this.event.availableLangs.includes(lang)) { - key += lang; + key += "-" + lang; } else { - key += "en"; + key += "-en"; } } + console.log(key); console.log(this.event.bannerKey); const padding = 5; const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY; diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index a9fdf22ec57..d87d4e5ca79 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -11,6 +11,7 @@ import { globalScene } from "#app/global-scene"; import type { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { PlayerGender } from "#enums/player-gender"; +import { timedEventManager } from "#app/global-event-manager"; export default class TitleUiHandler extends OptionSelectUiHandler { /** If the stats can not be retrieved, use this fallback value */ @@ -43,8 +44,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler { logo.setOrigin(0.5, 0); this.titleContainer.add(logo); - if (globalScene.eventManager.isEventActive()) { - this.eventDisplay = new TimedEventDisplay(0, 0, globalScene.eventManager.activeEvent()); + if (timedEventManager.isEventActive()) { + this.eventDisplay = new TimedEventDisplay(0, 0, timedEventManager.activeEvent()); this.eventDisplay.setup(); this.titleContainer.add(this.eventDisplay); } @@ -142,7 +143,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { const ui = this.getUi(); - if (globalScene.eventManager.isEventActive()) { + if (timedEventManager.isEventActive()) { this.eventDisplay.setWidth(globalScene.scaledCanvas.width - this.optionSelectBg.width - this.optionSelectBg.x); this.eventDisplay.show(); }