[Misc] AFD Event (#5596)

* global timed event manager

* more

* Music change

* Add AFD track loop points

* Add AFD music tracks

* changed music for afd

* Enable Seasonal Splash Text, adjust event values

* Add daily run challenge support

* update event date, change trainer shiny chance to 20%

* add banners lol

* fix activeeventhasbanner function

* Fix banner

* Update locales submodule

---------

Co-authored-by: AJ Fontaine <fontbane@gmail.com>
Co-authored-by: damocleas <damocleas25@gmail.com>
Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com>
Co-authored-by: Dean <me@deann.dev>
Co-authored-by: AJ Fontaine <36677462+Fontbane@users.noreply.github.com>
This commit is contained in:
NightKev 2025-03-31 15:11:01 -07:00 committed by GitHub
parent 6add614e1c
commit efa3662099
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 206 additions and 51 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@ -1 +1 @@
Subproject commit e599780a369f87a96ab0469a8908cea86628145f Subproject commit 488c2c7d01c3c888a1925a18ed0269e590c25675

View File

@ -167,9 +167,10 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
import { StatusEffect } from "#enums/status-effect"; 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 { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { HideAbilityPhase } from "#app/phases/hide-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"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -2268,6 +2269,9 @@ export default class BattleScene extends SceneBase {
if (bgmName === undefined) { if (bgmName === undefined) {
bgmName = this.currentBattle?.getBgmOverride() || this.arena?.bgm; bgmName = this.currentBattle?.getBgmOverride() || this.arena?.bgm;
} }
bgmName = timedEventManager.getEventBgmReplacement(bgmName);
if (this.bgm && bgmName === this.bgm.key) { if (this.bgm && bgmName === this.bgm.key) {
if (!this.bgm.isPlaying) { if (!this.bgm.isPlaying) {
this.bgm.play({ this.bgm.play({
@ -2660,6 +2664,10 @@ export default class BattleScene extends SceneBase {
return 41.42; return 41.42;
case "mystery_encounter_delibirdy": // Firel Delibirdy case "mystery_encounter_delibirdy": // Firel Delibirdy
return 82.28; 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; return 0;

View File

@ -2,7 +2,7 @@
export const PLAYER_PARTY_MAX_SIZE: number = 6; export const PLAYER_PARTY_MAX_SIZE: number = 6;
/** Whether to use seasonal splash messages in general */ /** 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 */ /** Name of the session ID cookie */
export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId"; export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId";

View File

@ -37,6 +37,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { timedEventManager } from "#app/global-event-manager";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/delibirdy"; const namespace = "mysteryEncounters/delibirdy";
@ -56,7 +57,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
const doEventReward = () => { const doEventReward = () => {
const event_buff = globalScene.eventManager.getDelibirdyBuff(); const event_buff = timedEventManager.getDelibirdyBuff();
if (event_buff.length > 0) { if (event_buff.length > 0) {
const candidates = event_buff.filter(c => { const candidates = event_buff.filter(c => {
const mtype = generateModifierType(modifierTypes[c]); const mtype = generateModifierType(modifierTypes[c]);

View File

@ -46,6 +46,7 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
import { doShinySparkleAnim } from "#app/field/anims"; import { doShinySparkleAnim } from "#app/field/anims";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { timedEventManager } from "#app/global-event-manager";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/globalTradeSystem"; 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) // Extra shiny roll at 1/128 odds (boosted by events and charms)
if (!tradePokemon.shiny) { if (!tradePokemon.shiny) {
const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE); const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE);
if (globalScene.eventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); shinyThreshold.value *= timedEventManager.getShinyMultiplier();
} }
globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);

View File

@ -65,6 +65,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getNatureName } from "#app/data/nature"; import { getNatureName } from "#app/data/nature";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { timedEventManager } from "#app/global-event-manager";
/** /**
* Animates exclamation sprite over trainer's head at start of encounter * 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 { export function getRandomEncounterSpecies(level: number, isBoss = false, rerollHidden = false): EnemyPokemon {
let bossSpecies: PokemonSpecies; let bossSpecies: PokemonSpecies;
let isEventEncounter = false; let isEventEncounter = false;
const eventEncounters = globalScene.eventManager.getEventEncounters(); const eventEncounters = timedEventManager.getEventEncounters();
let formIndex: number | undefined; let formIndex: number | undefined;
if (eventEncounters.length > 0 && randSeedInt(2) === 1) { if (eventEncounters.length > 0 && randSeedInt(2) === 1) {

View File

@ -32,6 +32,7 @@ import { TeraAIMode } from "#enums/tera-ai-mode";
import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { timedEventManager } from "#app/global-event-manager";
// Type imports // Type imports
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
@ -516,13 +517,13 @@ export class TrainerConfig {
// return ret; // return ret;
// } // }
setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { /**
this.eventRewardFuncs = modifierTypeFuncs.map(func => () => { * Sets eventRewardFuncs to the active event rewards for the specified wave
const modifierTypeFunc = func(); * @param wave Associated with {@linkcode getFixedBattleEventRewards}
const modifierType = modifierTypeFunc(); * @returns this
modifierType.withIdFromFunc(modifierTypeFunc); */
return modifierType; setEventModifierRewardFuncs(wave: number): TrainerConfig {
}); this.eventRewardFuncs = timedEventManager.getFixedBattleEventRewards(wave).map(r => modifierTypes[r]);
return this; return this;
} }
@ -3696,11 +3697,7 @@ export const trainerConfigs: TrainerConfigs = {
() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.SUPER_EXP_CHARM,
() => modifierTypes.EXP_SHARE, () => modifierTypes.EXP_SHARE,
) )
.setEventModifierRewardFuncs( .setEventModifierRewardFuncs(8)
() => modifierTypes.SHINY_CHARM,
() => modifierTypes.ABILITY_CHARM,
() => modifierTypes.CATCHING_CHARM,
)
.setPartyMemberFunc( .setPartyMemberFunc(
0, 0,
getRandomPartyMemberFunc( getRandomPartyMemberFunc(
@ -3768,7 +3765,7 @@ export const trainerConfigs: TrainerConfigs = {
.setMixedBattleBgm("battle_rival") .setMixedBattleBgm("battle_rival")
.setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setPartyTemplates(trainerPartyTemplates.RIVAL_2)
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
.setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM) .setEventModifierRewardFuncs(25)
.setPartyMemberFunc( .setPartyMemberFunc(
0, 0,
getRandomPartyMemberFunc( getRandomPartyMemberFunc(
@ -4077,7 +4074,7 @@ export const trainerConfigs: TrainerConfigs = {
getRandomPartyMemberFunc([Species.RAYQUAZA], TrainerSlot.TRAINER, true, p => { getRandomPartyMemberFunc([Species.RAYQUAZA], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3); p.setBoss(true, 3);
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
p.shiny = true; p.shiny = timedEventManager.getClassicTrainerShinyChance() === 0;
p.variant = 1; p.variant = 1;
}), }),
) )
@ -4174,7 +4171,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(); p.setBoss();
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
p.shiny = true; p.shiny = timedEventManager.getClassicTrainerShinyChance() === 0;
p.variant = 1; p.variant = 1;
p.formIndex = 1; // Mega Rayquaza p.formIndex = 1; // Mega Rayquaza
p.generateName(); p.generateName();

View File

@ -11,6 +11,7 @@ import { TerrainType, getTerrainName } from "./terrain";
import i18next from "i18next"; import i18next from "i18next";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Arena } from "#app/field/arena"; import type { Arena } from "#app/field/arena";
import { timedEventManager } from "#app/global-event-manager";
export class Weather { export class Weather {
public weatherType: WeatherType; public weatherType: WeatherType;
@ -405,8 +406,8 @@ export function getRandomWeatherType(arena: Arena): WeatherType {
break; break;
} }
if (arena.biomeType === Biome.TOWN && globalScene.eventManager.isEventActive()) { if (arena.biomeType === Biome.TOWN && timedEventManager.isEventActive()) {
globalScene.eventManager.getWeather()?.map(w => weatherPool.push(w)); timedEventManager.getWeather()?.map(w => weatherPool.push(w));
} }
if (weatherPool.length > 1) { if (weatherPool.length > 1) {

View File

@ -263,6 +263,7 @@ import { Nature } from "#enums/nature";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { doShinySparkleAnim } from "#app/field/anims"; import { doShinySparkleAnim } from "#app/field/anims";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { timedEventManager } from "#app/global-event-manager";
export enum LearnMoveSituation { export enum LearnMoveSituation {
MISC, MISC,
@ -2983,8 +2984,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE); const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE);
if (thresholdOverride === undefined) { if (thresholdOverride === undefined) {
if (globalScene.eventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); 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()) { if (!this.hasTrainer()) {
globalScene.applyModifiers( globalScene.applyModifiers(
@ -3025,8 +3030,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (thresholdOverride !== undefined && applyModifiersToOverride) { if (thresholdOverride !== undefined && applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride; shinyThreshold.value = thresholdOverride;
} }
if (globalScene.eventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); shinyThreshold.value *= timedEventManager.getShinyMultiplier();
} }
if (!this.hasTrainer()) { if (!this.hasTrainer()) {
globalScene.applyModifiers( globalScene.applyModifiers(
@ -6469,10 +6474,10 @@ export class PlayerPokemon extends Pokemon {
amount, amount,
); );
const candyFriendshipMultiplier = globalScene.gameMode.isClassic const candyFriendshipMultiplier = globalScene.gameMode.isClassic
? globalScene.eventManager.getClassicFriendshipMultiplier() ? timedEventManager.getClassicFriendshipMultiplier()
: 1; : 1;
const fusionReduction = fusionStarterSpeciesId const fusionReduction = fusionStarterSpeciesId
? globalScene.eventManager.areFusionsBoosted() ? timedEventManager.areFusionsBoosted()
? 1.5 // Divide candy gain for fusions by 1.5 during events ? 1.5 // Divide candy gain for fusions by 1.5 during events
: 2 // 2 for fusions outside events : 2 // 2 for fusions outside events
: 1; // 1 for non-fused mons : 1; // 1 for non-fused mons

View File

@ -68,6 +68,19 @@ export class GameMode implements GameModeConfig {
this.battleConfig = battleConfig || {}; 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 * Helper function to see if a GameMode has a specific challenge type
* @param challenge the Challenges it looks for * @param challenge the Challenges it looks for

View File

@ -0,0 +1,3 @@
import { TimedEventManager } from "./timed-event-manager";
export const timedEventManager = new TimedEventManager();

View File

@ -20,6 +20,7 @@ import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
import { initVouchers } from "#app/system/voucher"; import { initVouchers } from "#app/system/voucher";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
import { timedEventManager } from "./global-event-manager";
export class LoadingScene extends SceneBase { export class LoadingScene extends SceneBase {
public static readonly KEY = "loading"; public static readonly KEY = "loading";
@ -250,11 +251,13 @@ export class LoadingScene extends SceneBase {
this.loadAtlas("statuses", ""); this.loadAtlas("statuses", "");
this.loadAtlas("types", ""); this.loadAtlas("types", "");
} }
const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-MX", "pt-BR", "zh-CN", "zh-TW", "ca-ES"]; if (timedEventManager.activeEventHasBanner()) {
const availableLangs = timedEventManager.getEventBannerLangs();
if (lang && availableLangs.includes(lang)) { if (lang && availableLangs.includes(lang)) {
this.loadImage(`pkmnday2025event-${lang}`, "events"); this.loadImage(`${timedEventManager.getEventBannerFilename()}-${lang}`, "events");
} else { } else {
this.loadImage("pkmnday2025event-en", "events"); this.loadImage(`${timedEventManager.getEventBannerFilename()}-en`, "events");
}
} }
this.loadAtlas("statuses", ""); this.loadAtlas("statuses", "");

View File

@ -127,6 +127,7 @@ import type { PermanentStat, TempBattleStat } from "#enums/stat";
import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
import { timedEventManager } from "#app/global-event-manager";
const outputModifierData = false; const outputModifierData = false;
const useMaxWeightForOutput = false; const useMaxWeightForOutput = false;
@ -2655,7 +2656,7 @@ const modifierPool: ModifierPool = {
if (globalScene.gameMode.isSplicedOnly) { if (globalScene.gameMode.isSplicedOnly) {
return 4; return 4;
} }
if (globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) { if (globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) {
return 2; return 2;
} }
} }
@ -2939,7 +2940,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType( new WeightedModifierType(
modifierTypes.DNA_SPLICERS, modifierTypes.DNA_SPLICERS,
(party: Pokemon[]) => (party: Pokemon[]) =>
!(globalScene.gameMode.isClassic && globalScene.eventManager.areFusionsBoosted()) && !(globalScene.gameMode.isClassic && timedEventManager.areFusionsBoosted()) &&
!globalScene.gameMode.isSplicedOnly && !globalScene.gameMode.isSplicedOnly &&
party.filter(p => !p.fusionSpecies).length > 1 party.filter(p => !p.fusionSpecies).length > 1
? 24 ? 24
@ -3703,7 +3704,7 @@ export function getPartyLuckValue(party: Pokemon[]): number {
); );
return DailyLuck.value; return DailyLuck.value;
} }
const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies(); const eventSpecies = timedEventManager.getEventLuckBoostedSpecies();
const luck = Phaser.Math.Clamp( const luck = Phaser.Math.Clamp(
party party
.map(p => (p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0)) .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, 0,
14, 14,
); );
return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14); return Math.min(timedEventManager.getEventLuckBoost() + (luck ?? 0), 14);
} }
export function getLuckString(luckValue: number): string { export function getLuckString(luckValue: number): string {

View File

@ -212,6 +212,8 @@ export class TitlePhase extends Phase {
const generateDaily = (seed: string) => { const generateDaily = (seed: string) => {
globalScene.gameMode = getGameMode(GameModes.DAILY); 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.setSeed(seed);
globalScene.resetSeed(0); globalScene.resetSeed(0);

View File

@ -11,6 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { Biome } from "#app/enums/biome"; import { Biome } from "#app/enums/biome";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import { timedEventManager } from "#app/global-event-manager";
export class TrainerVictoryPhase extends BattlePhase { export class TrainerVictoryPhase extends BattlePhase {
constructor() { constructor() {
@ -29,7 +30,7 @@ export class TrainerVictoryPhase extends BattlePhase {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc));
} }
if (globalScene.eventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) { for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) {
globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc)); globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc));
} }
@ -42,7 +43,7 @@ export class TrainerVictoryPhase extends BattlePhase {
!globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) && !globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) &&
globalScene.currentBattle.trainer?.config.isBoss globalScene.currentBattle.trainer?.config.isBoss
) { ) {
if (globalScene.eventManager.getUpgradeUnlockedVouchers()) { if (timedEventManager.getUpgradeUnlockedVouchers()) {
globalScene.unshiftPhase( globalScene.unshiftPhase(
new ModifierRewardPhase( new ModifierRewardPhase(
[ [

View File

@ -9,6 +9,7 @@ import { WeatherType } from "#enums/weather-type";
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "./data/balance/starters"; import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "./data/balance/starters";
import { MysteryEncounterType } from "./enums/mystery-encounter-type"; import { MysteryEncounterType } from "./enums/mystery-encounter-type";
import { MysteryEncounterTier } from "./enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "./enums/mystery-encounter-tier";
import { Challenges } from "#enums/challenges";
export enum EventType { export enum EventType {
SHINY, SHINY,
@ -36,6 +37,18 @@ interface EventMysteryEncounterTier {
disable?: boolean; disable?: boolean;
} }
interface EventWaveReward {
wave: number;
type: string;
}
type EventMusicReplacement = [string, string];
interface EventChallenge {
challenge: Challenges;
value: number;
}
interface TimedEvent extends EventBanner { interface TimedEvent extends EventBanner {
name: string; name: string;
eventType: EventType; eventType: EventType;
@ -51,6 +64,10 @@ interface TimedEvent extends EventBanner {
mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; mysteryEncounterTierChanges?: EventMysteryEncounterTier[];
luckBoostedSpecies?: Species[]; luckBoostedSpecies?: Species[];
boostFusions?: boolean; //MODIFIER REWORK PLEASE 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[] = [ const timedEvents: TimedEvent[] = [
@ -61,7 +78,7 @@ const timedEvents: TimedEvent[] = [
upgradeUnlockedVouchers: true, upgradeUnlockedVouchers: true,
startDate: new Date(Date.UTC(2024, 11, 21, 0)), startDate: new Date(Date.UTC(2024, 11, 21, 0)),
endDate: new Date(Date.UTC(2025, 0, 4, 0)), endDate: new Date(Date.UTC(2025, 0, 4, 0)),
bannerKey: "winter_holidays2024-event-", bannerKey: "winter_holidays2024-event",
scale: 0.21, scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"],
eventEncounters: [ eventEncounters: [
@ -104,6 +121,12 @@ const timedEvents: TimedEvent[] = [
disable: true, 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", name: "Year of the Snake",
@ -111,7 +134,7 @@ const timedEvents: TimedEvent[] = [
luckBoost: 1, luckBoost: 1,
startDate: new Date(Date.UTC(2025, 0, 29, 0)), startDate: new Date(Date.UTC(2025, 0, 29, 0)),
endDate: new Date(Date.UTC(2025, 1, 3, 0)), endDate: new Date(Date.UTC(2025, 1, 3, 0)),
bannerKey: "yearofthesnakeevent-", bannerKey: "yearofthesnakeevent",
scale: 0.21, scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"],
eventEncounters: [ eventEncounters: [
@ -169,6 +192,12 @@ const timedEvents: TimedEvent[] = [
Species.ROARING_MOON, Species.ROARING_MOON,
Species.BLOODMOON_URSALUNA, 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", name: "Valentine",
@ -177,7 +206,7 @@ const timedEvents: TimedEvent[] = [
endDate: new Date(Date.UTC(2025, 1, 21)), endDate: new Date(Date.UTC(2025, 1, 21)),
boostFusions: true, boostFusions: true,
shinyMultiplier: 2, shinyMultiplier: 2,
bannerKey: "valentines2025event-", bannerKey: "valentines2025event",
scale: 0.21, scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"],
eventEncounters: [ eventEncounters: [
@ -203,6 +232,12 @@ const timedEvents: TimedEvent[] = [
{ species: Species.ENAMORUS }, { species: Species.ENAMORUS },
], ],
luckBoostedSpecies: [Species.LUVDISC], 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", name: "PKMNDAY2025",
@ -210,7 +245,7 @@ const timedEvents: TimedEvent[] = [
startDate: new Date(Date.UTC(2025, 1, 27)), startDate: new Date(Date.UTC(2025, 1, 27)),
endDate: new Date(Date.UTC(2025, 2, 4)), endDate: new Date(Date.UTC(2025, 2, 4)),
classicFriendshipMultiplier: 4, classicFriendshipMultiplier: 4,
bannerKey: "pkmnday2025event-", bannerKey: "pkmnday2025event",
scale: 0.21, scale: 0.21,
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"], availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN"],
eventEncounters: [ eventEncounters: [
@ -248,6 +283,32 @@ const timedEvents: TimedEvent[] = [
Species.ZYGARDE, Species.ZYGARDE,
Species.ETERNAL_FLOETTE, 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 { 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; return activeEvents.length > 0;
} }
@ -283,6 +344,12 @@ export class TimedEventManager {
return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerKey ?? ""; 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[] { getEventEncounters(): EventEncounter[] {
const ret: EventEncounter[] = []; const ret: EventEncounter[] = [];
timedEvents timedEvents
@ -417,6 +484,55 @@ export class TimedEventManager {
areFusionsBoosted(): boolean { areFusionsBoosted(): boolean {
return timedEvents.some(te => this.isActive(te) && te.boostFusions); 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 { export class TimedEventDisplay extends Phaser.GameObjects.Container {
@ -456,11 +572,12 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
let key = this.event.bannerKey; let key = this.event.bannerKey;
if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) { if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) {
if (this.event.availableLangs.includes(lang)) { if (this.event.availableLangs.includes(lang)) {
key += lang; key += "-" + lang;
} else { } else {
key += "en"; key += "-en";
} }
} }
console.log(key);
console.log(this.event.bannerKey); console.log(this.event.bannerKey);
const padding = 5; const padding = 5;
const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY; const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY;

View File

@ -11,6 +11,7 @@ import { globalScene } from "#app/global-scene";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { timedEventManager } from "#app/global-event-manager";
export default class TitleUiHandler extends OptionSelectUiHandler { export default class TitleUiHandler extends OptionSelectUiHandler {
/** If the stats can not be retrieved, use this fallback value */ /** 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); logo.setOrigin(0.5, 0);
this.titleContainer.add(logo); this.titleContainer.add(logo);
if (globalScene.eventManager.isEventActive()) { if (timedEventManager.isEventActive()) {
this.eventDisplay = new TimedEventDisplay(0, 0, globalScene.eventManager.activeEvent()); this.eventDisplay = new TimedEventDisplay(0, 0, timedEventManager.activeEvent());
this.eventDisplay.setup(); this.eventDisplay.setup();
this.titleContainer.add(this.eventDisplay); this.titleContainer.add(this.eventDisplay);
} }
@ -142,7 +143,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler {
const ui = this.getUi(); 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.setWidth(globalScene.scaledCanvas.width - this.optionSelectBg.width - this.optionSelectBg.x);
this.eventDisplay.show(); this.eventDisplay.show();
} }