diff --git a/lefthook.yml b/lefthook.yml index ff0ac00f9e5..0f91f658171 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -9,6 +9,11 @@ pre-commit: - rebase post-merge: + commands: + update-submodules: + run: git submodule update --init --recursive + +post-checkout: commands: update-submodules: run: git submodule update --init --recursive \ No newline at end of file diff --git a/public/images/egg/gacha_legendary.png b/public/images/egg/gacha_legendary.png index 8cd6fa38e29..6eb41e55099 100644 Binary files a/public/images/egg/gacha_legendary.png and b/public/images/egg/gacha_legendary.png differ diff --git a/src/@types/phase-types.ts b/src/@types/phase-types.ts new file mode 100644 index 00000000000..596d9b15723 --- /dev/null +++ b/src/@types/phase-types.ts @@ -0,0 +1,286 @@ +import type { MoveAnim } from "#app/data/battle-anims"; +import type { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase"; +import type { AttemptCapturePhase } from "#app/phases/attempt-capture-phase"; +import type { AttemptRunPhase } from "#app/phases/attempt-run-phase"; +import type { BattleEndPhase } from "#app/phases/battle-end-phase"; +import type { BerryPhase } from "#app/phases/berry-phase"; +import type { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; +import type { CheckSwitchPhase } from "#app/phases/check-switch-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; +import type { CommonAnimPhase } from "#app/phases/common-anim-phase"; +import type { DamageAnimPhase } from "#app/phases/damage-anim-phase"; +import type { EggHatchPhase } from "#app/phases/egg-hatch-phase"; +import type { EggLapsePhase } from "#app/phases/egg-lapse-phase"; +import type { EggSummaryPhase } from "#app/phases/egg-summary-phase"; +import type { EncounterPhase } from "#app/phases/encounter-phase"; +import type { EndCardPhase } from "#app/phases/end-card-phase"; +import type { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; +import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; +import type { EvolutionPhase } from "#app/phases/evolution-phase"; +import type { ExpPhase } from "#app/phases/exp-phase"; +import type { FaintPhase } from "#app/phases/faint-phase"; +import type { FormChangePhase } from "#app/phases/form-change-phase"; +import type { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; +import type { GameOverPhase } from "#app/phases/game-over-phase"; +import type { HideAbilityPhase } from "#app/phases/hide-ability-phase"; +import type { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase"; +import type { LearnMovePhase } from "#app/phases/learn-move-phase"; +import type { LevelCapPhase } from "#app/phases/level-cap-phase"; +import type { LevelUpPhase } from "#app/phases/level-up-phase"; +import type { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase"; +import type { LoginPhase } from "#app/phases/login-phase"; +import type { MessagePhase } from "#app/phases/message-phase"; +import type { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; +import type { MoneyRewardPhase } from "#app/phases/money-reward-phase"; +import type { MoveAnimPhase } from "#app/phases/move-anim-phase"; +import type { MoveChargePhase } from "#app/phases/move-charge-phase"; +import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import type { MoveEndPhase } from "#app/phases/move-end-phase"; +import type { MoveHeaderPhase } from "#app/phases/move-header-phase"; +import type { MovePhase } from "#app/phases/move-phase"; +import type { + MysteryEncounterPhase, + MysteryEncounterOptionSelectedPhase, + MysteryEncounterBattlePhase, + MysteryEncounterRewardsPhase, + PostMysteryEncounterPhase, + MysteryEncounterBattleStartCleanupPhase, +} from "#app/phases/mystery-encounter-phases"; +import type { NewBattlePhase } from "#app/phases/new-battle-phase"; +import type { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase"; +import type { NextEncounterPhase } from "#app/phases/next-encounter-phase"; +import type { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; +import type { PartyExpPhase } from "#app/phases/party-exp-phase"; +import type { PartyHealPhase } from "#app/phases/party-heal-phase"; +import type { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; +import type { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; +import type { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase"; +import type { PostGameOverPhase } from "#app/phases/post-game-over-phase"; +import type { PostSummonPhase } from "#app/phases/post-summon-phase"; +import type { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; +import type { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; +import type { ReloadSessionPhase } from "#app/phases/reload-session-phase"; +import type { ResetStatusPhase } from "#app/phases/reset-status-phase"; +import type { ReturnPhase } from "#app/phases/return-phase"; +import type { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; +import type { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase"; +import type { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; +import type { SelectBiomePhase } from "#app/phases/select-biome-phase"; +import type { SelectChallengePhase } from "#app/phases/select-challenge-phase"; +import type { SelectGenderPhase } from "#app/phases/select-gender-phase"; +import type { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import type { SelectStarterPhase } from "#app/phases/select-starter-phase"; +import type { SelectTargetPhase } from "#app/phases/select-target-phase"; +import type { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; +import type { ShowAbilityPhase } from "#app/phases/show-ability-phase"; +import type { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; +import type { ShowTrainerPhase } from "#app/phases/show-trainer-phase"; +import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import type { SummonMissingPhase } from "#app/phases/summon-missing-phase"; +import type { SummonPhase } from "#app/phases/summon-phase"; +import type { SwitchBiomePhase } from "#app/phases/switch-biome-phase"; +import type { SwitchPhase } from "#app/phases/switch-phase"; +import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; +import type { TeraPhase } from "#app/phases/tera-phase"; +import type { TitlePhase } from "#app/phases/title-phase"; +import type { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; +import type { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase"; +import type { TurnEndPhase } from "#app/phases/turn-end-phase"; +import type { TurnInitPhase } from "#app/phases/turn-init-phase"; +import type { TurnStartPhase } from "#app/phases/turn-start-phase"; +import type { UnavailablePhase } from "#app/phases/unavailable-phase"; +import type { UnlockPhase } from "#app/phases/unlock-phase"; +import type { VictoryPhase } from "#app/phases/victory-phase"; +import type { WeatherEffectPhase } from "#app/phases/weather-effect-phase"; + +export type PhaseClass = + | typeof AddEnemyBuffModifierPhase + | typeof AttemptCapturePhase + | typeof AttemptRunPhase + | typeof BattleEndPhase + | typeof BerryPhase + | typeof CheckStatusEffectPhase + | typeof CheckSwitchPhase + | typeof CommandPhase + | typeof CommonAnimPhase + | typeof DamageAnimPhase + | typeof EggHatchPhase + | typeof EggLapsePhase + | typeof EggSummaryPhase + | typeof EncounterPhase + | typeof EndCardPhase + | typeof EndEvolutionPhase + | typeof EnemyCommandPhase + | typeof EvolutionPhase + | typeof FormChangePhase + | typeof ExpPhase + | typeof FaintPhase + | typeof FormChangePhase + | typeof GameOverPhase + | typeof GameOverModifierRewardPhase + | typeof HideAbilityPhase + | typeof HidePartyExpBarPhase + | typeof LearnMovePhase + | typeof LevelUpPhase + | typeof LevelCapPhase + | typeof LoadMoveAnimPhase + | typeof LoginPhase + | typeof MessagePhase + | typeof ModifierRewardPhase + | typeof MoneyRewardPhase + | typeof MoveAnimPhase + | typeof MoveChargePhase + | typeof MoveEffectPhase + | typeof MoveEndPhase + | typeof MoveHeaderPhase + | typeof MovePhase + | typeof MysteryEncounterPhase + | typeof MysteryEncounterOptionSelectedPhase + | typeof MysteryEncounterBattlePhase + | typeof MysteryEncounterRewardsPhase + | typeof MysteryEncounterBattleStartCleanupPhase + | typeof MysteryEncounterRewardsPhase + | typeof PostMysteryEncounterPhase + | typeof NewBattlePhase + | typeof NewBiomeEncounterPhase + | typeof NextEncounterPhase + | typeof ObtainStatusEffectPhase + | typeof PartyExpPhase + | typeof PartyHealPhase + | typeof PokemonAnimPhase + | typeof PokemonHealPhase + | typeof PokemonTransformPhase + | typeof PostGameOverPhase + | typeof PostSummonPhase + | typeof PostTurnStatusEffectPhase + | typeof QuietFormChangePhase + | typeof ReloadSessionPhase + | typeof ResetStatusPhase + | typeof ReturnPhase + | typeof RevivalBlessingPhase + | typeof RibbonModifierRewardPhase + | typeof ScanIvsPhase + | typeof SelectBiomePhase + | typeof SelectChallengePhase + | typeof SelectGenderPhase + | typeof SelectModifierPhase + | typeof SelectStarterPhase + | typeof SelectTargetPhase + | typeof ShinySparklePhase + | typeof ShowAbilityPhase + | typeof ShowTrainerPhase + | typeof ShowPartyExpBarPhase + | typeof StatStageChangePhase + | typeof SummonMissingPhase + | typeof SummonPhase + | typeof SwitchBiomePhase + | typeof SwitchPhase + | typeof SwitchSummonPhase + | typeof TeraPhase + | typeof TitlePhase + | typeof ToggleDoublePositionPhase + | typeof TrainerVictoryPhase + | typeof TurnEndPhase + | typeof TurnInitPhase + | typeof TurnStartPhase + | typeof UnavailablePhase + | typeof UnlockPhase + | typeof VictoryPhase + | typeof WeatherEffectPhase; + +/** Typescript map used to map a string phase to the actual phase type */ +export type PhaseMap = { + AddEnemyBuffModifierPhase: AddEnemyBuffModifierPhase; + AttemptCapturePhase: AttemptCapturePhase; + AttemptRunPhase: AttemptRunPhase; + BattleEndPhase: BattleEndPhase; + BerryPhase: BerryPhase; + CheckStatusEffectPhase: CheckStatusEffectPhase; + CheckSwitchPhase: CheckSwitchPhase; + CommandPhase: CommandPhase; + CommonAnimPhase: CommonAnimPhase; + DamageAnimPhase: DamageAnimPhase; + EggHatchPhase: EggHatchPhase; + EggLapsePhase: EggLapsePhase; + EggSummaryPhase: EggSummaryPhase; + EncounterPhase: EncounterPhase; + EndCardPhase: EndCardPhase; + EndEvolutionPhase: EndEvolutionPhase; + EnemyCommandPhase: EnemyCommandPhase; + EvolutionPhase: EvolutionPhase; + ExpPhase: ExpPhase; + FaintPhase: FaintPhase; + FormChangePhase: FormChangePhase; + GameOverPhase: GameOverPhase; + GameOverModifierRewardPhase: GameOverModifierRewardPhase; + HideAbilityPhase: HideAbilityPhase; + HidePartyExpBarPhase: HidePartyExpBarPhase; + LearnMovePhase: LearnMovePhase; + LevelCapPhase: LevelCapPhase; + LevelUpPhase: LevelUpPhase; + LoadMoveAnimPhase: LoadMoveAnimPhase; + LoginPhase: LoginPhase; + MessagePhase: MessagePhase; + ModifierRewardPhase: ModifierRewardPhase; + MoneyRewardPhase: MoneyRewardPhase; + MoveAnimPhase: MoveAnimPhase; + MoveChargePhase: MoveChargePhase; + MoveEffectPhase: MoveEffectPhase; + MoveEndPhase: MoveEndPhase; + MoveHeaderPhase: MoveHeaderPhase; + MovePhase: MovePhase; + MysteryEncounterPhase: MysteryEncounterPhase; + MysteryEncounterOptionSelectedPhase: MysteryEncounterOptionSelectedPhase; + MysteryEncounterBattlePhase: MysteryEncounterBattlePhase; + MysteryEncounterBattleStartCleanupPhase: MysteryEncounterBattleStartCleanupPhase; + MysteryEncounterRewardsPhase: MysteryEncounterRewardsPhase; + PostMysteryEncounterPhase: PostMysteryEncounterPhase; + NewBattlePhase: NewBattlePhase; + NewBiomeEncounterPhase: NewBiomeEncounterPhase; + NextEncounterPhase: NextEncounterPhase; + ObtainStatusEffectPhase: ObtainStatusEffectPhase; + PartyExpPhase: PartyExpPhase; + PartyHealPhase: PartyHealPhase; + PokemonAnimPhase: PokemonAnimPhase; + PokemonHealPhase: PokemonHealPhase; + PokemonTransformPhase: PokemonTransformPhase; + PostGameOverPhase: PostGameOverPhase; + PostSummonPhase: PostSummonPhase; + PostTurnStatusEffectPhase: PostTurnStatusEffectPhase; + QuietFormChangePhase: QuietFormChangePhase; + ReloadSessionPhase: ReloadSessionPhase; + ResetStatusPhase: ResetStatusPhase; + ReturnPhase: ReturnPhase; + RevivalBlessingPhase: RevivalBlessingPhase; + RibbonModifierRewardPhase: RibbonModifierRewardPhase; + ScanIvsPhase: ScanIvsPhase; + SelectBiomePhase: SelectBiomePhase; + SelectChallengePhase: SelectChallengePhase; + SelectGenderPhase: SelectGenderPhase; + SelectModifierPhase: SelectModifierPhase; + SelectStarterPhase: SelectStarterPhase; + SelectTargetPhase: SelectTargetPhase; + ShinySparklePhase: ShinySparklePhase; + ShowAbilityPhase: ShowAbilityPhase; + ShowPartyExpBarPhase: ShowPartyExpBarPhase; + ShowTrainerPhase: ShowTrainerPhase; + StatStageChangePhase: StatStageChangePhase; + SummonMissingPhase: SummonMissingPhase; + SummonPhase: SummonPhase; + SwitchBiomePhase: SwitchBiomePhase; + SwitchPhase: SwitchPhase; + SwitchSummonPhase: SwitchSummonPhase; + TeraPhase: TeraPhase; + TitlePhase: TitlePhase; + ToggleDoublePositionPhase: ToggleDoublePositionPhase; + TrainerVictoryPhase: TrainerVictoryPhase; + TurnEndPhase: TurnEndPhase; + TurnInitPhase: TurnInitPhase; + TurnStartPhase: TurnStartPhase; + UnavailablePhase: UnavailablePhase; + UnlockPhase: UnlockPhase; + VictoryPhase: VictoryPhase; + WeatherEffectPhase: WeatherEffectPhase; +}; + +export type PhaseString = keyof PhaseMap; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 34d26b3975c..9a46baba899 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -145,7 +145,7 @@ import { LoadingScene } from "#app/loading-scene"; import { LevelCapPhase } from "#app/phases/level-cap-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { MessagePhase } from "#app/phases/message-phase"; -import { MovePhase } from "#app/phases/move-phase"; +import type { MovePhase } from "#app/phases/move-phase"; import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase"; import { NextEncounterPhase } from "#app/phases/next-encounter-phase"; import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase"; @@ -153,7 +153,6 @@ import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { ReturnPhase } from "#app/phases/return-phase"; import { ShowTrainerPhase } from "#app/phases/show-trainer-phase"; import { SummonPhase } from "#app/phases/summon-phase"; -import { SwitchPhase } from "#app/phases/switch-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; @@ -901,7 +900,7 @@ export default class BattleScene extends SceneBase { do { targetingMovePhase = this.findPhase( mp => - mp instanceof MovePhase && + mp.is("MovePhase") && mp.targets.length === 1 && mp.targets[0] === removedPokemon.getBattlerIndex() && mp.pokemon.isPlayer() !== allyPokemon.isPlayer(), @@ -1450,7 +1449,7 @@ export default class BattleScene extends SceneBase { } if (lastBattle?.double && !newDouble) { - this.tryRemovePhase(p => p instanceof SwitchPhase); + this.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); for (const p of this.getPlayerField()) { p.lapseTag(BattlerTagType.COMMANDED); } @@ -1588,9 +1587,7 @@ export default class BattleScene extends SceneBase { return 0; } - const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes( - this.getCurrentPhase()?.constructor.name ?? "", - ); + const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes(this.getCurrentPhase()?.phaseName ?? ""); if ( // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros. @@ -3569,21 +3566,18 @@ export default class BattleScene extends SceneBase { gameMode: this.currentBattle ? this.gameMode.getName() : "Title", biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "", wave: this.currentBattle?.waveIndex ?? 0, - party: this.party - ? this.party.map(p => { - return { - name: p.name, - form: p.getFormKey(), - types: p.getTypes().map(type => PokemonType[type]), - teraType: PokemonType[p.getTeraType()], - isTerastallized: p.isTerastallized, - level: p.level, - currentHP: p.hp, - maxHP: p.getMaxHp(), - status: p.status?.effect ? StatusEffect[p.status.effect] : "", - }; - }) - : [], + party: + this.party?.map(p => ({ + name: p.name, + form: p.getFormKey(), + types: p.getTypes().map(type => PokemonType[type]), + teraType: PokemonType[p.getTeraType()], + isTerastallized: p.isTerastallized, + level: p.level, + currentHP: p.hp, + maxHP: p.getMaxHp(), + status: p.status?.effect ? StatusEffect[p.status.effect] : "", + })) ?? [], // TODO: review if this can be nullish modeChain: this.ui?.getModeChain() ?? [], }; (window as any).gameInfo = gameInfo; @@ -3966,16 +3960,13 @@ export default class BattleScene extends SceneBase { if (previousEncounter !== null && encounterType === previousEncounter) { return false; } - if ( + return !( this.mysteryEncounterSaveData.encounteredEvents.length > 0 && encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0 && this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >= encounterCandidate.maxAllowedEncounters - ) { - return false; - } - return true; + ); }) .map(m => allMysteryEncounters[m]); // Decrement tier diff --git a/src/configs/inputs/configHandler.ts b/src/configs/inputs/configHandler.ts index b896f303cb3..227c2b964b9 100644 --- a/src/configs/inputs/configHandler.ts +++ b/src/configs/inputs/configHandler.ts @@ -197,10 +197,7 @@ export function canIAssignThisKey(config, key) { export function canIOverrideThisSetting(config, settingName) { const key = getKeyWithSettingName(config, settingName); // || isTheLatestBind(config, settingName) no longer needed since action and cancel are protected - if (config.blacklist?.includes(key)) { - return false; - } - return true; + return !config.blacklist?.includes(key); } export function canIDeleteThisKey(config, key) { diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index e8662d0e822..82ba9495b3b 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2130,6 +2130,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } + cancelled.value = this.overwrites; } } @@ -2649,11 +2650,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } const ally = pokemon.getAlly(); - if (isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)) { - return false; - } - - return true; + return !(isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)); } override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { @@ -2719,11 +2716,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { } // transforming from or into fusion pokemon causes various problems (including crashes and save corruption) - if (this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies) { - return false; - } - - return true; + return !(this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies); } override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { @@ -2824,7 +2817,7 @@ export class CommanderAbAttr extends AbAttr { // Apply boosts from this effect to the ally Dondozo pokemon.getAlly()?.addTag(BattlerTagType.COMMANDED, 0, MoveId.NONE, pokemon.id); // Cancel the source Pokemon's next move (if a move is queued) - globalScene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); + globalScene.tryRemovePhase((phase) => phase.is("MovePhase") && phase.pokemon === pokemon); } } } @@ -3540,10 +3533,7 @@ export class BlockStatusDamageAbAttr extends AbAttr { } override canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (pokemon.status && this.effects.includes(pokemon.status.effect)) { - return true; - } - return false; + return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); } /** @@ -4799,11 +4789,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { const diedToDirectDamage = move !== undefined && attacker !== undefined && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}); const cancelled = new BooleanHolder(false); globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); - if (!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { - return false; - } - - return true; + return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)); } override applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): void { @@ -6504,12 +6490,7 @@ export function initAbilities() { new Ability(AbilityId.INTIMIDATE, 3) .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], -1, false, true), new Ability(AbilityId.SHADOW_TAG, 3) - .attr(ArenaTrapAbAttr, (user, target) => { - if (target.hasAbility(AbilityId.SHADOW_TAG)) { - return false; - } - return true; - }), + .attr(ArenaTrapAbAttr, (_user, target) => !target.hasAbility(AbilityId.SHADOW_TAG)), new Ability(AbilityId.ROUGH_SKIN, 3) .attr(PostDefendContactDamageAbAttr, 8) .bypassFaint(), @@ -6569,10 +6550,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.MAGNET_PULL, 3) .attr(ArenaTrapAbAttr, (user, target) => { - if (target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL))) { - return true; - } - return false; + return target.getTypes(true).includes(PokemonType.STEEL) || (target.getTypes(true).includes(PokemonType.STELLAR) && target.getTypes().includes(PokemonType.STEEL)); }), new Ability(AbilityId.SOUNDPROOF, 3) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.SOUND_BASED)) @@ -6650,12 +6628,7 @@ export function initAbilities() { .attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY) .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY), new Ability(AbilityId.ARENA_TRAP, 3) - .attr(ArenaTrapAbAttr, (user, target) => { - if (target.isGrounded()) { - return true; - } - return false; - }) + .attr(ArenaTrapAbAttr, (user, target) => target.isGrounded()) .attr(DoubleBattleChanceAbAttr), new Ability(AbilityId.VITAL_SPIRIT, 3) .attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP) @@ -6893,7 +6866,7 @@ export function initAbilities() { .ignorable(), new Ability(AbilityId.ANALYTIC, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => { - const movePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user?.id); + const movePhase = globalScene.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.id !== user?.id); return isNullOrUndefined(movePhase); }, 1.3), new Ability(AbilityId.ILLUSION, 5) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 590319a01c0..0254aab37e1 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -383,7 +383,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => { const move = allMoves[moveId]; const effectPhase = globalScene.getCurrentPhase(); - if (effectPhase instanceof MoveEffectPhase) { + if (effectPhase?.is("MoveEffectPhase")) { const attacker = effectPhase.getUserPokemon(); if (attacker) { return move.getPriority(attacker) > 0; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 4f263fc152b..4d99fd18ac0 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -28,7 +28,7 @@ import type Pokemon from "#app/field/pokemon"; import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; @@ -553,9 +553,9 @@ export class ShellTrapTag extends BattlerTag { // Trap should only be triggered by opponent's Physical moves if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) { const shellTrapPhaseIndex = globalScene.phaseQueue.findIndex( - phase => phase instanceof MovePhase && phase.pokemon === pokemon, + phase => phase.is("MovePhase") && phase.pokemon === pokemon, ); - const firstMovePhaseIndex = globalScene.phaseQueue.findIndex(phase => phase instanceof MovePhase); + const firstMovePhaseIndex = globalScene.phaseQueue.findIndex(phase => phase.is("MovePhase")); // Only shift MovePhase timing if it's not already next up if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { @@ -1027,7 +1027,7 @@ export class PowderTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { const movePhase = globalScene.getCurrentPhase(); - if (movePhase instanceof MovePhase) { + if (movePhase?.is("MovePhase")) { const move = movePhase.move.getMove(); const weather = globalScene.arena.weather; if ( @@ -1183,13 +1183,13 @@ export class EncoreTag extends MoveRestrictionBattlerTag { }), ); - const movePhase = globalScene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon); + const movePhase = globalScene.findPhase(m => m.is("MovePhase") && m.pokemon === pokemon); if (movePhase) { const movesetMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); if (movesetMove) { const lastMove = pokemon.getLastXMoves(1)[0]; globalScene.tryReplacePhase( - m => m instanceof MovePhase && m.pokemon === pokemon, + m => m.is("MovePhase") && m.pokemon === pokemon, new MovePhase(pokemon, lastMove.targets ?? [], movesetMove), ); } @@ -1203,10 +1203,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { const encoredMove = pokemon.getMoveset().find(m => m.moveId === this.moveId); - if (encoredMove && encoredMove?.getPpRatio() > 0) { - return true; - } - return false; + return !isNullOrUndefined(encoredMove) && encoredMove.getPpRatio() > 0; } return super.lapse(pokemon, lapseType); } @@ -1218,10 +1215,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { * @returns `true` if the move does not match with the moveId stored and as a result, restricted */ override isMoveRestricted(move: MoveId, _user?: Pokemon): boolean { - if (move !== this.moveId) { - return true; - } - return false; + return move !== this.moveId; } override selectionDeniedText(_pokemon: Pokemon, move: MoveId): string { @@ -1624,7 +1618,7 @@ export class ProtectedTag extends BattlerTag { // Stop multi-hit moves early const effectPhase = globalScene.getCurrentPhase(); - if (effectPhase instanceof MoveEffectPhase) { + if (effectPhase?.is("MoveEffectPhase")) { effectPhase.stopMultiHit(pokemon); } return true; @@ -2646,7 +2640,7 @@ export class GulpMissileTag extends BattlerTag { } const moveEffectPhase = globalScene.getCurrentPhase(); - if (moveEffectPhase instanceof MoveEffectPhase) { + if (moveEffectPhase?.is("MoveEffectPhase")) { const attacker = moveEffectPhase.getUserPokemon(); if (!attacker) { @@ -2768,10 +2762,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { * @returns `true` if the move has a TRIAGE_MOVE flag and is a status move */ override isMoveRestricted(move: MoveId): boolean { - if (allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS) { - return true; - } - return false; + return allMoves[move].hasFlag(MoveFlags.TRIAGE_MOVE) && allMoves[move].category === MoveCategory.STATUS; } /** @@ -2785,10 +2776,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) { const moveCategory = new NumberHolder(allMoves[move].category); applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); - if (allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS) { - return true; - } - return false; + return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS; } /** @@ -3004,7 +2992,7 @@ export class SubstituteTag extends BattlerTag { /** If the Substitute redirects damage, queue a message to indicate it. */ onHit(pokemon: Pokemon): void { const moveEffectPhase = globalScene.getCurrentPhase(); - if (moveEffectPhase instanceof MoveEffectPhase) { + if (moveEffectPhase?.is("MoveEffectPhase")) { const attacker = moveEffectPhase.getUserPokemon(); if (!attacker) { return; @@ -3126,10 +3114,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { const moveObj = allMoves[lastMove.move]; const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY); const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS; - if (lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected) { - return true; - } - return false; + return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected; } override selectionDeniedText(pokemon: Pokemon, _move: MoveId): string { @@ -3693,7 +3678,7 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { */ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; attacker: Pokemon; move: Move } | null { const phase = globalScene.getCurrentPhase(); - if (phase instanceof MoveEffectPhase) { + if (phase?.is("MoveEffectPhase")) { return { phase: phase, attacker: phase.getPokemon(), diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 7e9f99e28c1..e98b28e2a48 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -3109,7 +3109,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { const overridden = args[0] as BooleanHolder; - const allyMovePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.isPlayer() === user.isPlayer()); + const allyMovePhase = globalScene.findPhase((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { const allyMove = allyMovePhase.move.getMove(); if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) { @@ -3123,7 +3123,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { // Move the ally's MovePhase (if needed) so that the ally moves next const allyMovePhaseIndex = globalScene.phaseQueue.indexOf(allyMovePhase); - const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase instanceof MovePhase); + const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase.is("MovePhase")); if (allyMovePhaseIndex !== firstMovePhaseIndex) { globalScene.prependToPhase(globalScene.phaseQueue.splice(allyMovePhaseIndex, 1)[0], MovePhase); } @@ -4477,7 +4477,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { const nextRoundPhase = globalScene.findPhase(phase => - phase instanceof MovePhase && phase.move.moveId === MoveId.ROUND + phase.is("MovePhase") && phase.move.moveId === MoveId.ROUND ); if (!nextRoundPhase) { @@ -4486,7 +4486,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { // Update the phase queue so that the next Pokemon using Round moves next const nextRoundIndex = globalScene.phaseQueue.indexOf(nextRoundPhase); - const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase instanceof MovePhase); + const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase.is("MovePhase")); if (nextRoundIndex !== nextMoveIndex) { globalScene.prependToPhase(globalScene.phaseQueue.splice(nextRoundIndex, 1)[0], MovePhase); } @@ -5472,13 +5472,6 @@ export class AddBattlerTagAttr extends MoveEffectAttr { this.failOnOverlap = !!failOnOverlap; } - canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!super.canApply(user, target, move, args)) { - return false; - } - return true; - } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) { return false; @@ -6177,7 +6170,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr { // Handle cases where revived pokemon needs to get switched in on same turn if (allyPokemon.isFainted() || allyPokemon === pokemon) { // Enemy switch phase should be removed and replaced with the revived pkmn switching in - globalScene.tryRemovePhase((phase: SwitchSummonPhase) => phase instanceof SwitchSummonPhase && phase.getPokemon() === pokemon); + globalScene.tryRemovePhase((phase: SwitchSummonPhase) => phase.is("SwitchSummonPhase") && phase.getPokemon() === pokemon); // If the pokemon being revived was alive earlier in the turn, cancel its move // (revived pokemon can't move in the turn they're brought back) globalScene.findPhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); @@ -6832,7 +6825,7 @@ export class RandomMovesetMoveAttr extends CallMoveAttr { return false; } - this.moveId = moves[user.randBattleSeedInt(moves.length)]!.moveId; + this.moveId = moves[user.randBattleSeedInt(moves.length)].moveId; return true; }; } @@ -7367,11 +7360,7 @@ export class SketchAttr extends MoveEffectAttr { return false; } - if (user.getMoveset().find(m => m.moveId === targetMove.move)) { - return false; - } - - return true; + return !user.getMoveset().some(m => m.moveId === targetMove.move); }; } } @@ -7896,7 +7885,7 @@ export class ForceLastAttr extends MoveEffectAttr { // Either the end of the turn or in front of another, slower move which has also been forced last const prependPhase = globalScene.findPhase((phase) => [ MovePhase, MoveEndPhase ].every(cls => !(phase instanceof cls)) - || (phase instanceof MovePhase) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM)) + || (phase.is("MovePhase")) && phaseForcedSlower(phase, target, !!globalScene.arena.getTag(ArenaTagType.TRICK_ROOM)) ); if (prependPhase) { globalScene.phaseQueue.splice( @@ -7942,7 +7931,7 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(AbilityId.COMATOSE); -const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined; +const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseQueue.find(phase => phase.is("MovePhase")) !== undefined; const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => { const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index ec78408ea83..88d9ba402a9 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -275,15 +275,11 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const timeOfDay = globalScene.arena?.getTimeOfDay(); - if ( + return !( !isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay) - ) { - return false; - } - - return true; + ); } override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { @@ -301,15 +297,11 @@ export class WeatherRequirement extends EncounterSceneRequirement { override meetsRequirement(): boolean { const currentWeather = globalScene.arena.weather?.weatherType; - if ( + return !( !isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!) - ) { - return false; - } - - return true; + ); } override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] { @@ -803,7 +795,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } filterByForm(pokemon, formChangeItem) { - if ( + return ( pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) && // Get all form changes for this species with an item trigger, including any compound triggers pokemonFormChanges[pokemon.species.speciesId] @@ -812,10 +804,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen .flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) .flatMap(fc => fc.item) .includes(formChangeItem) - ) { - return true; - } - return false; + ); } override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { @@ -873,17 +862,15 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { ) { return true; } - if ( + + return ( pokemon.isFusion() && pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) && pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter( e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)), ).length && pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX - ) { - return true; - } - return false; + ); } override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 060de9c3a9e..c9eaa2e6968 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -769,7 +769,7 @@ export function setEncounterRewards( if (customShopRewards) { globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); } else { - globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); + globalScene.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); } if (eggRewards) { diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 1098ddc4eeb..47eb355c8b6 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -186,11 +186,7 @@ export class SpeciesFormChange { } } - if (!this.trigger.canChange(pokemon)) { - return false; - } - - return true; + return this.trigger.canChange(pokemon); } findTrigger(triggerType: Constructor): SpeciesFormChangeTrigger | nil { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 66ed0b09eeb..df7a8c118d5 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1281,10 +1281,6 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali }; } - isObtainable() { - return super.isObtainable(); - } - hasVariants() { let variantDataIndex: string | number = this.speciesId; if (this.forms.length > 0) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index cd8563cfb30..df953f06834 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -232,7 +232,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { FaintPhase } from "#app/phases/faint-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; @@ -1293,19 +1292,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ isOffsetBySubstitute(): boolean { const substitute = this.getTag(SubstituteTag); - if (substitute) { - if (substitute.sprite === undefined) { - return false; - } - - // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" - const currentPhase = globalScene.getCurrentPhase(); - if (currentPhase instanceof MoveEffectPhase && currentPhase.getPokemon() === this) { - return false; - } - return true; + if (!substitute || substitute.sprite === undefined) { + return false; } - return false; + // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" + const currentPhase = globalScene.getCurrentPhase(); + return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this); } /** If this Pokemon has a Substitute on the field, removes its sprite from the field. */ @@ -2242,10 +2234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { - return true; - } - return false; + return this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true)); } /** @@ -2262,10 +2251,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { return true; } - if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType)) { - return true; - } - return false; + return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType); } /** @@ -4775,7 +4761,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { const currentPhase = globalScene.getCurrentPhase(); - if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) { + if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) { this.turnData.hitCount = 1; this.turnData.hitsLeft = 1; } @@ -5465,10 +5451,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if ((ownedAbilityAttrs & 2) > 0 && this.hasSameAbilityInRootForm(1)) { return true; } - if ((ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2)) { - return true; - } - return false; + return (ownedAbilityAttrs & 4) > 0 && this.hasSameAbilityInRootForm(2); } /** diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 11631b64451..f8c71b2c891 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -40,7 +40,6 @@ import { type TerastallizeModifierType, type TmModifierType, getModifierType, - ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType, @@ -1091,10 +1090,6 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { return new PokemonIncrementingStatModifier(this.type, this.pokemonId, this.stackCount); } - getArgs(): any[] { - return super.getArgs(); - } - /** * Checks if the {@linkcode PokemonIncrementingStatModifier} should be applied to the {@linkcode Pokemon}. * @param pokemon The {@linkcode Pokemon} that holds the item @@ -1217,10 +1212,6 @@ export class StatBoosterModifier extends PokemonHeldItemModifier { * @see {@linkcode apply} */ export class EvolutionStatBoosterModifier extends StatBoosterModifier { - clone() { - return super.clone() as EvolutionStatBoosterModifier; - } - matchType(modifier: Modifier): boolean { return modifier instanceof EvolutionStatBoosterModifier; } @@ -3232,8 +3223,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { } /** - * Steals an item from a set of target Pokemon. - * This prioritizes high-tier held items when selecting the item to steal. + * Steals an item, chosen randomly, from a set of target Pokemon. * @param pokemon The {@linkcode Pokemon} holding this item * @param target The {@linkcode Pokemon} to steal from (optional) * @param _args N/A @@ -3253,30 +3243,15 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { return false; } - const poolType = pokemon.isPlayer() - ? ModifierPoolType.PLAYER - : pokemon.hasTrainer() - ? ModifierPoolType.TRAINER - : ModifierPoolType.WILD; - const transferredModifierTypes: ModifierType[] = []; const itemModifiers = globalScene.findModifiers( m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, targetPokemon.isPlayer(), ) as PokemonHeldItemModifier[]; - let highestItemTier = itemModifiers - .map(m => m.type.getOrInferTier(poolType)) - .reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct? - let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); for (let i = 0; i < transferredItemCount; i++) { - if (!tierItemModifiers.length) { - while (highestItemTier-- && !tierItemModifiers.length) { - tierItemModifiers = itemModifiers.filter(m => m.type.tier === highestItemTier); - } - if (!tierItemModifiers.length) { - break; - } + if (!itemModifiers.length) { + break; } const randItemIndex = pokemon.randBattleSeedInt(itemModifiers.length); const randItem = itemModifiers[randItemIndex]; diff --git a/src/phase.ts b/src/phase.ts index 20cc7cc4063..9e2468ebdff 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -1,9 +1,33 @@ import { globalScene } from "#app/global-scene"; +import type { PhaseMap, PhaseString } from "./@types/phase-types"; -export class Phase { +export abstract class Phase { start() {} end() { globalScene.shiftPhase(); } + + /** + * The string name of the phase, used to identify the phase type for {@linkcode is} + * + * @privateremarks + * + * When implementing a phase, you must set the `phaseName` property to the name of the phase. + */ + public abstract readonly phaseName: PhaseString; + + /** + * Check if the phase is of the given type without requiring `instanceof`. + * + * @param phase - The string name of the phase to check. + * @returns Whether this phase is of the provided type. + * + * @remarks + * This does not check for subclasses! It only checks if the phase is *exactly* the given type. + * This method exists to avoid circular import issues, as using `instanceof` would require importing each phase. + */ + is(phase: K): this is PhaseMap[K] { + return this.phaseName === phase; + } } diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 16ed78e6d0d..28eaf0dc4df 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -9,6 +9,7 @@ import { Phase } from "#app/phase"; import { globalScene } from "#app/global-scene"; export class AddEnemyBuffModifierPhase extends Phase { + public readonly phaseName = "AddEnemyBuffModifierPhase"; start() { super.start(); diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 8592cd98508..6c2f5f4dd76 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -27,6 +27,7 @@ import { globalScene } from "#app/global-scene"; import { Gender } from "#app/data/gender"; export class AttemptCapturePhase extends PokemonPhase { + public readonly phaseName = "AttemptCapturePhase"; private pokeballType: PokeballType; private pokeball: Phaser.GameObjects.Sprite; private originalY: number; diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 15c521c01fc..ced81567c43 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -17,6 +17,7 @@ import { globalScene } from "#app/global-scene"; import { SelectBiomePhase } from "./select-biome-phase"; export class AttemptRunPhase extends PokemonPhase { + public readonly phaseName = "AttemptRunPhase"; /** For testing purposes: this is to force the pokemon to fail and escape */ public forceFailEscape = false; diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index b4bb28fe55e..96f6d02b1fc 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -5,6 +5,7 @@ import { BattlePhase } from "./battle-phase"; import { GameOverPhase } from "./game-over-phase"; export class BattleEndPhase extends BattlePhase { + public readonly phaseName = "BattleEndPhase"; /** If true, will increment battles won */ isVictory: boolean; @@ -19,7 +20,7 @@ export class BattleEndPhase extends BattlePhase { // cull any extra `BattleEnd` phases from the queue. globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => { - if (phase instanceof BattleEndPhase) { + if (phase.is("BattleEndPhase")) { this.isVictory ||= phase.isVictory; return false; } @@ -28,7 +29,7 @@ export class BattleEndPhase extends BattlePhase { // `phaseQueuePrepend` is private, so we have to use this inefficient loop. while ( globalScene.tryRemoveUnshiftedPhase(phase => { - if (phase instanceof BattleEndPhase) { + if (phase.is("BattleEndPhase")) { this.isVictory ||= phase.isVictory; return true; } diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts index d70b3909639..7cefd0369d9 100644 --- a/src/phases/battle-phase.ts +++ b/src/phases/battle-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { TrainerSlot } from "#enums/trainer-slot"; import { Phase } from "#app/phase"; -export class BattlePhase extends Phase { +export abstract class BattlePhase extends Phase { showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { if (!globalScene.currentBattle.trainer) { console.warn("Enemy trainer is missing!"); diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 989f19c944f..8d66d498039 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -20,6 +20,7 @@ import type Pokemon from "#app/field/pokemon"; * Also triggers Cud Chew's "repeat berry use" effects */ export class BerryPhase extends FieldPhase { + public readonly phaseName = "BerryPhase"; start() { super.start(); diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts index f59dfea9f02..0c5884bbac7 100644 --- a/src/phases/check-status-effect-phase.ts +++ b/src/phases/check-status-effect-phase.ts @@ -4,6 +4,7 @@ import type { BattlerIndex } from "#app/battle"; import { globalScene } from "#app/global-scene"; export class CheckStatusEffectPhase extends Phase { + public readonly phaseName = "CheckStatusEffectPhase"; private order: BattlerIndex[]; constructor(order: BattlerIndex[]) { super(); diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index 9d73411fd37..0506cd36c46 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -10,6 +10,7 @@ import { SwitchPhase } from "./switch-phase"; import { SwitchType } from "#enums/switch-type"; export class CheckSwitchPhase extends BattlePhase { + public readonly phaseName = "CheckSwitchPhase"; protected fieldIndex: number; protected useName: boolean; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 3f18ea95777..209c1eefc85 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -25,6 +25,7 @@ import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagType } from "#app/enums/arena-tag-type"; export class CommandPhase extends FieldPhase { + public readonly phaseName = "CommandPhase"; protected fieldIndex: number; constructor(fieldIndex: number) { diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts index 5be5e112389..4a27db3a651 100644 --- a/src/phases/common-anim-phase.ts +++ b/src/phases/common-anim-phase.ts @@ -5,6 +5,9 @@ import { CommonBattleAnim } from "#app/data/battle-anims"; import { PokemonPhase } from "./pokemon-phase"; export class CommonAnimPhase extends PokemonPhase { + // PokemonHealPhase extends CommonAnimPhase, and to make typescript happy, + // we need to allow phaseName to be a union of the two + public readonly phaseName: "CommonAnimPhase" | "PokemonHealPhase" | "WeatherEffectPhase" = "CommonAnimPhase"; private anim: CommonAnim | null; private targetIndex?: BattlerIndex; private playOnEmptyField: boolean; diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index b9581573f2e..85cb26e0a09 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -6,6 +6,7 @@ import { fixedInt } from "#app/utils/common"; import { PokemonPhase } from "#app/phases/pokemon-phase"; export class DamageAnimPhase extends PokemonPhase { + public readonly phaseName = "DamageAnimPhase"; private amount: number; private damageResult: DamageResult; private critical: boolean; diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 69bcf741383..dfcdc05f9a2 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -20,6 +20,7 @@ import { doShinySparkleAnim } from "#app/field/anims"; * Class that represents egg hatching */ export class EggHatchPhase extends Phase { + public readonly phaseName = "EggHatchPhase"; /** The egg that is hatching */ private egg: Egg; /** The new EggHatchData for the egg/pokemon that hatches */ @@ -224,7 +225,7 @@ export class EggHatchPhase extends Phase { } end() { - if (globalScene.findPhase(p => p instanceof EggHatchPhase)) { + if (globalScene.findPhase(p => p.is("EggHatchPhase"))) { this.eggHatchHandler.clear(); } else { globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index 4632e264c1d..182d6f304d6 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -16,6 +16,7 @@ import { EggHatchData } from "#app/data/egg-hatch-data"; * Also handles prompts for skipping animation, and calling the egg summary phase */ export class EggLapsePhase extends Phase { + public readonly phaseName = "EggLapsePhase"; private eggHatchData: EggHatchData[] = []; private readonly minEggsToSkip: number = 2; diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts index d16cafa7611..cc7857426bc 100644 --- a/src/phases/egg-summary-phase.ts +++ b/src/phases/egg-summary-phase.ts @@ -9,6 +9,7 @@ import type { EggHatchData } from "#app/data/egg-hatch-data"; * Phase is handled mostly by the egg-hatch-scene-handler UI */ export class EggSummaryPhase extends Phase { + public readonly phaseName = "EggSummaryPhase"; private eggHatchData: EggHatchData[]; constructor(eggHatchData: EggHatchData[]) { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index c7308fc5a64..df84f8f8ff4 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -47,6 +47,8 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mys import { getNatureName } from "#app/data/nature"; export class EncounterPhase extends BattlePhase { + // Union type is necessary as this is subclassed, and typescript will otherwise complain + public readonly phaseName: "EncounterPhase" | "NextEncounterPhase" | "NewBiomeEncounterPhase" = "EncounterPhase"; private loaded: boolean; constructor(loaded = false) { diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts index 41775248b67..bb64969514f 100644 --- a/src/phases/end-card-phase.ts +++ b/src/phases/end-card-phase.ts @@ -5,6 +5,7 @@ import { addTextObject, TextStyle } from "#app/ui/text"; import i18next from "i18next"; export class EndCardPhase extends Phase { + public readonly phaseName = "EndCardPhase"; public endCard: Phaser.GameObjects.Image; public text: Phaser.GameObjects.Text; start(): void { diff --git a/src/phases/end-evolution-phase.ts b/src/phases/end-evolution-phase.ts index 579920dde90..cfc0d89fc31 100644 --- a/src/phases/end-evolution-phase.ts +++ b/src/phases/end-evolution-phase.ts @@ -3,6 +3,7 @@ import { Phase } from "#app/phase"; import { UiMode } from "#enums/ui-mode"; export class EndEvolutionPhase extends Phase { + public readonly phaseName = "EndEvolutionPhase"; start() { super.start(); diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 2a1719f9002..a81fc4d2107 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -15,6 +15,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; * @see {@linkcode EnemyPokemon.getNextMove} */ export class EnemyCommandPhase extends FieldPhase { + public readonly phaseName = "EnemyCommandPhase"; protected fieldIndex: number; protected skipTurn = false; diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 8fc8a8be031..5e635f4cd82 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -19,6 +19,9 @@ import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; export class EvolutionPhase extends Phase { + // FormChangePhase inherits from this, but EvolutionPhase is not abstract. + // We have to use the union here + public readonly phaseName: "EvolutionPhase" | "FormChangePhase" = "EvolutionPhase"; protected pokemon: PlayerPokemon; protected lastLevel: number; diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 8841a90d5b1..14d7d7578f8 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -7,6 +7,7 @@ import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-pha import { LevelUpPhase } from "./level-up-phase"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "ExpPhase"; private expValue: number; constructor(partyMemberIndex: number, expValue: number) { diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index bf0adf77061..7332d6b9462 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -35,6 +35,7 @@ import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { BattlerTagType } from "#enums/battler-tag-type"; export class FaintPhase extends PokemonPhase { + public readonly phaseName = "FaintPhase"; /** * Whether or not instant revive should be prevented */ @@ -78,10 +79,15 @@ export class FaintPhase extends PokemonPhase { } } - /** In case the current pokemon was just switched in, make sure it is counted as participating in the combat */ + /** + * In case the current pokemon was just switched in, make sure it is counted as participating in the combat. + * For EXP_SHARE purposes, if the current pokemon faints as the combat ends and it was the ONLY player pokemon + * involved in combat, it needs to be counted as a participant so the other party pokemon can get their EXP, + * so the fainted pokemon has been included. + */ for (const pokemon of globalScene.getPlayerField()) { - if (pokemon?.isActive(true) && pokemon.isPlayer()) { - globalScene.currentBattle.addParticipant(pokemon as PlayerPokemon); + if (pokemon?.isActive() || pokemon?.isFainted()) { + globalScene.currentBattle.addParticipant(pokemon); } } diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 5517fb0f402..f5e428c6d3d 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -13,6 +13,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { SpeciesFormKey } from "#enums/species-form-key"; export class FormChangePhase extends EvolutionPhase { + public readonly phaseName = "FormChangePhase"; private formChange: SpeciesFormChange; private modal: boolean; diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-modifier-reward-phase.ts index ab6f6554c99..13c8f48abad 100644 --- a/src/phases/game-over-modifier-reward-phase.ts +++ b/src/phases/game-over-modifier-reward-phase.ts @@ -4,6 +4,7 @@ import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; export class GameOverModifierRewardPhase extends ModifierRewardPhase { + public readonly phaseName = "GameOverModifierRewardPhase"; doReward(): Promise { return new Promise(resolve => { const newModifier = this.modifierType.newModifier(); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 3a3305fd45e..1d03739d610 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -34,6 +34,7 @@ import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { MessagePhase } from "./message-phase"; export class GameOverPhase extends BattlePhase { + public readonly phaseName = "GameOverPhase"; private isVictory: boolean; private firstRibbons: PokemonSpecies[] = []; diff --git a/src/phases/hide-ability-phase.ts b/src/phases/hide-ability-phase.ts index 142bb4b251d..b0a12da24b1 100644 --- a/src/phases/hide-ability-phase.ts +++ b/src/phases/hide-ability-phase.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; export class HideAbilityPhase extends Phase { + public readonly phaseName = "HideAbilityPhase"; start() { super.start(); diff --git a/src/phases/hide-party-exp-bar-phase.ts b/src/phases/hide-party-exp-bar-phase.ts index 52cfd1f71d6..9ee08280cd4 100644 --- a/src/phases/hide-party-exp-bar-phase.ts +++ b/src/phases/hide-party-exp-bar-phase.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { BattlePhase } from "./battle-phase"; export class HidePartyExpBarPhase extends BattlePhase { + public readonly phaseName = "HidePartyExpBarPhase"; start() { super.start(); diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 65679a7ade7..d455ed35591 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -12,7 +12,6 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import type Pokemon from "#app/field/pokemon"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; export enum LearnMoveType { /** For learning a move via level-up, evolution, or other non-item-based event */ @@ -24,6 +23,7 @@ export enum LearnMoveType { } export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "LearnMovePhase"; private moveId: MoveId; private messageMode: UiMode; private learnMoveType: LearnMoveType; @@ -195,7 +195,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); - globalScene.tryRemovePhase(phase => phase instanceof SelectModifierPhase); + globalScene.tryRemovePhase(phase => phase.is("SelectModifierPhase")); } else if (this.learnMoveType === LearnMoveType.MEMORY) { if (this.cost !== -1) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -205,7 +205,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { } globalScene.playSound("se/buy"); } else { - globalScene.tryRemovePhase(phase => phase instanceof SelectModifierPhase); + globalScene.tryRemovePhase(phase => phase.is("SelectModifierPhase")); } } pokemon.setMove(index, this.moveId); diff --git a/src/phases/level-cap-phase.ts b/src/phases/level-cap-phase.ts index 6f3fa6fdb39..12d4d64e8e2 100644 --- a/src/phases/level-cap-phase.ts +++ b/src/phases/level-cap-phase.ts @@ -4,6 +4,7 @@ import i18next from "i18next"; import { FieldPhase } from "./field-phase"; export class LevelCapPhase extends FieldPhase { + public readonly phaseName = "LevelCapPhase"; start(): void { super.start(); diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 8c4f4f58095..7cf86a313df 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -10,6 +10,7 @@ import { NumberHolder } from "#app/utils/common"; import i18next from "i18next"; export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "LevelUpPhase"; protected lastLevel: number; protected level: number; protected pokemon: PlayerPokemon = this.getPlayerPokemon(); diff --git a/src/phases/load-move-anim-phase.ts b/src/phases/load-move-anim-phase.ts index c0b6cd58c54..c9b78797407 100644 --- a/src/phases/load-move-anim-phase.ts +++ b/src/phases/load-move-anim-phase.ts @@ -8,6 +8,7 @@ import { Phase } from "#app/phase"; * isn't already loaded (e.g. for Metronome) */ export class LoadMoveAnimPhase extends Phase { + public readonly phaseName = "LoadMoveAnimPhase"; constructor(protected moveId: MoveId) { super(); } diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index 673b94b1148..ec12b5ddaa4 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -11,6 +11,7 @@ import { SelectGenderPhase } from "./select-gender-phase"; import { UnavailablePhase } from "./unavailable-phase"; export class LoginPhase extends Phase { + public readonly phaseName = "LoginPhase"; private showText: boolean; constructor(showText = true) { diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index b277d67de82..335258abe5c 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; export class MessagePhase extends Phase { + public readonly phaseName = "MessagePhase"; private text: string; private callbackDelay?: number | null; private prompt?: boolean | null; diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts index c94c4deb819..83bd8704f59 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/modifier-reward-phase.ts @@ -5,6 +5,10 @@ import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; export class ModifierRewardPhase extends BattlePhase { + // RibbonModifierRewardPhase extends ModifierRewardPhase and to make typescript happy + // we need to use a union type here + public readonly phaseName: "ModifierRewardPhase" | "RibbonModifierRewardPhase" | "GameOverModifierRewardPhase" = + "ModifierRewardPhase"; protected modifierType: ModifierType; constructor(modifierTypeFunc: ModifierTypeFunc) { diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 708bb3a2fa8..52cb9ecb3ff 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -6,6 +6,7 @@ import { NumberHolder } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; export class MoneyRewardPhase extends BattlePhase { + public readonly phaseName = "MoneyRewardPhase"; private moneyMultiplier: number; constructor(moneyMultiplier: number) { diff --git a/src/phases/move-anim-phase.ts b/src/phases/move-anim-phase.ts index 830e72cb8be..383841a0146 100644 --- a/src/phases/move-anim-phase.ts +++ b/src/phases/move-anim-phase.ts @@ -5,6 +5,8 @@ import { Phase } from "#app/phase"; * Plays the given {@linkcode MoveAnim} sequentially. */ export class MoveAnimPhase extends Phase { + public readonly phaseName = "MoveAnimPhase"; + constructor( protected anim: Anim, protected onSubstitute = false, diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index ea43f1ddb88..789651623fa 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -9,13 +9,13 @@ import { BooleanHolder } from "#app/utils/common"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; /** * Phase for the "charging turn" of two-turn moves (e.g. Dig). * @extends {@linkcode PokemonPhase} */ export class MoveChargePhase extends PokemonPhase { + public readonly phaseName = "MoveChargePhase"; /** The move instance that this phase applies */ public move: PokemonMove; /** The field index targeted by the move (Charging moves assume single target) */ @@ -62,7 +62,7 @@ export class MoveChargePhase extends PokemonPhase { if (instantCharge.value) { // this MoveEndPhase will be duplicated by the queued MovePhase if not removed - globalScene.tryRemovePhase(phase => phase instanceof MoveEndPhase && phase.getPokemon() === user); + globalScene.tryRemovePhase(phase => phase.is("MoveEndPhase") && phase.getPokemon() === user); // queue a new MovePhase for this move's attack phase globalScene.unshiftPhase(new MovePhase(user, [this.targetIndex], this.move, false)); } else { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 636f85f0f82..3160d848624 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -82,6 +82,7 @@ import { DamageAchv } from "#app/system/achv"; type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; export class MoveEffectPhase extends PokemonPhase { + public readonly phaseName = "MoveEffectPhase"; public move: Move; private virtual = false; protected targets: BattlerIndex[]; diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 037596dca59..6642b97773b 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -6,6 +6,7 @@ import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/ import type Pokemon from "#app/field/pokemon"; export class MoveEndPhase extends PokemonPhase { + public readonly phaseName = "MoveEndPhase"; private wasFollowUp: boolean; /** Targets from the preceding MovePhase */ diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts index c320df462d1..50100e827d6 100644 --- a/src/phases/move-header-phase.ts +++ b/src/phases/move-header-phase.ts @@ -4,6 +4,7 @@ import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class MoveHeaderPhase extends BattlePhase { + public readonly phaseName = "MoveHeaderPhase"; public pokemon: Pokemon; public move: PokemonMove; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 1ccf5b7957e..300c27e01fc 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -52,6 +52,7 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; export class MovePhase extends BattlePhase { + public readonly phaseName = "MovePhase"; protected _pokemon: Pokemon; protected _move: PokemonMove; protected _targets: BattlerIndex[]; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index fd0c4ef7949..5365ab3da32 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -6,7 +6,6 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { GameOverPhase } from "#app/phases/game-over-phase"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; -import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; import { ReturnPhase } from "#app/phases/return-phase"; import { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -39,6 +38,7 @@ import { SelectBiomePhase } from "./select-biome-phase"; * - Queuing of the {@linkcode MysteryEncounterOptionSelectedPhase} */ export class MysteryEncounterPhase extends Phase { + public readonly phaseName = "MysteryEncounterPhase"; private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300; optionSelectSettings?: OptionSelectSettings; @@ -180,6 +180,7 @@ export class MysteryEncounterPhase extends Phase { * Any phase that is meant to follow this one MUST be queued via the onOptionSelect() logic of the selected option */ export class MysteryEncounterOptionSelectedPhase extends Phase { + public readonly phaseName = "MysteryEncounterOptionSelectedPhase"; onOptionSelect: OptionPhaseCallback; constructor() { @@ -221,6 +222,7 @@ export class MysteryEncounterOptionSelectedPhase extends Phase { * See {@linkcode TurnEndPhase} for more details */ export class MysteryEncounterBattleStartCleanupPhase extends Phase { + public readonly phaseName = "MysteryEncounterBattleStartCleanupPhase"; /** * Cleans up `TURN_END` tags, any {@linkcode PostTurnStatusEffectPhase}s, checks for Pokemon switches, then continues */ @@ -245,8 +247,8 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { }); // Remove any status tick phases - while (globalScene.findPhase(p => p instanceof PostTurnStatusEffectPhase)) { - globalScene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase); + while (globalScene.findPhase(p => p.is("PostTurnStatusEffectPhase"))) { + globalScene.tryRemovePhase(p => p.is("PostTurnStatusEffectPhase")); } // The total number of Pokemon in the player's party that can legally fight @@ -284,6 +286,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { * - Queue the {@linkcode SummonPhase}s, {@linkcode PostSummonPhase}s, etc., required to initialize the phase queue for a battle */ export class MysteryEncounterBattlePhase extends Phase { + public readonly phaseName = "MysteryEncounterBattlePhase"; disableSwitch: boolean; constructor(disableSwitch = false) { @@ -513,6 +516,7 @@ export class MysteryEncounterBattlePhase extends Phase { * - Queuing of the {@linkcode PostMysteryEncounterPhase} */ export class MysteryEncounterRewardsPhase extends Phase { + public readonly phaseName = "MysteryEncounterRewardsPhase"; addHealPhase: boolean; constructor(addHealPhase = false) { @@ -558,7 +562,7 @@ export class MysteryEncounterRewardsPhase extends Phase { if (encounter.doEncounterRewards) { encounter.doEncounterRewards(); } else if (this.addHealPhase) { - globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); + globalScene.tryRemovePhase(p => p.is("SelectModifierPhase")); globalScene.unshiftPhase( new SelectModifierPhase(0, undefined, { fillRemaining: false, @@ -580,6 +584,7 @@ export class MysteryEncounterRewardsPhase extends Phase { * - Queuing of the next wave */ export class PostMysteryEncounterPhase extends Phase { + public readonly phaseName = "PostMysteryEncounterPhase"; private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750; onPostOptionSelect?: OptionPhaseCallback; diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts index 09b8ab1d335..c4cfc72fb53 100644 --- a/src/phases/new-battle-phase.ts +++ b/src/phases/new-battle-phase.ts @@ -2,13 +2,14 @@ import { globalScene } from "#app/global-scene"; import { BattlePhase } from "./battle-phase"; export class NewBattlePhase extends BattlePhase { + public readonly phaseName = "NewBattlePhase"; start() { super.start(); // cull any extra `NewBattle` phases from the queue. - globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !(phase instanceof NewBattlePhase)); + globalScene.phaseQueue = globalScene.phaseQueue.filter(phase => !phase.is("NewBattlePhase")); // `phaseQueuePrepend` is private, so we have to use this inefficient loop. - while (globalScene.tryRemoveUnshiftedPhase(phase => phase instanceof NewBattlePhase)) {} + while (globalScene.tryRemoveUnshiftedPhase(phase => phase.is("NewBattlePhase"))) {} globalScene.newBattle(); diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index ef027bfd77a..29ba67cb797 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -4,6 +4,7 @@ import { getRandomWeatherType } from "#app/data/weather"; import { NextEncounterPhase } from "./next-encounter-phase"; export class NewBiomeEncounterPhase extends NextEncounterPhase { + public readonly phaseName = "NewBiomeEncounterPhase"; doEncounter(): void { globalScene.playBgm(undefined, true); diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts index 30b4004363c..c31b4b5bbc3 100644 --- a/src/phases/next-encounter-phase.ts +++ b/src/phases/next-encounter-phase.ts @@ -6,6 +6,7 @@ import { EncounterPhase } from "./encounter-phase"; * Handles generating, loading and preparing for it. */ export class NextEncounterPhase extends EncounterPhase { + public readonly phaseName: "NextEncounterPhase" | "NewBiomeEncounterPhase" = "NextEncounterPhase"; start() { super.start(); } diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index 47cae2dcbf6..820db910681 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -11,6 +11,7 @@ import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilit import { isNullOrUndefined } from "#app/utils/common"; export class ObtainStatusEffectPhase extends PokemonPhase { + public readonly phaseName = "ObtainStatusEffectPhase"; private statusEffect?: StatusEffect; private turnsRemaining?: number; private sourceText?: string | null; diff --git a/src/phases/party-exp-phase.ts b/src/phases/party-exp-phase.ts index 8fd9e1cf0f6..30fc97d9105 100644 --- a/src/phases/party-exp-phase.ts +++ b/src/phases/party-exp-phase.ts @@ -6,6 +6,7 @@ import { Phase } from "#app/phase"; * Intended to be used as a more 1-off phase to provide exp to the party (such as during MEs), rather than cleanup a battle entirely */ export class PartyExpPhase extends Phase { + public readonly phaseName = "PartyExpPhase"; expValue: number; useWaveIndexMultiplier?: boolean; pokemonParticipantIds?: Set; diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 4a9f8a0c888..765c7dbad8e 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -3,6 +3,7 @@ import { fixedInt } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; export class PartyHealPhase extends BattlePhase { + public readonly phaseName = "PartyHealPhase"; private resumeBgm: boolean; constructor(resumeBgm: boolean) { diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index e9f0097459a..b1a21446996 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -7,6 +7,7 @@ import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { SpeciesId } from "#enums/species-id"; export class PokemonAnimPhase extends BattlePhase { + public readonly phaseName = "PokemonAnimPhase"; /** The type of animation to play in this phase */ protected key: PokemonAnimType; /** The Pokemon to which this animation applies */ diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index 7cb013251f6..60bbb17c30a 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -14,6 +14,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import type { HealBlockTag } from "#app/data/battler-tags"; export class PokemonHealPhase extends CommonAnimPhase { + public readonly phaseName = "PokemonHealPhase"; private hpHealed: number; private message: string | null; private showFullHpMessage: boolean; diff --git a/src/phases/pokemon-transform-phase.ts b/src/phases/pokemon-transform-phase.ts index 23a9a983bae..c0f3b048003 100644 --- a/src/phases/pokemon-transform-phase.ts +++ b/src/phases/pokemon-transform-phase.ts @@ -13,6 +13,7 @@ import i18next from "i18next"; * Used for Transform (move) and Imposter (ability) */ export class PokemonTransformPhase extends PokemonPhase { + public readonly phaseName = "PokemonTransformPhase"; protected targetIndex: BattlerIndex; private playSound: boolean; diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts index 753251e992f..f985419da7a 100644 --- a/src/phases/post-game-over-phase.ts +++ b/src/phases/post-game-over-phase.ts @@ -4,6 +4,7 @@ import type { EndCardPhase } from "./end-card-phase"; import { TitlePhase } from "./title-phase"; export class PostGameOverPhase extends Phase { + public readonly phaseName = "PostGameOverPhase"; private endCardPhase?: EndCardPhase; constructor(endCardPhase?: EndCardPhase) { diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index 446d45bb2fa..a7faf614292 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -7,6 +7,7 @@ import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#enums/battler-tag-type"; export class PostSummonPhase extends PokemonPhase { + public readonly phaseName = "PostSummonPhase"; start() { super.start(); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 9b530d48196..47a84059745 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -17,6 +17,7 @@ import { BooleanHolder, NumberHolder } from "#app/utils/common"; import { PokemonPhase } from "./pokemon-phase"; export class PostTurnStatusEffectPhase extends PokemonPhase { + public readonly phaseName = "PostTurnStatusEffectPhase"; // biome-ignore lint/complexity/noUselessConstructor: Not unnecessary as it makes battlerIndex required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 76411f62f77..9f6b5cb3361 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -9,7 +9,7 @@ import type Pokemon from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; -import { MovePhase } from "./move-phase"; +import type { MovePhase } from "./move-phase"; import { PokemonHealPhase } from "./pokemon-heal-phase"; import { applyAbAttrs, @@ -19,6 +19,7 @@ import { } from "#app/data/abilities/ability"; export class QuietFormChangePhase extends BattlePhase { + public readonly phaseName = "QuietFormChangePhase"; protected pokemon: Pokemon; protected formChange: SpeciesFormChange; @@ -168,7 +169,7 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.initBattleInfo(); this.pokemon.cry(); - const movePhase = globalScene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; + const movePhase = globalScene.findPhase(p => p.is("MovePhase") && p.pokemon === this.pokemon) as MovePhase; if (movePhase) { movePhase.cancel(); } diff --git a/src/phases/reload-session-phase.ts b/src/phases/reload-session-phase.ts index 8cd5f67b43a..ac9337753c4 100644 --- a/src/phases/reload-session-phase.ts +++ b/src/phases/reload-session-phase.ts @@ -4,6 +4,7 @@ import { UiMode } from "#enums/ui-mode"; import { fixedInt } from "#app/utils/common"; export class ReloadSessionPhase extends Phase { + public readonly phaseName = "ReloadSessionPhase"; private systemDataStr?: string; constructor(systemDataStr?: string) { diff --git a/src/phases/reset-status-phase.ts b/src/phases/reset-status-phase.ts index 19bfc3027e2..779f375d7e2 100644 --- a/src/phases/reset-status-phase.ts +++ b/src/phases/reset-status-phase.ts @@ -7,6 +7,7 @@ import { BattlePhase } from "#app/phases/battle-phase"; * This is necessary to perform in a phase primarly to ensure that the status icon disappears at the correct time in the battle */ export class ResetStatusPhase extends BattlePhase { + public readonly phaseName = "ResetStatusPhase"; private readonly pokemon: Pokemon; private readonly affectConfusion: boolean; private readonly reloadAssets: boolean; diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts index 6dee982a4f0..6365256d40a 100644 --- a/src/phases/return-phase.ts +++ b/src/phases/return-phase.ts @@ -4,6 +4,7 @@ import { SwitchType } from "#enums/switch-type"; import { SwitchSummonPhase } from "./switch-summon-phase"; export class ReturnPhase extends SwitchSummonPhase { + public readonly phaseName = "ReturnPhase"; constructor(fieldIndex: number) { super(SwitchType.SWITCH, fieldIndex, -1, true); } diff --git a/src/phases/revival-blessing-phase.ts b/src/phases/revival-blessing-phase.ts index 428acaf9ed4..3f70c93dd7a 100644 --- a/src/phases/revival-blessing-phase.ts +++ b/src/phases/revival-blessing-phase.ts @@ -15,6 +15,7 @@ import type { PlayerPokemon } from "#app/field/pokemon"; * when used by one of the player's Pokemon. */ export class RevivalBlessingPhase extends BattlePhase { + public readonly phaseName = "RevivalBlessingPhase"; constructor(protected user: PlayerPokemon) { super(); } diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts index 21114ab3de9..949f7af0302 100644 --- a/src/phases/ribbon-modifier-reward-phase.ts +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -6,6 +6,7 @@ import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; export class RibbonModifierRewardPhase extends ModifierRewardPhase { + public readonly phaseName = "RibbonModifierRewardPhase"; private species: PokemonSpecies; constructor(modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index d79a32bd47e..df68a2d1cab 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -8,6 +8,7 @@ import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; export class ScanIvsPhase extends PokemonPhase { + public readonly phaseName = "ScanIvsPhase"; // biome-ignore lint/complexity/noUselessConstructor: This changes `battlerIndex` to be required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index a7736b16811..ef6b39e8b8f 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -10,6 +10,7 @@ import { PartyHealPhase } from "./party-heal-phase"; import { SwitchBiomePhase } from "./switch-biome-phase"; export class SelectBiomePhase extends BattlePhase { + public readonly phaseName = "SelectBiomePhase"; start() { super.start(); diff --git a/src/phases/select-challenge-phase.ts b/src/phases/select-challenge-phase.ts index 76ac8a60c4f..dcf72d1b441 100644 --- a/src/phases/select-challenge-phase.ts +++ b/src/phases/select-challenge-phase.ts @@ -3,6 +3,7 @@ import { Phase } from "#app/phase"; import { UiMode } from "#enums/ui-mode"; export class SelectChallengePhase extends Phase { + public readonly phaseName = "SelectChallengePhase"; start() { super.start(); diff --git a/src/phases/select-gender-phase.ts b/src/phases/select-gender-phase.ts index a1171c1a5db..ad8515e312e 100644 --- a/src/phases/select-gender-phase.ts +++ b/src/phases/select-gender-phase.ts @@ -6,6 +6,7 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; export class SelectGenderPhase extends Phase { + public readonly phaseName = "SelectGenderPhase"; start(): void { super.start(); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 5f11441333b..6e429d9ad7f 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -32,6 +32,7 @@ import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; export class SelectModifierPhase extends BattlePhase { + public readonly phaseName = "SelectModifierPhase"; private rerollCount: number; private modifierTiers?: ModifierTier[]; private customModifierSettings?: CustomModifierSettings; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 6d333f4001c..d25c2dd7211 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -15,6 +15,7 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { isNullOrUndefined } from "#app/utils/common"; export class SelectStarterPhase extends Phase { + public readonly phaseName = "SelectStarterPhase"; start() { super.start(); diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index f8a8ecfbf18..46ba378a56c 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -8,6 +8,7 @@ import i18next from "#app/plugins/i18n"; import { allMoves } from "#app/data/data-lists"; export class SelectTargetPhase extends PokemonPhase { + public readonly phaseName = "SelectTargetPhase"; // biome-ignore lint/complexity/noUselessConstructor: This makes `fieldIndex` required constructor(fieldIndex: number) { super(fieldIndex); diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts index 87a7db29cf6..93d7dd67209 100644 --- a/src/phases/shiny-sparkle-phase.ts +++ b/src/phases/shiny-sparkle-phase.ts @@ -3,6 +3,7 @@ import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; export class ShinySparklePhase extends PokemonPhase { + public readonly phaseName = "ShinySparklePhase"; // biome-ignore lint/complexity/noUselessConstructor: This makes `battlerIndex` required constructor(battlerIndex: BattlerIndex) { super(battlerIndex); diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index d6193ac3946..81aa69537e5 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -5,6 +5,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; export class ShowAbilityPhase extends PokemonPhase { + public readonly phaseName = "ShowAbilityPhase"; private passive: boolean; private pokemonName: string; private abilityName: string; diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index 89bec6d8fdd..6b4236f0868 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -8,6 +8,7 @@ import { LevelUpPhase } from "./level-up-phase"; import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { + public readonly phaseName = "ShowPartyExpBarPhase"; private expValue: number; constructor(partyMemberIndex: number, expValue: number) { diff --git a/src/phases/show-trainer-phase.ts b/src/phases/show-trainer-phase.ts index b6c1e345c70..bae6ecd839c 100644 --- a/src/phases/show-trainer-phase.ts +++ b/src/phases/show-trainer-phase.ts @@ -3,6 +3,7 @@ import { PlayerGender } from "#app/enums/player-gender"; import { BattlePhase } from "./battle-phase"; export class ShowTrainerPhase extends BattlePhase { + public readonly phaseName = "ShowTrainerPhase"; start() { super.start(); diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 6731e45025c..baa93c63099 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -31,6 +31,7 @@ export type StatStageChangeCallback = ( ) => void; export class StatStageChangePhase extends PokemonPhase { + public readonly phaseName = "StatStageChangePhase"; private stats: BattleStat[]; private selfTarget: boolean; private stages: number; @@ -235,9 +236,9 @@ export class StatStageChangePhase extends PokemonPhase { // Look for any other stat change phases; if this is the last one, do White Herb check const existingPhase = globalScene.findPhase( - p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex, + p => p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex, ); - if (!(existingPhase instanceof StatStageChangePhase)) { + if (!existingPhase?.is("StatStageChangePhase")) { // Apply White Herb if needed const whiteHerb = globalScene.applyModifier( ResetNegativeStatStageModifier, @@ -316,7 +317,7 @@ export class StatStageChangePhase extends PokemonPhase { while ( (existingPhase = globalScene.findPhase( p => - p instanceof StatStageChangePhase && + p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex && p.stats.length === 1 && p.stats[0] === this.stats[0] && @@ -335,7 +336,7 @@ export class StatStageChangePhase extends PokemonPhase { while ( (existingPhase = globalScene.findPhase( p => - p instanceof StatStageChangePhase && + p.is("StatStageChangePhase") && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget && accEva.some(s => p.stats.includes(s)) === isAccEva && diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts index a692455ce47..ce3e982055e 100644 --- a/src/phases/summon-missing-phase.ts +++ b/src/phases/summon-missing-phase.ts @@ -4,6 +4,7 @@ import { SummonPhase } from "./summon-phase"; import { globalScene } from "#app/global-scene"; export class SummonMissingPhase extends SummonPhase { + public readonly phaseName = "SummonMissingPhase"; preSummon(): void { globalScene.ui.showText( i18next.t("battle:sendOutPokemon", { diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index c217583f163..2cd7b122bb3 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -17,6 +17,8 @@ import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/abil import { globalScene } from "#app/global-scene"; export class SummonPhase extends PartyMemberPokemonPhase { + // The union type is needed to keep typescript happy as these phases extend from SummonPhase + public readonly phaseName: "SummonPhase" | "SummonMissingPhase" | "SwitchSummonPhase" | "ReturnPhase" = "SummonPhase"; private loaded: boolean; constructor(fieldIndex: number, player = true, loaded = false) { diff --git a/src/phases/switch-biome-phase.ts b/src/phases/switch-biome-phase.ts index 69a6c97cd9a..f84f1d517b4 100644 --- a/src/phases/switch-biome-phase.ts +++ b/src/phases/switch-biome-phase.ts @@ -4,6 +4,7 @@ import { getBiomeKey } from "#app/field/arena"; import { BattlePhase } from "./battle-phase"; export class SwitchBiomePhase extends BattlePhase { + public readonly phaseName = "SwitchBiomePhase"; private nextBiome: BiomeId; constructor(nextBiome: BiomeId) { diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index c056b186021..6017aa0fa70 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -3,7 +3,6 @@ import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handl import { UiMode } from "#enums/ui-mode"; import { SwitchType } from "#enums/switch-type"; import { BattlePhase } from "./battle-phase"; -import { PostSummonPhase } from "./post-summon-phase"; import { SwitchSummonPhase } from "./switch-summon-phase"; /** @@ -11,6 +10,7 @@ import { SwitchSummonPhase } from "./switch-summon-phase"; * for the player (if a switch would be valid for the current battle state). */ export class SwitchPhase extends BattlePhase { + public readonly phaseName = "SwitchPhase"; protected readonly fieldIndex: number; private readonly switchType: SwitchType; private readonly isModal: boolean; @@ -76,7 +76,7 @@ export class SwitchPhase extends BattlePhase { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { // Remove any pre-existing PostSummonPhase under the same field index. // Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave. - globalScene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); + globalScene.tryRemovePhase(p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex); const switchType = option === PartyOption.PASS_BATON ? SwitchType.BATON_PASS : this.switchType; globalScene.unshiftPhase(new SwitchSummonPhase(switchType, fieldIndex, slotIndex, this.doReturn)); } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 6bdbb66be14..d81ca6029c5 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -22,6 +22,7 @@ import { SubstituteTag } from "#app/data/battler-tags"; import { SwitchType } from "#enums/switch-type"; export class SwitchSummonPhase extends SummonPhase { + public readonly phaseName: "SwitchSummonPhase" | "ReturnPhase" = "SwitchSummonPhase"; private readonly switchType: SwitchType; private readonly slotIndex: number; private readonly doReturn: boolean; diff --git a/src/phases/tera-phase.ts b/src/phases/tera-phase.ts index c9320daf12f..5e4ea2fe54e 100644 --- a/src/phases/tera-phase.ts +++ b/src/phases/tera-phase.ts @@ -9,6 +9,7 @@ import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; export class TeraPhase extends BattlePhase { + public readonly phaseName = "TeraPhase"; public pokemon: Pokemon; constructor(pokemon: Pokemon) { diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 56057c23372..aa9ae49ca8b 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -29,6 +29,7 @@ import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; export class TitlePhase extends Phase { + public readonly phaseName = "TitlePhase"; private loaded = false; private lastSessionData: SessionSaveData; public gameMode: GameModes; diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts index 37f47d5cf95..a6b8705f580 100644 --- a/src/phases/toggle-double-position-phase.ts +++ b/src/phases/toggle-double-position-phase.ts @@ -3,6 +3,7 @@ import { FieldPosition } from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class ToggleDoublePositionPhase extends BattlePhase { + public readonly phaseName = "ToggleDoublePositionPhase"; private double: boolean; constructor(double: boolean) { diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index daf5c38e57b..bd035248530 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -14,6 +14,7 @@ import { achvs } from "#app/system/achv"; import { timedEventManager } from "#app/global-event-manager"; export class TrainerVictoryPhase extends BattlePhase { + public readonly phaseName = "TrainerVictoryPhase"; start() { globalScene.disableMenu = true; diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 756c497802b..4d486c4bbd6 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -18,6 +18,7 @@ import { PokemonHealPhase } from "./pokemon-heal-phase"; import { globalScene } from "#app/global-scene"; export class TurnEndPhase extends FieldPhase { + public readonly phaseName = "TurnEndPhase"; start() { super.start(); diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index 0c110024af7..7f94acd3b32 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -15,6 +15,7 @@ import { TurnStartPhase } from "./turn-start-phase"; import { globalScene } from "#app/global-scene"; export class TurnInitPhase extends FieldPhase { + public readonly phaseName = "TurnInitPhase"; start() { super.start(); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index a02d869af10..2d009b30bf3 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -25,6 +25,7 @@ import { globalScene } from "#app/global-scene"; import { TeraPhase } from "./tera-phase"; export class TurnStartPhase extends FieldPhase { + public readonly phaseName = "TurnStartPhase"; /** * This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array. * It also checks for Trick Room and reverses the array if it is present. diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts index e5f1d899191..4c4333ceb90 100644 --- a/src/phases/unavailable-phase.ts +++ b/src/phases/unavailable-phase.ts @@ -4,6 +4,7 @@ import { UiMode } from "#enums/ui-mode"; import { LoginPhase } from "./login-phase"; export class UnavailablePhase extends Phase { + public readonly phaseName = "UnavailablePhase"; start(): void { globalScene.ui.setMode(UiMode.UNAVAILABLE, () => { globalScene.unshiftPhase(new LoginPhase(true)); diff --git a/src/phases/unlock-phase.ts b/src/phases/unlock-phase.ts index 7a69fc207bb..839ac31dc5d 100644 --- a/src/phases/unlock-phase.ts +++ b/src/phases/unlock-phase.ts @@ -6,6 +6,7 @@ import { UiMode } from "#enums/ui-mode"; import i18next from "i18next"; export class UnlockPhase extends Phase { + public readonly phaseName = "UnlockPhase"; private unlockable: Unlockables; constructor(unlockable: Unlockables) { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 1204877fec2..2d21f8abc08 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -18,6 +18,7 @@ import { timedEventManager } from "#app/global-event-manager"; import { SelectBiomePhase } from "./select-biome-phase"; export class VictoryPhase extends PokemonPhase { + public readonly phaseName = "VictoryPhase"; /** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */ isExpOnly: boolean; diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index d89c78e96c7..cd91b89771c 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -19,6 +19,7 @@ import { BooleanHolder, toDmgValue } from "#app/utils/common"; import { CommonAnimPhase } from "./common-anim-phase"; export class WeatherEffectPhase extends CommonAnimPhase { + public readonly phaseName = "WeatherEffectPhase"; public weather: Weather | null; constructor() { diff --git a/src/sprites/sprite-utils.ts b/src/sprites/sprite-utils.ts index 8a352de3d55..0f4adf7882f 100644 --- a/src/sprites/sprite-utils.ts +++ b/src/sprites/sprite-utils.ts @@ -21,8 +21,5 @@ export function hasExpSprite(key: string): boolean { if (keyMatch[5]) { k += keyMatch[5]; } - if (!expSpriteKeys.has(k)) { - return false; - } - return true; + return expSpriteKeys.has(k); } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index bc74ab15930..ab907d2f768 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -179,11 +179,11 @@ interface Unlocks { [key: number]: boolean; } -interface AchvUnlocks { +export interface AchvUnlocks { [key: string]: number; } -interface VoucherUnlocks { +export interface VoucherUnlocks { [key: string]: number; } diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index 12add905096..d39d5cf5a41 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -4,6 +4,7 @@ import { truncateString } from "../../utils/common"; import { Button } from "#enums/buttons"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; export enum SettingGamepad { Controller = "CONTROLLER", @@ -27,11 +28,11 @@ export enum SettingGamepad { Button_Submit = "BUTTON_SUBMIT", } -const pressAction = "Press action to assign"; +const pressAction = i18next.t("settings:pressActionToAssign"); export const settingGamepadOptions = { - [SettingGamepad.Controller]: ["Default", "Change"], - [SettingGamepad.Gamepad_Support]: ["Auto", "Disabled"], + [SettingGamepad.Controller]: [i18next.t("settings:controllerDefault"), i18next.t("settings:controllerChange")], + [SettingGamepad.Gamepad_Support]: [i18next.t("settings:gamepadSupportAuto"), i18next.t("settings:gamepadSupportDisabled")], [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], @@ -140,7 +141,7 @@ export function setSettingGamepad(setting: SettingGamepad, value: number): boole handler: () => changeGamepadHandler(g), })), { - label: "Cancel", + label: i18next.t("settings:cancelContollerChoice"), handler: cancelHandler, }, ], diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index d0c8b716c7a..8588530d370 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -11,20 +11,20 @@ import { addWindow } from "#app/ui/ui-theme"; import { ScrollBar } from "#app/ui/scroll-bar"; import { PlayerGender } from "#enums/player-gender"; import { globalScene } from "#app/global-scene"; +import type { AchvUnlocks, VoucherUnlocks } from "#app/system/game-data"; -enum Page { - ACHIEVEMENTS, - VOUCHERS, -} +const Page = { + ACHIEVEMENTS: 0, + VOUCHERS: 1, +} as const; +type Page = (typeof Page)[keyof typeof Page]; interface LanguageSetting { TextSize: string; } const languageSettings: { [key: string]: LanguageSetting } = { - de: { - TextSize: "80px", - }, + de: { TextSize: "80px" }, }; export default class AchvsUiHandler extends MessageUiHandler { @@ -70,44 +70,35 @@ export default class AchvsUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.mainContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); + /** Width of the global canvas / 6 */ + const WIDTH = globalScene.game.canvas.width / 6; + /** Height of the global canvas / 6 */ + const HEIGHT = globalScene.game.canvas.height / 6; - this.mainContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), - Phaser.Geom.Rectangle.Contains, - ); + this.mainContainer = globalScene.add.container(1, -HEIGHT + 1); - this.headerBg = addWindow(0, 0, globalScene.game.canvas.width / 6 - 2, 24); - this.headerBg.setOrigin(0, 0); + this.mainContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, WIDTH, HEIGHT), Phaser.Geom.Rectangle.Contains); - this.headerText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL); - this.headerText.setOrigin(0, 0); - this.headerText.setPositionRelative(this.headerBg, 8, 4); - this.headerActionButton = new Phaser.GameObjects.Sprite(globalScene, 0, 0, "keyboard", "ACTION.png"); - this.headerActionButton.setOrigin(0, 0); - this.headerActionButton.setPositionRelative(this.headerBg, 236, 6); - this.headerActionText = addTextObject(0, 0, "", TextStyle.WINDOW, { - fontSize: "60px", - }); - this.headerActionText.setOrigin(0, 0); - this.headerActionText.setPositionRelative(this.headerBg, 264, 8); + this.headerBg = addWindow(0, 0, WIDTH - 2, 24); + + this.headerText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL) + .setOrigin(0) + .setPositionRelative(this.headerBg, 8, 4); + this.headerActionButton = new Phaser.GameObjects.Sprite(globalScene, 0, 0, "keyboard", "ACTION.png") + .setOrigin(0) + .setPositionRelative(this.headerBg, 236, 6); + this.headerActionText = addTextObject(0, 0, "", TextStyle.WINDOW, { fontSize: "60px" }) + .setOrigin(0) + .setPositionRelative(this.headerBg, 264, 8); // We need to get the player gender from the game data to add the correct prefix to the achievement name const genderIndex = globalScene.gameData.gender ?? PlayerGender.MALE; const genderStr = PlayerGender[genderIndex].toLowerCase(); - this.achvsName = i18next.t("achv:Achievements.name", { - context: genderStr, - }); + this.achvsName = i18next.t("achv:Achievements.name", { context: genderStr }); this.vouchersName = i18next.t("voucher:vouchers"); - this.iconsBg = addWindow( - 0, - this.headerBg.height, - globalScene.game.canvas.width / 6 - 2, - globalScene.game.canvas.height / 6 - this.headerBg.height - 68, - ); - this.iconsBg.setOrigin(0, 0); + this.iconsBg = addWindow(0, this.headerBg.height, WIDTH - 2, HEIGHT - this.headerBg.height - 68).setOrigin(0); const yOffset = 6; this.scrollBar = new ScrollBar( @@ -126,68 +117,59 @@ export default class AchvsUiHandler extends MessageUiHandler { const x = (a % this.COLS) * 18; const y = Math.floor(a / this.COLS) * 18; - const icon = globalScene.add.sprite(x, y, "items", "unknown"); - icon.setOrigin(0, 0); - icon.setScale(0.5); + const icon = globalScene.add.sprite(x, y, "items", "unknown").setOrigin(0).setScale(0.5); this.icons.push(icon); this.iconsContainer.add(icon); } const titleBg = addWindow(0, this.headerBg.height + this.iconsBg.height, 174, 24); - titleBg.setOrigin(0, 0); this.titleBg = titleBg; - this.titleText = addTextObject(0, 0, "", TextStyle.WINDOW); + this.titleText = addTextObject(0, 0, "", TextStyle.WINDOW).setOrigin(); const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize; this.titleText.setFontSize(textSize); const titleBgCenterX = titleBg.x + titleBg.width / 2; const titleBgCenterY = titleBg.y + titleBg.height / 2; - this.titleText.setOrigin(0.5, 0.5); this.titleText.setPosition(titleBgCenterX, titleBgCenterY); this.scoreContainer = globalScene.add.container(titleBg.x + titleBg.width, titleBg.y); const scoreBg = addWindow(0, 0, 46, 24); - scoreBg.setOrigin(0, 0); - this.scoreContainer.add(scoreBg); - this.scoreText = addTextObject(scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW); - this.scoreText.setOrigin(0.5, 0.5); - this.scoreContainer.add(this.scoreText); + this.scoreText = addTextObject(scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW).setOrigin(); + this.scoreContainer.add([scoreBg, this.scoreText]); const unlockBg = addWindow(this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24); - unlockBg.setOrigin(0, 0); - this.unlockText = addTextObject(0, 0, "", TextStyle.WINDOW); - this.unlockText.setOrigin(0.5, 0.5); - this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2); + this.unlockText = addTextObject(0, 0, "", TextStyle.WINDOW) + .setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2) + .setOrigin(); - const descriptionBg = addWindow(0, titleBg.y + titleBg.height, globalScene.game.canvas.width / 6 - 2, 42); - descriptionBg.setOrigin(0, 0); + const descriptionBg = addWindow(0, titleBg.y + titleBg.height, WIDTH - 2, 42); - const descriptionText = addTextObject(0, 0, "", TextStyle.WINDOW, { - maxLines: 2, - }); - descriptionText.setWordWrapWidth(1870); - descriptionText.setOrigin(0, 0); - descriptionText.setPositionRelative(descriptionBg, 8, 4); + const descriptionText = addTextObject(0, 0, "", TextStyle.WINDOW, { maxLines: 2 }) + .setWordWrapWidth(1870) + .setOrigin(0) + .setPositionRelative(descriptionBg, 8, 4); this.message = descriptionText; - this.mainContainer.add(this.headerBg); - this.mainContainer.add(this.headerActionButton); - this.mainContainer.add(this.headerText); - this.mainContainer.add(this.headerActionText); - this.mainContainer.add(this.iconsBg); - this.mainContainer.add(this.scrollBar); - this.mainContainer.add(this.iconsContainer); - this.mainContainer.add(titleBg); - this.mainContainer.add(this.titleText); - this.mainContainer.add(this.scoreContainer); - this.mainContainer.add(unlockBg); - this.mainContainer.add(this.unlockText); - this.mainContainer.add(descriptionBg); - this.mainContainer.add(descriptionText); + this.mainContainer.add([ + this.headerBg, + this.headerActionButton, + this.headerText, + this.headerActionText, + this.iconsBg, + this.scrollBar, + this.iconsContainer, + titleBg, + this.titleText, + this.scoreContainer, + unlockBg, + this.unlockText, + descriptionBg, + descriptionText, + ]); ui.add(this.mainContainer); @@ -246,87 +228,132 @@ export default class AchvsUiHandler extends MessageUiHandler { ); } - processInput(button: Button): boolean { - const ui = this.getUi(); + // #region Input Processing + /** + * Submethod of {@linkcode processInput} that handles the action button input + * @returns Whether the success sound should be played + */ + private processActionInput(): true { + this.setScrollCursor(0); + if (this.currentPage === Page.ACHIEVEMENTS) { + this.currentPage = Page.VOUCHERS; + this.updateVoucherIcons(); + } else if (this.currentPage === Page.VOUCHERS) { + this.currentPage = Page.ACHIEVEMENTS; + this.updateAchvIcons(); + } + this.setCursor(0, true); + this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS)); + this.scrollBar.setScrollCursor(0); + this.mainContainer.update(); + return true; + } + /** + * Submethod of {@linkcode processInput} that handles the up button input + * @returns Whether the success sound should be played + */ + private processUpInput(): boolean { + if (this.cursor >= this.COLS) { + return this.setCursor(this.cursor - this.COLS); + } + if (this.scrollCursor) { + return this.setScrollCursor(this.scrollCursor - 1); + } + + // Wrap around to the last row + const success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); + let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; + if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { + newCursorIndex -= this.COLS; + } + return success && this.setCursor(newCursorIndex); + } + + /** + * Submethod of {@linkcode processInput} that handles the down button input + * @returns Whether the success sound should be played + */ + private processDownInput(): boolean { + const rowIndex = Math.floor(this.cursor / this.COLS); + const itemOffset = this.scrollCursor * this.COLS; + const canMoveDown = itemOffset + 1 < this.currentTotal; + + if (rowIndex >= this.ROWS - 1) { + if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { + // scroll down one row + return this.setScrollCursor(this.scrollCursor + 1); + } + // wrap back to the first row + return this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + } + if (canMoveDown) { + return this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); + } + return false; + } + + /** + * Submethod of {@linkcode processInput} that handles the left button input + * @returns Whether the success sound should be played + */ + private processLeftInput(): boolean { + const itemOffset = this.scrollCursor * this.COLS; + if (this.cursor % this.COLS === 0) { + return this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); + } + return this.setCursor(this.cursor - 1); + } + + /** + * Submethod of {@linkcode processInput} that handles the right button input + * @returns Whether the success sound should be played + */ + private processRightInput(): boolean { + const itemOffset = this.scrollCursor * this.COLS; + if ((this.cursor + 1) % this.COLS === 0 || this.cursor + itemOffset === this.currentTotal - 1) { + return this.setCursor(this.cursor - (this.cursor % this.COLS)); + } + return this.setCursor(this.cursor + 1); + } + + /** + * Process user input to navigate through the achievements and vouchers UI. + * @param button - The button that was pressed + * @returns Whether an action was successfully processed + */ + processInput(button: Button): boolean { let success = false; - if (button === Button.ACTION) { - success = true; - this.setScrollCursor(0); - if (this.currentPage === Page.ACHIEVEMENTS) { - this.currentPage = Page.VOUCHERS; - this.updateVoucherIcons(); - } else if (this.currentPage === Page.VOUCHERS) { - this.currentPage = Page.ACHIEVEMENTS; - this.updateAchvIcons(); - } - this.setCursor(0, true); - this.scrollBar.setTotalRows(Math.ceil(this.currentTotal / this.COLS)); - this.scrollBar.setScrollCursor(0); - this.mainContainer.update(); - } - if (button === Button.CANCEL) { - success = true; - globalScene.ui.revertMode(); - } else { - const rowIndex = Math.floor(this.cursor / this.COLS); - const itemOffset = this.scrollCursor * this.COLS; - switch (button) { - case Button.UP: - if (this.cursor < this.COLS) { - if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { - // Wrap around to the last row - success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); - let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; - if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { - newCursorIndex -= this.COLS; - } - success = success && this.setCursor(newCursorIndex); - } - } else { - success = this.setCursor(this.cursor - this.COLS); - } - break; - case Button.DOWN: - const canMoveDown = itemOffset + 1 < this.currentTotal; - if (rowIndex >= this.ROWS - 1) { - if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { - // scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); - } else { - // wrap back to the first row - success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); - } - } else if (canMoveDown) { - success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); - } - break; - case Button.LEFT: - if (this.cursor % this.COLS === 0) { - success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); - } else { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if ((this.cursor + 1) % this.COLS === 0 || this.cursor + itemOffset === this.currentTotal - 1) { - success = this.setCursor(this.cursor - (this.cursor % this.COLS)); - } else { - success = this.setCursor(this.cursor + 1); - } - break; - } + switch (button) { + case Button.ACTION: + success = this.processActionInput(); + break; + case Button.CANCEL: + success = true; + globalScene.ui.revertMode(); + break; + case Button.UP: + success = this.processUpInput(); + break; + case Button.DOWN: + success = this.processDownInput(); + break; + case Button.LEFT: + success = this.processLeftInput(); + break; + case Button.RIGHT: + success = this.processRightInput(); + break; } if (success) { - ui.playSelect(); + this.getUi().playSelect(); } return success; } + // #endregion Input Processing setCursor(cursor: number, pageChange?: boolean): boolean { const ret = super.setCursor(cursor); @@ -334,33 +361,35 @@ export default class AchvsUiHandler extends MessageUiHandler { let update = ret; if (!this.cursorObj) { - this.cursorObj = globalScene.add.nineslice(0, 0, "select_cursor_highlight", undefined, 16, 16, 1, 1, 1, 1); - this.cursorObj.setOrigin(0, 0); + this.cursorObj = globalScene.add + .nineslice(0, 0, "select_cursor_highlight", undefined, 16, 16, 1, 1, 1, 1) + .setOrigin(0); this.iconsContainer.add(this.cursorObj); update = true; } this.cursorObj.setPositionRelative(this.icons[this.cursor], 0, 0); + if (!update && !pageChange) { + return ret; + } - if (update || pageChange) { - switch (this.currentPage) { - case Page.ACHIEVEMENTS: - if (pageChange) { - this.titleBg.width = 174; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(true); - } - this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - if (pageChange) { - this.titleBg.width = 220; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(false); - } - this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); - break; - } + switch (this.currentPage) { + case Page.ACHIEVEMENTS: + if (pageChange) { + this.titleBg.width = 174; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(true); + } + this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + if (pageChange) { + this.titleBg.width = 220; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(false); + } + this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); + break; } return ret; } @@ -399,30 +428,50 @@ export default class AchvsUiHandler extends MessageUiHandler { } /** - * updateAchvIcons(): void - * Determines what data is to be displayed on the UI and updates it accordingly based on the current value of this.scrollCursor + * Updates the icons displayed on the UI based on the current page and scroll cursor. + * @param items - The items to display (achievements or vouchers). + * @param unlocks - The unlocks data for the items. + * @param getIconFrame - A function to determine the frame for each item. + * @param headerText - The text for the header. + * @param actionText - The text for the action button. + * @param totalItems - The total number of items. + * @param forAchievements - `True` when updating icons for the achievements page, `false` for the vouchers page. */ - updateAchvIcons(): void { - this.headerText.text = this.achvsName; - this.headerActionText.text = this.vouchersName; + private updateIcons( + items: T extends true ? Achv[] : Voucher[], + unlocks: T extends true ? AchvUnlocks : VoucherUnlocks, + headerText: string, + actionText: string, + totalItems: number, + forAchievements: T, + ): void { + // type ItemType = T extends true ? Achv : Voucher; + // type RangeType = ItemType[]; + this.headerText.text = headerText; + this.headerActionText.text = actionText; const textPosition = this.headerBgX - this.headerActionText.displayWidth - 8; this.headerActionText.setX(textPosition); this.headerActionButton.setX(textPosition - this.headerActionButton.displayWidth - 4); - const achvUnlocks = globalScene.gameData.achvUnlocks; - const itemOffset = this.scrollCursor * this.COLS; const itemLimit = this.ROWS * this.COLS; - const achvRange = Object.values(achvs).slice(itemOffset, itemLimit + itemOffset); + const itemRange = items.slice(itemOffset, itemLimit + itemOffset); - achvRange.forEach((achv: Achv, i: number) => { + itemRange.forEach((item: (typeof itemRange)[0], i: number) => { const icon = this.icons[i]; - const unlocked = achvUnlocks.hasOwnProperty(achv.id); - const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); - const tinted = !hidden && !unlocked; + const unlocked = unlocks.hasOwnProperty(item.id); + let tinted = !unlocked; + if (forAchievements) { + // Typescript cannot properly infer the type of `item` here, so we need to cast it + const achv = item as Achv; + const hidden = !unlocked && achv.secret && (!achv.parentId || !unlocks.hasOwnProperty(achv.parentId)); + tinted &&= !hidden; + icon.setFrame(!hidden ? achv.iconImage : "unknown"); + } else { + icon.setFrame(getVoucherTypeIcon((item as Voucher).voucherType)); + } - icon.setFrame(!hidden ? achv.iconImage : "unknown"); icon.setVisible(true); if (tinted) { icon.setTintFill(0); @@ -431,48 +480,39 @@ export default class AchvsUiHandler extends MessageUiHandler { } }); - if (achvRange.length < this.icons.length) { - this.icons.slice(achvRange.length).map(i => i.setVisible(false)); + if (itemRange.length < this.icons.length) { + this.icons.slice(itemRange.length).forEach(i => i.setVisible(false)); } - this.currentTotal = this.achvsTotal; + this.currentTotal = totalItems; } /** - * updateVoucherIcons(): void - * Determines what data is to be displayed on the UI and updates it accordingly based on the current value of this.scrollCursor + * Update the achievement icons displayed on the UI based on the current scroll cursor. + */ + updateAchvIcons(): void { + this.updateIcons( + Object.values(achvs), + globalScene.gameData.achvUnlocks, + this.achvsName, + this.vouchersName, + this.achvsTotal, + true, + ); + } + + /** + * Update the voucher icons displayed on the UI based on the current scroll cursor. */ updateVoucherIcons(): void { - this.headerText.text = this.vouchersName; - this.headerActionText.text = this.achvsName; - const textPosition = this.headerBgX - this.headerActionText.displayWidth - 8; - this.headerActionText.setX(textPosition); - this.headerActionButton.setX(textPosition - this.headerActionButton.displayWidth - 4); - - const voucherUnlocks = globalScene.gameData.voucherUnlocks; - - const itemOffset = this.scrollCursor * this.COLS; - const itemLimit = this.ROWS * this.COLS; - - const voucherRange = Object.values(vouchers).slice(itemOffset, itemLimit + itemOffset); - - voucherRange.forEach((voucher: Voucher, i: number) => { - const icon = this.icons[i]; - const unlocked = voucherUnlocks.hasOwnProperty(voucher.id); - - icon.setFrame(getVoucherTypeIcon(voucher.voucherType)); - icon.setVisible(true); - if (!unlocked) { - icon.setTintFill(0); - } else { - icon.clearTint(); - } - }); - - if (voucherRange.length < this.icons.length) { - this.icons.slice(voucherRange.length).map(i => i.setVisible(false)); - } - this.currentTotal = this.vouchersTotal; + this.updateIcons( + Object.values(vouchers), + globalScene.gameData.voucherUnlocks, + this.vouchersName, + this.achvsName, + this.vouchersTotal, + false, + ); } clear() { diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index fbfd4d2623b..0d67fdea624 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -5,7 +5,7 @@ import UiHandler from "./ui-handler"; import i18next from "i18next"; import { Button } from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import { globalScene } from "#app/global-scene"; import { TerastallizeAccessModifier } from "#app/modifier/modifier"; import { PokemonType } from "#enums/pokemon-type"; @@ -75,7 +75,7 @@ export default class CommandUiHandler extends UiHandler { let commandPhase: CommandPhase; const currentPhase = globalScene.getCurrentPhase(); - if (currentPhase instanceof CommandPhase) { + if (currentPhase?.is("CommandPhase")) { commandPhase = currentPhase; } else { commandPhase = globalScene.getStandbyPhase() as CommandPhase; diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 1bb7124d935..d6b1b630a05 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -40,6 +40,9 @@ export default class EggGachaUiHandler extends MessageUiHandler { private scale = 0.1666666667; + private legendaryExpiration = addTextObject(0, 0, "", TextStyle.WINDOW_ALT); + private playTimeTimer: Phaser.Time.TimerEvent | null; + constructor() { super(UiMode.EGG_GACHA); @@ -108,7 +111,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { let pokemonIconX = -20; let pokemonIconY = 6; - if (["de", "es-ES", "es-MX", "fr", "ko", "pt-BR"].includes(currentLanguage)) { + if (["de", "es-ES", "es-MX", "fr", "ko", "pt-BR", "ru"].includes(currentLanguage)) { gachaTextStyle = TextStyle.SMALLER_WINDOW_ALT; gachaX = 2; gachaY = 2; @@ -150,7 +153,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(pokemonIcon); break; case GachaType.MOVE: - if (["de", "es-ES", "fr", "pt-BR"].includes(currentLanguage)) { + if (["de", "es-ES", "fr", "pt-BR", "ru"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); } @@ -160,7 +163,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaUpLabel.setOrigin(0.5, 0); break; case GachaType.SHINY: - if (["de", "fr", "ko"].includes(currentLanguage)) { + if (["de", "fr", "ko", "ru"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); } @@ -198,6 +201,19 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.add(gachaContainer); + // Expiration timer for the legendary gacha + if (gachaType === GachaType.LEGENDARY) { + this.legendaryExpiration + .setText(this.getLegendaryGachaTimeLeft()) + .setFontSize("64px") + .setPositionRelative( + gacha, + gacha.width / 2 - this.legendaryExpiration.displayWidth / 2 + 0.3, + gacha.height / 2 + 12.5, + ); + gachaContainer.add(this.legendaryExpiration); + } + this.updateGachaInfo(g); }); @@ -206,7 +222,17 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaOptionsContainer = globalScene.add.container(globalScene.game.canvas.width / 6, 148); this.eggGachaContainer.add(this.eggGachaOptionsContainer); - this.eggGachaOptionSelectBg = addWindow(0, 0, 96, 16 + 576 * this.scale); + // Increase egg box width on certain languages + let eggGachaOptionSelectWidth = 0; + switch (i18next.resolvedLanguage) { + case "ru": + eggGachaOptionSelectWidth = 100; + break; + default: + eggGachaOptionSelectWidth = 96; + } + + this.eggGachaOptionSelectBg = addWindow(0, 0, eggGachaOptionSelectWidth, 16 + 576 * this.scale); this.eggGachaOptionSelectBg.setOrigin(1, 1); this.eggGachaOptionsContainer.add(this.eggGachaOptionSelectBg); @@ -348,6 +374,8 @@ export default class EggGachaUiHandler extends MessageUiHandler { handleTutorial(Tutorial.Egg_Gacha); + this.legendaryGachaTimer(); + return true; } @@ -836,9 +864,37 @@ export default class EggGachaUiHandler extends MessageUiHandler { return changed; } + legendaryGachaTimer(): void { + if (this.playTimeTimer) { + this.playTimeTimer.destroy(); + this.playTimeTimer = null; + } + this.playTimeTimer = globalScene.time.addEvent({ + loop: true, + delay: fixedInt(1000), + callback: () => { + this.legendaryExpiration.setText(this.getLegendaryGachaTimeLeft()); + }, + }); + } + + getLegendaryGachaTimeLeft(): string { + // 86400000 is the number of miliseconds in one day + const msUntilMidnight = 86400000 - (Date.now() % 86400000); + const hours = `${Math.floor(msUntilMidnight / 3600000)}`; + const minutes = `${Math.floor((msUntilMidnight % 3600000) / 60000)}`; + const seconds = `${Math.floor((msUntilMidnight % 60000) / 1000)}`; + + return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}:${seconds.padStart(2, "0")}`; + } + clear(): void { super.clear(); this.setGachaCursor(-1); this.eggGachaContainer.setVisible(false); + if (this.playTimeTimer) { + this.playTimeTimer.destroy(); + this.playTimeTimer = null; + } } } diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index 76e2c54f4b6..b2863a213f8 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -1,7 +1,6 @@ import { UiMode } from "#enums/ui-mode"; import UiHandler from "./ui-handler"; import { Button } from "#enums/buttons"; -import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { globalScene } from "#app/global-scene"; export default class EggHatchSceneHandler extends UiHandler { @@ -46,7 +45,7 @@ export default class EggHatchSceneHandler extends UiHandler { processInput(button: Button): boolean { if (button === Button.ACTION || button === Button.CANCEL) { const phase = globalScene.getCurrentPhase(); - if (phase instanceof EggHatchPhase && phase.trySkip()) { + if (phase?.is("EggHatchPhase") && phase.trySkip()) { return true; } } diff --git a/src/ui/egg-list-ui-handler.ts b/src/ui/egg-list-ui-handler.ts index 9f41feea8ab..9a1b1f51e25 100644 --- a/src/ui/egg-list-ui-handler.ts +++ b/src/ui/egg-list-ui-handler.ts @@ -35,81 +35,70 @@ export default class EggListUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.eggListContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); - this.eggListContainer.setVisible(false); + this.eggListContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6).setVisible(false); ui.add(this.eggListContainer); - const bgColor = globalScene.add.rectangle( - 0, - 0, - globalScene.game.canvas.width / 6, - globalScene.game.canvas.height / 6, - 0x006860, - ); - bgColor.setOrigin(0, 0); - this.eggListContainer.add(bgColor); + const bgColor = globalScene.add + .rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860) + .setOrigin(0); - const eggListBg = globalScene.add.image(0, 0, "egg_list_bg"); - eggListBg.setOrigin(0, 0); - this.eggListContainer.add(eggListBg); - - this.eggListContainer.add(addWindow(1, 85, 106, 22)); - this.eggListContainer.add(addWindow(1, 102, 106, 50, true)); - this.eggListContainer.add(addWindow(1, 147, 106, 32, true)); - this.eggListContainer.add(addWindow(107, 1, 212, 178)); + const eggListBg = globalScene.add.image(0, 0, "egg_list_bg").setOrigin(0); this.iconAnimHandler = new PokemonIconAnimHandler(); this.iconAnimHandler.setup(); - this.eggNameText = addTextObject(8, 68, "", TextStyle.SUMMARY); - this.eggNameText.setOrigin(0, 0); - this.eggListContainer.add(this.eggNameText); + this.eggNameText = addTextObject(8, 68, "", TextStyle.SUMMARY).setOrigin(0); this.eggDateText = addTextObject(8, 91, "", TextStyle.TOOLTIP_CONTENT); - this.eggListContainer.add(this.eggDateText); - this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.TOOLTIP_CONTENT); - this.eggHatchWavesText.setWordWrapWidth(540); - this.eggListContainer.add(this.eggHatchWavesText); + this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.TOOLTIP_CONTENT).setWordWrapWidth(540); - this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.TOOLTIP_CONTENT); - this.eggGachaInfoText.setWordWrapWidth(540); - this.eggListContainer.add(this.eggGachaInfoText); + this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.TOOLTIP_CONTENT).setWordWrapWidth(540); this.eggListIconContainer = globalScene.add.container(113, 5); - this.eggListContainer.add(this.eggListIconContainer); - this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); - this.cursorObj.setOrigin(0, 0); - this.eggListContainer.add(this.cursorObj); + this.cursorObj = globalScene.add.image(0, 0, "select_cursor").setOrigin(0); this.eggSprite = globalScene.add.sprite(54, 37, "egg"); - this.eggListContainer.add(this.eggSprite); const scrollBar = new ScrollBar(310, 5, 4, 170, this.ROWS); - this.eggListContainer.add(scrollBar); this.scrollGridHandler = new ScrollableGridUiHandler(this, this.ROWS, this.COLUMNS) .withScrollBar(scrollBar) .withUpdateGridCallBack(() => this.updateEggIcons()) .withUpdateSingleElementCallback((i: number) => this.setEggDetails(i)); - this.eggListMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); - this.eggListMessageBoxContainer.setVisible(false); - this.eggListContainer.add(this.eggListMessageBoxContainer); + this.eggListMessageBoxContainer = globalScene.add + .container(0, globalScene.game.canvas.height / 6) + .setVisible(false); - const eggListMessageBox = addWindow(1, -1, 318, 28); - eggListMessageBox.setOrigin(0, 1); + const eggListMessageBox = addWindow(1, -1, 318, 28).setOrigin(0, 1); this.eggListMessageBoxContainer.add(eggListMessageBox); - this.message = addTextObject(8, -8, "", TextStyle.WINDOW, { maxLines: 1 }); - this.message.setOrigin(0, 1); - this.eggListMessageBoxContainer.add(this.message); + // Message isn't used, but is expected to exist as this subclasses MessageUiHandler + this.message = addTextObject(8, -8, "", TextStyle.WINDOW, { maxLines: 1 }).setActive(false).setVisible(false); this.cursor = -1; + + this.eggListContainer.add([ + bgColor, + eggListBg, + addWindow(1, 85, 106, 22), + addWindow(1, 102, 106, 50, true), + addWindow(1, 147, 106, 32, true), + addWindow(107, 1, 212, 178), + this.eggNameText, + this.eggDateText, + this.eggHatchWavesText, + this.eggGachaInfoText, + this.eggListIconContainer, + this.cursorObj, + this.eggSprite, + scrollBar, + ]); } - show(args: any[]): boolean { + override show(args: any[]): boolean { super.show(args); this.initEggIcons(); @@ -134,9 +123,10 @@ export default class EggListUiHandler extends MessageUiHandler { for (let i = 0; i < Math.min(this.ROWS * this.COLUMNS, globalScene.gameData.eggs.length); i++) { const x = (i % this.COLUMNS) * 18; const y = Math.floor(i / this.COLUMNS) * 18; - const icon = globalScene.add.sprite(x - 2, y + 2, "egg_icons"); - icon.setScale(0.5); - icon.setOrigin(0, 0); + const icon = globalScene.add + .sprite(x - 2, y + 2, "egg_icons") + .setScale(0.5) + .setOrigin(0); this.eggListIconContainer.add(icon); this.eggIcons.push(icon); } @@ -148,15 +138,13 @@ export default class EggListUiHandler extends MessageUiHandler { private updateEggIcons() { const indexOffset = this.scrollGridHandler.getItemOffset(); const eggsToShow = Math.min(this.eggIcons.length, globalScene.gameData.eggs.length - indexOffset); - this.eggIcons.forEach((icon, i) => { if (i !== this.cursor) { this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE); } if (i < eggsToShow) { const egg = globalScene.gameData.eggs[i + indexOffset]; - icon.setFrame(egg.getKey()); - icon.setVisible(true); + icon.setFrame(egg.getKey()).setVisible(true); } else { icon.setVisible(false); } @@ -187,7 +175,6 @@ export default class EggListUiHandler extends MessageUiHandler { const ui = this.getUi(); let success = false; - const error = false; if (button === Button.CANCEL) { ui.revertMode(); @@ -198,11 +185,9 @@ export default class EggListUiHandler extends MessageUiHandler { if (success) { ui.playSelect(); - } else if (error) { - ui.playError(); } - return success || error; + return success; } setCursor(cursor: number): boolean { diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index ddc536fe1ad..ed2506ba0c1 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -4,7 +4,6 @@ import MessageUiHandler from "./message-ui-handler"; import { getEggTierForSpecies } from "../data/egg"; import { Button } from "#enums/buttons"; import PokemonHatchInfoContainer from "./pokemon-hatch-info-container"; -import { EggSummaryPhase } from "#app/phases/egg-summary-phase"; import type { EggHatchData } from "#app/data/egg-hatch-data"; import ScrollableGridUiHandler from "./scrollable-grid-handler"; import { HatchedPokemonContainer } from "./hatched-pokemon-container"; @@ -223,7 +222,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler { if (button === Button.CANCEL) { if (!this.blockExit) { const phase = globalScene.getCurrentPhase(); - if (phase instanceof EggSummaryPhase) { + if (phase?.is("EggSummaryPhase")) { phase.end(); } success = true; diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index cc684111617..b6c3a9d7b8e 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -15,7 +15,6 @@ import { Button } from "#enums/buttons"; import { GameDataType } from "#enums/game-data-type"; import BgmBar from "#app/ui/bgm-bar"; import type AwaitableUiHandler from "./awaitable-ui-handler"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { AdminMode, getAdminModeName } from "./admin-ui-handler"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; @@ -126,7 +125,7 @@ export default class MenuUiHandler extends MessageUiHandler { const ui = this.getUi(); this.excludedMenus = () => [ { - condition: globalScene.getCurrentPhase() instanceof SelectModifierPhase, + condition: !!globalScene.getCurrentPhase()?.is("SelectModifierPhase"), options: [MenuOptions.EGG_GACHA], }, { condition: bypassLogin, options: [MenuOptions.LOG_OUT] }, diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 452ffcf5192..e26ecb531e9 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -29,7 +29,6 @@ import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { getPokemonNameWithAffix } from "#app/messages"; import type { CommandPhase } from "#app/phases/command-phase"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { globalScene } from "#app/global-scene"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -751,7 +750,7 @@ export default class PartyUiHandler extends MessageUiHandler { // TODO: This risks hitting the other options (.MOVE_i and ALL) so does it? Do we need an extra check? if ( option >= PartyOption.FORM_CHANGE_ITEM && - globalScene.getCurrentPhase() instanceof SelectModifierPhase && + globalScene.getCurrentPhase()?.is("SelectModifierPhase") && this.partyUiMode === PartyUiMode.CHECK ) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); @@ -1338,7 +1337,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.addCommonOptions(pokemon); break; case PartyUiMode.CHECK: - if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { + if (globalScene.getCurrentPhase()?.is("SelectModifierPhase")) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); for (let i = 0; i < formChangeItemModifiers.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); diff --git a/src/ui/pokedex-page-ui-handler.ts b/src/ui/pokedex-page-ui-handler.ts index 263842bd4f9..00e166f075d 100644 --- a/src/ui/pokedex-page-ui-handler.ts +++ b/src/ui/pokedex-page-ui-handler.ts @@ -665,7 +665,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler { show(args: any[]): boolean { // Allow the use of candies if we are in one of the whitelisted phases this.canUseCandies = ["TitlePhase", "SelectStarterPhase", "CommandPhase"].includes( - globalScene.getCurrentPhase()?.constructor.name ?? "", + globalScene.getCurrentPhase()?.phaseName ?? "", ); if (args.length >= 1 && args[0] === "refresh") { diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 27ca95c25ac..1fb4b6d34dc 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -108,10 +108,12 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.reloadSettings = this.settings.filter(s => s?.requireReload); + let anyReloadRequired = false; this.settings.forEach((setting, s) => { let settingName = setting.label; if (setting?.requireReload) { - settingName += ` (${i18next.t("settings:requireReload")})`; + settingName += "*"; + anyReloadRequired = true; } this.settingLabels[s] = addTextObject(8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL); @@ -187,6 +189,14 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.settingsContainer.add(iconAction); this.settingsContainer.add(iconCancel); this.settingsContainer.add(actionText); + // Only add the ReloadRequired text on pages that have settings that require a reload. + if (anyReloadRequired) { + const reloadRequired = addTextObject(0, 0, `*${i18next.t("settings:requireReload")}`, TextStyle.SETTINGS_LABEL) + .setOrigin(0, 0.15) + .setPositionRelative(actionsBg, 6, 0) + .setY(actionText.y); + this.settingsContainer.add(reloadRequired); + } this.settingsContainer.add(cancelText); this.settingsContainer.add(this.messageBoxContainer); diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts index 0f226ddcafa..9ddb54131d5 100644 --- a/src/ui/settings/gamepad-binding-ui-handler.ts +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -4,6 +4,7 @@ import { Device } from "#enums/devices"; import { getIconWithSettingName, getKeyWithKeycode } from "#app/configs/inputs/configHandler"; import { addTextObject, TextStyle } from "#app/ui/text"; import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { constructor(mode: UiMode | null = null) { @@ -19,7 +20,7 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { this.newButtonIcon.setOrigin(0.5); this.newButtonIcon.setVisible(false); - this.swapText = addTextObject(0, 0, "will swap with", TextStyle.WINDOW); + this.swapText = addTextObject(0, 0, i18next.t("settings:willSwapWith"), TextStyle.WINDOW); this.swapText.setOrigin(0.5); this.swapText.setPositionRelative( this.optionSelectBg, @@ -33,7 +34,7 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { this.targetButtonIcon.setOrigin(0.5); this.targetButtonIcon.setVisible(false); - this.actionLabel = addTextObject(0, 0, "Confirm swap", TextStyle.SETTINGS_LABEL); + this.actionLabel = addTextObject(0, 0, i18next.t("settings:confirmSwap"), TextStyle.SETTINGS_LABEL); this.actionLabel.setOrigin(0, 0.5); this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 75, this.actionBg.height / 2); this.actionsContainer.add(this.actionLabel); diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts index c05a31ca91e..183b8269e78 100644 --- a/src/ui/settings/keyboard-binding-ui-handler.ts +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -4,6 +4,7 @@ import { getKeyWithKeycode } from "#app/configs/inputs/configHandler"; import { Device } from "#enums/devices"; import { addTextObject, TextStyle } from "#app/ui/text"; import { globalScene } from "#app/global-scene"; +import i18next from "i18next"; export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { constructor(mode: UiMode | null = null) { @@ -21,7 +22,7 @@ export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { this.newButtonIcon.setOrigin(0.5); this.newButtonIcon.setVisible(false); - this.actionLabel = addTextObject(0, 0, "Assign button", TextStyle.SETTINGS_LABEL); + this.actionLabel = addTextObject(0, 0, i18next.t("settings:assignButton"), TextStyle.SETTINGS_LABEL); this.actionLabel.setOrigin(0, 0.5); this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 80, this.actionBg.height / 2); this.actionsContainer.add(this.actionLabel); diff --git a/test/testUtils/phaseInterceptor.ts b/test/testUtils/phaseInterceptor.ts index b1d76ecd4a6..b7577550568 100644 --- a/test/testUtils/phaseInterceptor.ts +++ b/test/testUtils/phaseInterceptor.ts @@ -63,6 +63,8 @@ import { UnlockPhase } from "#app/phases/unlock-phase"; import { PostGameOverPhase } from "#app/phases/post-game-over-phase"; import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase"; +import type { PhaseClass, PhaseString } from "#app/@types/phase-types"; + export interface PromptHandler { phaseTarget?: string; mode?: UiMode; @@ -71,126 +73,6 @@ export interface PromptHandler { awaitingActionInput?: boolean; } -type PhaseClass = - | typeof LoginPhase - | typeof TitlePhase - | typeof SelectGenderPhase - | typeof NewBiomeEncounterPhase - | typeof SelectStarterPhase - | typeof PostSummonPhase - | typeof SummonPhase - | typeof ToggleDoublePositionPhase - | typeof CheckSwitchPhase - | typeof ShowAbilityPhase - | typeof MessagePhase - | typeof TurnInitPhase - | typeof CommandPhase - | typeof EnemyCommandPhase - | typeof TurnStartPhase - | typeof MovePhase - | typeof MoveEffectPhase - | typeof DamageAnimPhase - | typeof FaintPhase - | typeof BerryPhase - | typeof TurnEndPhase - | typeof BattleEndPhase - | typeof EggLapsePhase - | typeof SelectModifierPhase - | typeof NextEncounterPhase - | typeof NewBattlePhase - | typeof VictoryPhase - | typeof LearnMovePhase - | typeof MoveEndPhase - | typeof StatStageChangePhase - | typeof ShinySparklePhase - | typeof SelectTargetPhase - | typeof UnavailablePhase - | typeof QuietFormChangePhase - | typeof SwitchPhase - | typeof SwitchSummonPhase - | typeof PartyHealPhase - | typeof FormChangePhase - | typeof EvolutionPhase - | typeof EndEvolutionPhase - | typeof LevelCapPhase - | typeof AttemptRunPhase - | typeof SelectBiomePhase - | typeof MysteryEncounterPhase - | typeof MysteryEncounterOptionSelectedPhase - | typeof MysteryEncounterBattlePhase - | typeof MysteryEncounterRewardsPhase - | typeof PostMysteryEncounterPhase - | typeof RibbonModifierRewardPhase - | typeof GameOverModifierRewardPhase - | typeof ModifierRewardPhase - | typeof PartyExpPhase - | typeof ExpPhase - | typeof EncounterPhase - | typeof GameOverPhase - | typeof UnlockPhase - | typeof PostGameOverPhase - | typeof RevivalBlessingPhase; - -type PhaseString = - | "LoginPhase" - | "TitlePhase" - | "SelectGenderPhase" - | "NewBiomeEncounterPhase" - | "SelectStarterPhase" - | "PostSummonPhase" - | "SummonPhase" - | "ToggleDoublePositionPhase" - | "CheckSwitchPhase" - | "ShowAbilityPhase" - | "MessagePhase" - | "TurnInitPhase" - | "CommandPhase" - | "EnemyCommandPhase" - | "TurnStartPhase" - | "MovePhase" - | "MoveEffectPhase" - | "DamageAnimPhase" - | "FaintPhase" - | "BerryPhase" - | "TurnEndPhase" - | "BattleEndPhase" - | "EggLapsePhase" - | "SelectModifierPhase" - | "NextEncounterPhase" - | "NewBattlePhase" - | "VictoryPhase" - | "LearnMovePhase" - | "MoveEndPhase" - | "StatStageChangePhase" - | "ShinySparklePhase" - | "SelectTargetPhase" - | "UnavailablePhase" - | "QuietFormChangePhase" - | "SwitchPhase" - | "SwitchSummonPhase" - | "PartyHealPhase" - | "FormChangePhase" - | "EvolutionPhase" - | "EndEvolutionPhase" - | "LevelCapPhase" - | "AttemptRunPhase" - | "SelectBiomePhase" - | "MysteryEncounterPhase" - | "MysteryEncounterOptionSelectedPhase" - | "MysteryEncounterBattlePhase" - | "MysteryEncounterRewardsPhase" - | "PostMysteryEncounterPhase" - | "RibbonModifierRewardPhase" - | "GameOverModifierRewardPhase" - | "ModifierRewardPhase" - | "PartyExpPhase" - | "ExpPhase" - | "EncounterPhase" - | "GameOverPhase" - | "UnlockPhase" - | "PostGameOverPhase" - | "RevivalBlessingPhase"; - type PhaseInterceptorPhase = PhaseClass | PhaseString; export default class PhaseInterceptor {