From 54460405b1fb2eaca881a915a93c28b950f95e9f Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Sun, 18 Aug 2024 03:36:25 +0900 Subject: [PATCH 01/26] =?UTF-8?q?[Hotfix]=20Fixed=20the=20bug=20where=20Po?= =?UTF-8?q?k=C3=A9mon=20with=20only=20rare/epic=20shiny=20but=20no=20commo?= =?UTF-8?q?n=20shiny=20were=20unable=20to=20use=20cycle=20shiny=20(#3593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed the bug where Pokémon with only rare/epic shiny but no common shiny were unable to use cycle shiny * remove unecessary log * fix condition --- src/ui/starter-select-ui-handler.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 03a13e7661a..de56e69f65c 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2975,7 +2975,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female!, formIndex, shiny, variant)); currentFilteredContainer.checkIconId(female, formIndex, shiny, variant); } - this.canCycleShiny = !!(dexEntry.caughtAttr & DexAttr.NON_SHINY && dexEntry.caughtAttr & DexAttr.SHINY); + // First, ensure you have the caught attributes for the species else default to bigint 0 + const caughtVariants = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); + // Define the variables based on whether their respective variants have been caught + const isVariant3Caught = !!(caughtVariants & DexAttr.VARIANT_3); + const isVariant2Caught = !!(caughtVariants & DexAttr.VARIANT_2); + const isVariantCaught = !!(caughtVariants & DexAttr.SHINY); + + this.canCycleShiny = isVariantCaught || isVariant2Caught || isVariant3Caught; this.canCycleGender = !!(dexEntry.caughtAttr & DexAttr.MALE && dexEntry.caughtAttr & DexAttr.FEMALE); this.canCycleAbility = [ abilityAttr & AbilityAttr.ABILITY_1, (abilityAttr & AbilityAttr.ABILITY_2) && species.ability2, abilityAttr & AbilityAttr.ABILITY_HIDDEN ].filter(a => a).length > 1; this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey)) From 15584f8f1e5e61114f17a4da56cb6cfb34349f21 Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Sun, 18 Aug 2024 04:47:45 +0900 Subject: [PATCH 02/26] fix typecast to BigInt (#3598) --- src/system/game-data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index d64aa8f8e91..40f24fc8326 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1709,7 +1709,7 @@ export class GameData { } getFormAttr(formIndex: integer): bigint { - return BigInt(1 << (7 + formIndex)); + return BigInt(1) << BigInt(7 + formIndex); } consolidateDexData(dexData: DexData): void { From e192e57c630c9884255ae53738212dffb91eedf7 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 17 Aug 2024 15:48:16 -0400 Subject: [PATCH 03/26] [Balance] Balance Hotfixes for August 17 Update (#3594) * [Balance] Balance Hotfixes for August 17 Update * Eternatus Moveset fix * fixed Cresselia passive * fixed Jirachi Egg Moves * fixed Doduo and Arctozolt egg moves --- src/data/egg-moves.ts | 8 ++++---- src/data/pokemon-species.ts | 2 +- src/field/pokemon.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data/egg-moves.ts b/src/data/egg-moves.ts index 657f59bddce..f88ac2c71b2 100644 --- a/src/data/egg-moves.ts +++ b/src/data/egg-moves.ts @@ -37,7 +37,7 @@ export const speciesEggMoves = { [Species.SLOWPOKE]: [ Moves.BOUNCY_BUBBLE, Moves.FLAMETHROWER, Moves.MYSTICAL_POWER, Moves.SHED_TAIL ], [Species.MAGNEMITE]: [ Moves.PARABOLIC_CHARGE, Moves.BODY_PRESS, Moves.ICE_BEAM, Moves.THUNDERCLAP ], [Species.FARFETCHD]: [ Moves.IVY_CUDGEL, Moves.TRIPLE_ARROWS, Moves.ROOST, Moves.VICTORY_DANCE ], - [Species.DODUO]: [ Moves.ICE_SPINNER, Moves.MULTI_ATTACK, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], + [Species.DODUO]: [ Moves.TRIPLE_AXEL, Moves.MULTI_ATTACK, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], [Species.SEEL]: [ Moves.FREEZE_DRY, Moves.BOUNCY_BUBBLE, Moves.SLACK_OFF, Moves.STEAM_ERUPTION ], [Species.GRIMER]: [ Moves.SUCKER_PUNCH, Moves.CURSE, Moves.STRENGTH_SAP, Moves.NOXIOUS_TORQUE ], [Species.SHELLDER]: [ Moves.ROCK_BLAST, Moves.WATER_SHURIKEN, Moves.BANEFUL_BUNKER, Moves.BONE_RUSH ], @@ -198,7 +198,7 @@ export const speciesEggMoves = { [Species.KYOGRE]: [ Moves.BOUNCY_BUBBLE, Moves.HURRICANE, Moves.FREEZE_DRY, Moves.ELECTRO_SHOT ], [Species.GROUDON]: [ Moves.STONE_AXE, Moves.SOLAR_BLADE, Moves.MORNING_SUN, Moves.SACRED_FIRE ], [Species.RAYQUAZA]: [ Moves.V_CREATE, Moves.DRAGON_DARTS, Moves.CORE_ENFORCER, Moves.OBLIVION_WING ], - [Species.JIRACHI]: [ Moves.TACHYON_CUTTER, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS, Moves.SHELL_SMASH ], + [Species.JIRACHI]: [ Moves.TACHYON_CUTTER, Moves.TRIPLE_ARROWS, Moves.ROCK_SLIDE, Moves.SHELL_SMASH ], [Species.DEOXYS]: [ Moves.COLLISION_COURSE, Moves.EARTH_POWER, Moves.PARTING_SHOT, Moves.LUMINA_CRASH ], [Species.TURTWIG]: [ Moves.SHELL_SMASH, Moves.MIGHTY_CLEAVE, Moves.ICE_SPINNER, Moves.SAPPY_SEED ], [Species.CHIMCHAR]: [ Moves.FIERY_DANCE, Moves.SECRET_SWORD, Moves.TRIPLE_AXEL, Moves.SACRED_FIRE ], @@ -418,7 +418,7 @@ export const speciesEggMoves = { [Species.CELESTEELA]: [ Moves.RECOVER, Moves.BUZZY_BUZZ, Moves.SANDSEAR_STORM, Moves.OBLIVION_WING ], [Species.KARTANA]: [ Moves.MIGHTY_CLEAVE, Moves.PSYBLADE, Moves.BITTER_BLADE, Moves.BEHEMOTH_BLADE ], [Species.GUZZLORD]: [ Moves.SUCKER_PUNCH, Moves.COMEUPPANCE, Moves.SLACK_OFF, Moves.SHED_TAIL ], - [Species.NECROZMA]: [ Moves.CLANGOROUS_SOUL, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE, Moves.CLANGOROUS_SOUL ], + [Species.NECROZMA]: [ Moves.CLANGOROUS_SOUL, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE, Moves.DYNAMAX_CANNON ], [Species.MAGEARNA]: [ Moves.STRENGTH_SAP, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.MAKE_IT_RAIN ], [Species.MARSHADOW]: [ Moves.POWER_UP_PUNCH, Moves.TRIPLE_AXEL, Moves.METEOR_MASH, Moves.STORM_THROW ], [Species.POIPOLE]: [ Moves.CORE_ENFORCER, Moves.ICE_BEAM, Moves.SEARING_SHOT, Moves.MALIGNANT_CHAIN ], @@ -458,7 +458,7 @@ export const speciesEggMoves = { [Species.MORPEKO]: [ Moves.TRIPLE_AXEL, Moves.OBSTRUCT, Moves.SWORDS_DANCE, Moves.COLLISION_COURSE ], [Species.CUFANT]: [ Moves.LIQUIDATION, Moves.CURSE, Moves.COMBAT_TORQUE, Moves.GIGATON_HAMMER ], [Species.DRACOZOLT]: [ Moves.TRIPLE_AXEL, Moves.DRAGON_HAMMER, Moves.FIRE_LASH, Moves.DRAGON_DANCE ], - [Species.ARCTOZOLT]: [ Moves.TRIPLE_AXEL, Moves.AQUA_STEP, Moves.HIGH_HORSEPOWER, Moves.SHIFT_GEAR ], + [Species.ARCTOZOLT]: [ Moves.MOUNTAIN_GALE, Moves.AQUA_STEP, Moves.HIGH_HORSEPOWER, Moves.SHIFT_GEAR ], [Species.DRACOVISH]: [ Moves.TRIPLE_AXEL, Moves.DRAGON_HAMMER, Moves.THUNDER_FANG, Moves.DRAGON_DANCE ], [Species.ARCTOVISH]: [ Moves.ICE_FANG, Moves.THUNDER_FANG, Moves.HIGH_HORSEPOWER, Moves.SHIFT_GEAR ], [Species.DURALUDON]: [ Moves.CORE_ENFORCER, Moves.BODY_PRESS, Moves.RECOVER, Moves.TACHYON_CUTTER ], diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 762f009e4e4..aa2c29a9725 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -3559,7 +3559,7 @@ export const starterPassiveAbilities = { [Species.HEATRAN]: Abilities.EARTH_EATER, [Species.REGIGIGAS]: Abilities.MINDS_EYE, [Species.GIRATINA]: Abilities.SHADOW_SHIELD, - [Species.CRESSELIA]: Abilities.MAGIC_BOUNCE, + [Species.CRESSELIA]: Abilities.UNAWARE, [Species.PHIONE]: Abilities.SIMPLE, [Species.MANAPHY]: Abilities.PRIMORDIAL_SEA, [Species.DARKRAI]: Abilities.UNNERVE, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 930ffeb700f..4ab0b482916 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3780,7 +3780,7 @@ export class EnemyPokemon extends Pokemon { this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) ? [ new PokemonMove(Moves.DYNAMAX_CANNON), - new PokemonMove(Moves.CROSS_POISON), + new PokemonMove(Moves.SLUDGE_BOMB), new PokemonMove(Moves.FLAMETHROWER), new PokemonMove(Moves.RECOVER, 0, -4) ] From 616219d17eae93d390f7e3ea3ba5ace6fb6cae91 Mon Sep 17 00:00:00 2001 From: Mumble Date: Sat, 17 Aug 2024 12:50:50 -0700 Subject: [PATCH 04/26] [Hotfix] Removed isFreshStartChallenge() check (#3599) * Removed isFreshStartChallenge() check * Better conditional --------- Co-authored-by: Frutescens --- src/phases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases.ts b/src/phases.ts index 6a9e25b8b5e..da3096b0d2d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2036,7 +2036,7 @@ export class CommandPhase extends FieldPhase { } break; case Command.BALL: - if (!this.scene.gameMode.isFreshStartChallenge() && this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1))) { + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1))) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { From b59cb128bff17d4dfa6d26ec2f58adb1d35ea621 Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Sun, 18 Aug 2024 06:01:35 +0900 Subject: [PATCH 05/26] fix female bug. refine variable name also (#3601) --- src/ui/starter-select-ui-handler.ts | 32 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index de56e69f65c..5e942f3e75a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2295,13 +2295,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { container.cost = this.scene.gameData.getSpeciesStarterValue(container.species.speciesId); // First, ensure you have the caught attributes for the species else default to bigint 0 - const caughtVariants = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); + const isCaught = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); // Define the variables based on whether their respective variants have been caught - const isVariant3Caught = !!(caughtVariants & DexAttr.VARIANT_3); - const isVariant2Caught = !!(caughtVariants & DexAttr.VARIANT_2); - const isVariantCaught = !!(caughtVariants & DexAttr.SHINY); - const isCaught = !!(caughtVariants & DexAttr.NON_SHINY); + const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3); + const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2); + const isVariantCaught = !!(isCaught & DexAttr.SHINY); const isUncaught = !isCaught && !isVariantCaught && !isVariant2Caught && !isVariant3Caught; const isPassiveUnlocked = this.scene.gameData.starterData[container.species.speciesId].passiveAttr > 0; const isPassiveUnlockable = this.isPassiveAvailable(container.species.speciesId) && !isPassiveUnlocked; @@ -2913,6 +2912,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (species) { const dexEntry = this.scene.gameData.dexData[species.speciesId]; const abilityAttr = this.scene.gameData.starterData[species.speciesId].abilityAttr; + + const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); + const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3); + const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2); + const isVariantCaught = !!(isCaught & DexAttr.SHINY); + const isMaleCaught = !!(isCaught & DexAttr.MALE); + const isFemaleCaught = !!(isCaught & DexAttr.FEMALE); + if (!dexEntry.caughtAttr) { const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); @@ -2975,15 +2982,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female!, formIndex, shiny, variant)); currentFilteredContainer.checkIconId(female, formIndex, shiny, variant); } - // First, ensure you have the caught attributes for the species else default to bigint 0 - const caughtVariants = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); - // Define the variables based on whether their respective variants have been caught - const isVariant3Caught = !!(caughtVariants & DexAttr.VARIANT_3); - const isVariant2Caught = !!(caughtVariants & DexAttr.VARIANT_2); - const isVariantCaught = !!(caughtVariants & DexAttr.SHINY); this.canCycleShiny = isVariantCaught || isVariant2Caught || isVariant3Caught; - this.canCycleGender = !!(dexEntry.caughtAttr & DexAttr.MALE && dexEntry.caughtAttr & DexAttr.FEMALE); + this.canCycleGender = isMaleCaught && isFemaleCaught; this.canCycleAbility = [ abilityAttr & AbilityAttr.ABILITY_1, (abilityAttr & AbilityAttr.ABILITY_2) && species.ability2, abilityAttr & AbilityAttr.ABILITY_HIDDEN ].filter(a => a).length > 1; this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey)) .map((_, f) => dexEntry.caughtAttr & this.scene.gameData.getFormAttr(f)).filter(f => f).length > 1; @@ -2992,7 +2993,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } if (dexEntry.caughtAttr && species.malePercent !== null) { - const gender = !female ? Gender.MALE : Gender.FEMALE; + let gender: Gender; + if ((female && isFemaleCaught) || (!female && !isMaleCaught)) { + gender = Gender.FEMALE; + } else { + gender = Gender.MALE; + } this.pokemonGenderText.setText(getGenderSymbol(gender)); this.pokemonGenderText.setColor(getGenderColor(gender)); this.pokemonGenderText.setShadowColor(getGenderColor(gender, true)); From 2b853bae2588daa6d1bf52b11fb665d38b521663 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sat, 17 Aug 2024 15:25:03 -0700 Subject: [PATCH 06/26] [Hotfix] Fix Pokemon info not fully appearing after switch-out (#3596) * Add hideInfo param to leaveField * Update docs for leaveField --- src/field/pokemon.ts | 8 ++++++-- src/phases.ts | 6 ++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 4ab0b482916..b19fe7ce678 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3223,14 +3223,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Causes a Pokemon to leave the field (such as in preparation for a switch out/escape). * @param clearEffects Indicates if effects should be cleared (true) or passed * to the next pokemon, such as during a baton pass (false) + * @param hideInfo Indicates if this should also play the animation to hide the Pokemon's + * info container. */ - leaveField(clearEffects: boolean = true) { + leaveField(clearEffects: boolean = true, hideInfo: boolean = true) { this.resetTurnData(); if (clearEffects) { this.resetSummonData(); this.resetBattleData(); } - this.hideInfo(); + if (hideInfo) { + this.hideInfo(); + } this.setVisible(false); this.scene.field.remove(this); this.scene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); diff --git a/src/phases.ts b/src/phases.ts index da3096b0d2d..b881f0de819 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1635,7 +1635,7 @@ export class SwitchSummonPhase extends SummonPhase { }) ); this.scene.playSound("pb_rel"); - pokemon.hideInfo(); // this is also done by pokemon.leaveField(), but needs to go earlier for animation purposes + pokemon.hideInfo(); pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); this.scene.tweens.add({ targets: pokemon, @@ -1643,9 +1643,7 @@ export class SwitchSummonPhase extends SummonPhase { ease: "Sine.easeIn", scale: 0.5, onComplete: () => { - // 300ms delay on leaveField is necessary to avoid calling hideInfo() twice - // and double-animating the stats panel slideout - this.scene.time.delayedCall(300, () => pokemon.leaveField(!this.batonPass)); + pokemon.leaveField(!this.batonPass, false); this.scene.time.delayedCall(750, () => this.switchAndSummon()); } }); From 5ede6a54c630512f0e4ab7fa9f549412fd4b3e0a Mon Sep 17 00:00:00 2001 From: Mumble Date: Sat, 17 Aug 2024 17:09:28 -0700 Subject: [PATCH 07/26] [Hotfix] End Biome Catch Problems (#3605) * Needs more testing. * removed debugging --------- Co-authored-by: Frutescens --- src/game-mode.ts | 2 +- src/phases.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/game-mode.ts b/src/game-mode.ts index e78b9017c12..f5dadad6f1b 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -62,7 +62,7 @@ export class GameMode implements GameModeConfig { * @returns true if the game mode has that challenge */ hasChallenge(challenge: Challenges): boolean { - return this.challenges.some(c => c.id === challenge); + return this.challenges.some(c => c.id === challenge && c.value !== 0); } /** diff --git a/src/phases.ts b/src/phases.ts index b881f0de819..88acfc825ef 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2034,7 +2034,8 @@ export class CommandPhase extends FieldPhase { } break; case Command.BALL: - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1))) { + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1); + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { From 8704723c9cc7f98121c61753588d4866642c793c Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:22:21 -0700 Subject: [PATCH 08/26] Fix missing form change logic for Cramorant (#3603) --- src/data/pokemon-forms.ts | 2 ++ src/test/abilities/gulp_missile.test.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 5180664ce07..95a89c7c640 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -837,6 +837,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.CRAMORANT, "", "gorging", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() < .5)), new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeManualTrigger, true), new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeManualTrigger, true), + new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeActiveTrigger(false), true), + new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeActiveTrigger(false), true), ] }; diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 2647f765f6e..52ae323839d 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -84,6 +84,21 @@ describe("Abilities - Gulp Missile", () => { expect(cramorant.formIndex).toBe(GORGING_FORM); }); + it("changes to base form when switched out after Surf or Dive is used", async () => { + await game.startBattle([Species.CRAMORANT, Species.MAGIKARP]); + const cramorant = game.scene.getPlayerPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.SURF)); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); // form change is delayed until after end of turn + + expect(cramorant.formIndex).toBe(NORMAL_FORM); + expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined(); + expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_PIKACHU)).toBeUndefined(); + }); + it("changes form during Dive's charge turn", async () => { await game.startBattle([Species.CRAMORANT]); const cramorant = game.scene.getPlayerPokemon()!; From 0e92366cacc0e4d71313cfa6d1508d80dffefc05 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Sat, 17 Aug 2024 21:23:02 -0400 Subject: [PATCH 09/26] Fixed egg moves being relearnable in daily runs (#3604) --- src/field/pokemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b19fe7ce678..f1721299ad0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -922,7 +922,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getLearnableLevelMoves(): Moves[] { let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]); - if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge()) { + if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } return levelMoves.filter(lm => !this.moveset.some(m => m?.moveId === lm)); From 5f6cb6ce007acfe0c8e255982106182089fd9109 Mon Sep 17 00:00:00 2001 From: cam Date: Sat, 17 Aug 2024 23:14:25 -0400 Subject: [PATCH 10/26] [Sprite] Floette animation fix - Xatu female variant fix (#3610) * 177-178 icons, variant icons * 178 icons * [HotFix][Sprite] Xatu female variants added * [HotFix][Sprite] Floette json: update to match images * Xatu variant- reverted _masterlist reverted _masterlist.json added anim files for both female variants * [fix] Xatu female: show variants, added back sprite anim Edited _masterlist on correct keys added anim json for back sprites --- public/images/pokemon/back/female/178.png | Bin 5917 -> 6304 bytes .../images/pokemon/back/shiny/female/178.png | Bin 5917 -> 6304 bytes public/images/pokemon/exp/670-blue.json | 2534 +++++++++++++- public/images/pokemon/exp/670-orange.json | 2534 +++++++++++++- public/images/pokemon/exp/670-red.json | 3 +- public/images/pokemon/exp/670-white.json | 2534 +++++++++++++- public/images/pokemon/exp/670-yellow.json | 2534 +++++++++++++- public/images/pokemon/exp/shiny/670-blue.json | 2578 +++++++++++++- .../images/pokemon/exp/shiny/670-orange.json | 2578 +++++++++++++- public/images/pokemon/exp/shiny/670-red.json | 3097 ++++++++--------- .../images/pokemon/exp/shiny/670-white.json | 2578 +++++++++++++- .../images/pokemon/exp/shiny/670-yellow.json | 2578 +++++++++++++- public/images/pokemon/female/178.png | Bin 5100 -> 5314 bytes public/images/pokemon/icons/2/178.png | Bin 326 -> 318 bytes public/images/pokemon/icons/2/178s.png | Bin 326 -> 318 bytes .../images/pokemon/icons/variant/2/177_2.png | Bin 0 -> 271 bytes .../images/pokemon/icons/variant/2/177_3.png | Bin 0 -> 271 bytes .../images/pokemon/icons/variant/2/178_2.png | Bin 0 -> 318 bytes .../images/pokemon/icons/variant/2/178_3.png | Bin 0 -> 318 bytes public/images/pokemon/shiny/female/178.png | Bin 5100 -> 5314 bytes .../images/pokemon/variant/_masterlist.json | 10 + public/images/pokemon/variant/back/177_2.png | Bin 0 -> 7262 bytes public/images/pokemon/variant/back/177_3.png | Bin 0 -> 7254 bytes .../pokemon/variant/back/female/178_2.json | 2372 +++++++++++++ .../pokemon/variant/back/female/178_2.png | Bin 0 -> 6304 bytes .../pokemon/variant/back/female/178_3.json | 2372 +++++++++++++ .../pokemon/variant/back/female/178_3.png | Bin 0 -> 6304 bytes .../images/pokemon/variant/female/178_2.json | 2372 +++++++++++++ .../images/pokemon/variant/female/178_2.png | Bin 0 -> 5314 bytes .../images/pokemon/variant/female/178_3.json | 2372 +++++++++++++ .../images/pokemon/variant/female/178_3.png | Bin 0 -> 5314 bytes 31 files changed, 30255 insertions(+), 2791 deletions(-) create mode 100644 public/images/pokemon/icons/variant/2/177_2.png create mode 100644 public/images/pokemon/icons/variant/2/177_3.png create mode 100644 public/images/pokemon/icons/variant/2/178_2.png create mode 100644 public/images/pokemon/icons/variant/2/178_3.png create mode 100644 public/images/pokemon/variant/back/177_2.png create mode 100644 public/images/pokemon/variant/back/177_3.png create mode 100644 public/images/pokemon/variant/back/female/178_2.json create mode 100644 public/images/pokemon/variant/back/female/178_2.png create mode 100644 public/images/pokemon/variant/back/female/178_3.json create mode 100644 public/images/pokemon/variant/back/female/178_3.png create mode 100644 public/images/pokemon/variant/female/178_2.json create mode 100644 public/images/pokemon/variant/female/178_2.png create mode 100644 public/images/pokemon/variant/female/178_3.json create mode 100644 public/images/pokemon/variant/female/178_3.png diff --git a/public/images/pokemon/back/female/178.png b/public/images/pokemon/back/female/178.png index b6b1aa908d8f6ef64327b80864962739e9f7d3f5..bf4e727088fcc17984ff76199d9b510b011ce30b 100644 GIT binary patch literal 6304 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 Ud#aWikpKVy07*qoM6N<$f;G=33;+NC delta 5093 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 Hu0mjfwJXZ) diff --git a/public/images/pokemon/icons/2/178.png b/public/images/pokemon/icons/2/178.png index 3b6046319572e2316cd72e838de6982730796277..9f4b97ffc1cda1217f3ce7d9be632382f88cbb13 100644 GIT binary patch delta 47 qcmX@cw2x_mA)~=WBNGLw`+iys3=9G#L4Lsu|53q#fNtlBeLDc$)fpfF delta 55 scmdnTbc|_&A*0zuBNGh@~ diff --git a/public/images/pokemon/icons/2/178s.png b/public/images/pokemon/icons/2/178s.png index 8605b01c01e682c131330bdd3a2b7130628a1b06..55edd478de2d94d38076169e01abaa14dda09fd0 100644 GIT binary patch delta 47 rcmX@cw2x_mA)~=WBNK&xmM0%GFfa&|1o;Is{6_@`0=k_i_U!-w4F4LE delta 55 scmdnTbc|_&A*0zuBNGh443CQwF0h1`0x)O>%1>%~n_TxuWBwc%yf z-AOB+Mz*izW88Q|M8#Of^U0ni-iqAQ9_3}2oj7x8%Z|mj4;)grTDeQKaK)nJ1J6QR zR+-&6?{k)`N=RYe#_)po(UW2gqkg^kxJEkMB*`r0^uCGQm!p}AUNFb&03E{M>FVdQ I&MBb@04C>fT>t<8 literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/2/177_3.png b/public/images/pokemon/icons/variant/2/177_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2952d8f4926778a4415008a65c5c5b3abf33e9d0 GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a?}HSLR^8g zf`WpQW20yB@uY<>ny&mwQ+AkT;b@^#5$ZUtJ$-*rP|*MX|2HSZo(3xAEeY}qW&rZx zK$pdFEl{G?)5S5wqWA4ZPrd^R9IOHM4;)_qJs-WLkXvt-n(wc0y|@XAOYP&iHoVNb zJ88w!$o92-j2n-Ls2Iz5KH0OxTakO(qr42W6K5`M*|GTcfkWz6D|d+&u2__O;8|$P zDzh8sea>=K2`S9m7+&x`dQz-m)UOvG*GPw(B$=h0-Zzo^ax_!X3+8wophFluUHx3v IIVCg!08f2x00000 literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/2/178_2.png b/public/images/pokemon/icons/variant/2/178_2.png new file mode 100644 index 0000000000000000000000000000000000000000..060c25138e0bda912df694bb03849639b5210bfb GIT binary patch literal 318 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4ats1|LR^8g zf`UR&P*947ahIpl@sjBO|NkqyM|kAcn5L!p&S+cu{p6Blrvsf~A9fV|f6@0?QW9jI zKuM5aFauC2GB^;>?F^J#;OXKRV$u8dvbPYEAqUF^6SXdh8Ncse^GJZqllPbDt*v|NlQu#IQSOBG=pt!5xoEcK;VtT5uew zN1!CgFPH(S6d4={=ynFmE%0=446*2ad)Zrv$&iEPf{9v}#EjqfuklR_U3X|lf%@xk z-Y1uD?cb-#@I1n0&Vmn*7kmj)5^`e=zaK6t^v*@ewC2VWt#Hk@CB~g6&n|6XJIN`# z-D%sDGpkju3!da$7|qEme^_0)RQuv`zEeGSJieTc$h)gpVyu?bjn%A#dSdRNwJFf5M)QKM(e=SC`wt+ETzN(*|@agQu&X%Q~loCIIwU Bgm?e| literal 0 HcmV?d00001 diff --git a/public/images/pokemon/shiny/female/178.png b/public/images/pokemon/shiny/female/178.png index 7695b06e1558fe1ba4aa6d1ae63fa69e7a20470a..b0d983307e4cb741cf9a420e0373bf1de2a58c41 100644 GIT binary patch literal 5314 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 Ul*sRr7ytkO07*qoM6N<$f~8X?sQ>@~ literal 5100 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 Mq^0;t;W_eu0AYLGw*UYD literal 0 HcmV?d00001 diff --git a/public/images/pokemon/variant/back/177_3.png b/public/images/pokemon/variant/back/177_3.png new file mode 100644 index 0000000000000000000000000000000000000000..04ec33986cd511dee258fd7c72693912ae801081 GIT binary patch literal 7254 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 C^TLk+ literal 0 HcmV?d00001 diff --git a/public/images/pokemon/variant/back/female/178_2.json b/public/images/pokemon/variant/back/female/178_2.json new file mode 100644 index 00000000000..202894b474c --- /dev/null +++ b/public/images/pokemon/variant/back/female/178_2.json @@ -0,0 +1,2372 @@ +{ + "textures": [ + { + "image": "178_2.png", + "format": "RGBA8888", + "size": { + "w": 263, + "h": 263 + }, + "scale": 1, + "frames": [ + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 67, + "h": 58 + }, + "frame": { + "x": 0, + "y": 0, + "w": 67, + "h": 58 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 67, + "h": 58 + }, + "frame": { + "x": 0, + "y": 0, + "w": 67, + "h": 58 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 64, + "h": 58 + }, + "frame": { + "x": 0, + "y": 58, + "w": 64, + "h": 58 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 64, + "h": 58 + }, + "frame": { + "x": 0, + "y": 58, + "w": 64, + "h": 58 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 58, + "h": 57 + }, + "frame": { + "x": 67, + "y": 0, + "w": 58, + "h": 57 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 58, + "h": 57 + }, + "frame": { + "x": 67, + "y": 0, + "w": 58, + "h": 57 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 8, + "y": 5, + "w": 59, + "h": 54 + }, + "frame": { + "x": 0, + "y": 116, + "w": 59, + "h": 54 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 8, + "y": 5, + "w": 59, + "h": 54 + }, + "frame": { + "x": 0, + "y": 116, + "w": 59, + "h": 54 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 7, + "y": 2, + "w": 55, + "h": 57 + }, + "frame": { + "x": 125, + "y": 0, + "w": 55, + "h": 57 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 7, + "y": 2, + "w": 55, + "h": 57 + }, + "frame": { + "x": 125, + "y": 0, + "w": 55, + "h": 57 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 54, + "h": 57 + }, + "frame": { + "x": 0, + "y": 170, + "w": 54, + "h": 57 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 54, + "h": 57 + }, + "frame": { + "x": 0, + "y": 170, + "w": 54, + "h": 57 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 54, + "h": 52 + }, + "frame": { + "x": 180, + "y": 0, + "w": 54, + "h": 52 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 54, + "h": 52 + }, + "frame": { + "x": 180, + "y": 0, + "w": 54, + "h": 52 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 3, + "w": 48, + "h": 56 + }, + "frame": { + "x": 54, + "y": 170, + "w": 48, + "h": 56 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 3, + "w": 48, + "h": 56 + }, + "frame": { + "x": 54, + "y": 170, + "w": 48, + "h": 56 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 5, + "w": 48, + "h": 54 + }, + "frame": { + "x": 59, + "y": 116, + "w": 48, + "h": 54 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 5, + "w": 48, + "h": 54 + }, + "frame": { + "x": 59, + "y": 116, + "w": 48, + "h": 54 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 102, + "y": 170, + "w": 48, + "h": 52 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 52, + "h": 52 + }, + "frame": { + "x": 180, + "y": 52, + "w": 52, + "h": 52 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 50, + "h": 52 + }, + "frame": { + "x": 64, + "y": 58, + "w": 50, + "h": 52 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 9, + "y": 7, + "w": 51, + "h": 52 + }, + "frame": { + "x": 114, + "y": 57, + "w": 51, + "h": 52 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 11, + "y": 7, + "w": 47, + "h": 52 + }, + "frame": { + "x": 107, + "y": 110, + "w": 47, + "h": 52 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 154, + "y": 109, + "w": 45, + "h": 52 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 13, + "y": 7, + "w": 45, + "h": 52 + }, + "frame": { + "x": 199, + "y": 104, + "w": 45, + "h": 52 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 199, + "y": 156, + "w": 48, + "h": 52 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 7, + "w": 48, + "h": 52 + }, + "frame": { + "x": 199, + "y": 156, + "w": 48, + "h": 52 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 48, + "h": 51 + }, + "frame": { + "x": 150, + "y": 162, + "w": 48, + "h": 51 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 48, + "h": 51 + }, + "frame": { + "x": 150, + "y": 162, + "w": 48, + "h": 51 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 9, + "w": 48, + "h": 50 + }, + "frame": { + "x": 150, + "y": 213, + "w": 48, + "h": 50 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 9, + "w": 48, + "h": 50 + }, + "frame": { + "x": 150, + "y": 213, + "w": 48, + "h": 50 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 9, + "w": 48, + "h": 50 + }, + "frame": { + "x": 150, + "y": 213, + "w": 48, + "h": 50 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 9, + "w": 48, + "h": 50 + }, + "frame": { + "x": 150, + "y": 213, + "w": 48, + "h": 50 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 50, + "h": 51 + }, + "frame": { + "x": 198, + "y": 208, + "w": 50, + "h": 51 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 67, + "h": 59 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 50, + "h": 51 + }, + "frame": { + "x": 198, + "y": 208, + "w": 50, + "h": 51 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:09a3b8263891ad99a615fcb08d56ef56:420667b66547b2d5cc8ddbc8c794dd00:319c95b9f5acf1139a5c6761349cd6ab$" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/178_2.png b/public/images/pokemon/variant/back/female/178_2.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2785dc0af5cd7e1b0ac3b16350f2b860b66be1 GIT binary patch literal 6304 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 UIDq;fjQ{`u07*qoM6N<$f_9f6`v3p{ literal 0 HcmV?d00001 diff --git a/public/images/pokemon/variant/female/178_3.json b/public/images/pokemon/variant/female/178_3.json new file mode 100644 index 00000000000..2e851f20d9d --- /dev/null +++ b/public/images/pokemon/variant/female/178_3.json @@ -0,0 +1,2372 @@ +{ + "textures": [ + { + "image": "178_3.png", + "format": "RGBA8888", + "size": { + "w": 224, + "h": 224 + }, + "scale": 1, + "frames": [ + { + "filename": "0101.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 45, + "h": 60 + }, + "frame": { + "x": 0, + "y": 0, + "w": 45, + "h": 60 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 45, + "h": 60 + }, + "frame": { + "x": 0, + "y": 0, + "w": 45, + "h": 60 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 43, + "h": 60 + }, + "frame": { + "x": 45, + "y": 0, + "w": 43, + "h": 60 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 43, + "h": 60 + }, + "frame": { + "x": 45, + "y": 0, + "w": 43, + "h": 60 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 43, + "h": 60 + }, + "frame": { + "x": 88, + "y": 0, + "w": 43, + "h": 60 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 43, + "h": 60 + }, + "frame": { + "x": 88, + "y": 0, + "w": 43, + "h": 60 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 41, + "h": 59 + }, + "frame": { + "x": 131, + "y": 0, + "w": 41, + "h": 59 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 41, + "h": 59 + }, + "frame": { + "x": 131, + "y": 0, + "w": 41, + "h": 59 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 39, + "h": 59 + }, + "frame": { + "x": 131, + "y": 59, + "w": 39, + "h": 59 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 39, + "h": 59 + }, + "frame": { + "x": 131, + "y": 59, + "w": 39, + "h": 59 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 37, + "h": 59 + }, + "frame": { + "x": 0, + "y": 60, + "w": 37, + "h": 59 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 37, + "h": 59 + }, + "frame": { + "x": 0, + "y": 60, + "w": 37, + "h": 59 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 40, + "h": 57 + }, + "frame": { + "x": 37, + "y": 60, + "w": 40, + "h": 57 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 0, + "y": 3, + "w": 40, + "h": 57 + }, + "frame": { + "x": 37, + "y": 60, + "w": 40, + "h": 57 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 6, + "w": 40, + "h": 54 + }, + "frame": { + "x": 37, + "y": 117, + "w": 40, + "h": 54 + } + }, + { + "filename": "0111.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 36, + "h": 57 + }, + "frame": { + "x": 0, + "y": 119, + "w": 36, + "h": 57 + } + }, + { + "filename": "0112.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 36, + "h": 57 + }, + "frame": { + "x": 0, + "y": 119, + "w": 36, + "h": 57 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 60, + "w": 37, + "h": 55 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 115, + "w": 37, + "h": 55 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 1, + "y": 5, + "w": 37, + "h": 55 + }, + "frame": { + "x": 77, + "y": 115, + "w": 37, + "h": 55 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 38, + "h": 54 + }, + "frame": { + "x": 114, + "y": 118, + "w": 38, + "h": 54 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 36, + "h": 54 + }, + "frame": { + "x": 77, + "y": 170, + "w": 36, + "h": 54 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 36, + "h": 54 + }, + "frame": { + "x": 77, + "y": 170, + "w": 36, + "h": 54 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 36, + "h": 53 + }, + "frame": { + "x": 36, + "y": 171, + "w": 36, + "h": 53 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 36, + "h": 53 + }, + "frame": { + "x": 36, + "y": 171, + "w": 36, + "h": 53 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 36, + "h": 53 + }, + "frame": { + "x": 36, + "y": 171, + "w": 36, + "h": 53 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 7, + "w": 36, + "h": 53 + }, + "frame": { + "x": 36, + "y": 171, + "w": 36, + "h": 53 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 152, + "y": 118, + "w": 36, + "h": 55 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 36, + "h": 55 + }, + "frame": { + "x": 170, + "y": 59, + "w": 36, + "h": 55 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 36, + "h": 54 + }, + "frame": { + "x": 172, + "y": 0, + "w": 36, + "h": 54 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 6, + "w": 36, + "h": 54 + }, + "frame": { + "x": 172, + "y": 0, + "w": 36, + "h": 54 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 35, + "h": 55 + }, + "frame": { + "x": 188, + "y": 114, + "w": 35, + "h": 55 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 45, + "h": 60 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 34, + "h": 55 + }, + "frame": { + "x": 188, + "y": 169, + "w": 34, + "h": 55 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:898a0175e268e2bf429019b4b00a4414:3e0d176fabaa6df9ef39756046ad4ad5:319c95b9f5acf1139a5c6761349cd6ab$" + } +} diff --git a/public/images/pokemon/variant/female/178_3.png b/public/images/pokemon/variant/female/178_3.png new file mode 100644 index 0000000000000000000000000000000000000000..0ca5fe14c721f2d80087c6c56b882f929c87b832 GIT binary patch literal 5314 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 UIDq;fjQ{`u07*qoM6N<$g8t_q-2eap literal 0 HcmV?d00001 From abced6cf0261a5daba718f55296cff219b7d1a53 Mon Sep 17 00:00:00 2001 From: cam Date: Sat, 17 Aug 2024 23:17:21 -0400 Subject: [PATCH 11/26] [Sprite] Lumineon female spritesheet fix (#3608) fix from Vari --- public/images/pokemon/back/female/457.png | Bin 23501 -> 23970 bytes public/images/pokemon/female/457.png | Bin 20837 -> 21579 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/pokemon/back/female/457.png b/public/images/pokemon/back/female/457.png index 852684481293db6901e75b6e3068e58424a2b6af..04e1ae12ff4b23faa3154330b8eb199b4fdd2d33 100644 GIT binary patch literal 23970 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⧀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 Y-Gb9kWy-z-ias1a~ABLIK^@$X4S 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 z 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 osHo^Q59sX3$jBn>|Mk)%$tF Date: Sat, 17 Aug 2024 20:28:06 -0700 Subject: [PATCH 12/26] [Bug][Hotfix] Final Boss MBH no longer transferrable (#3611) * Should fix it. * typedocs fixes --------- Co-authored-by: Frutescens --- src/battle-scene.ts | 6 ++++-- src/modifier/modifier.ts | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index b2e5635b642..a8edf7b3af0 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -5,7 +5,7 @@ import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species"; import { Constructor } from "#app/utils"; import * as Utils from "./utils"; -import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier"; +import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, TurnHeldItemTransferModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems } from "./modifier/modifier"; import { PokeballType } from "./data/pokeball"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims"; import { Phase } from "./phase"; @@ -2666,7 +2666,9 @@ export default class BattleScene extends SceneBase { if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { this.fadeOutBgm(Utils.fixedInt(2000), false); this.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.species.name, undefined, () => { - this.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true); + const finalBossMBH = getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as TurnHeldItemTransferModifier; + finalBossMBH.setTransferrableFalse(); + this.addEnemyModifier(finalBossMBH, false, true); pokemon.generateAndPopulateMoveset(1); this.setFieldScale(0.75); this.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index a32f3c019f4..1dff041a14e 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2338,7 +2338,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * @see {@linkcode modifierTypes[MINI_BLACK_HOLE]} */ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { - readonly isTransferrable: boolean = true; + isTransferrable: boolean = true; constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) { super(type, pokemonId, stackCount); } @@ -2362,6 +2362,10 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { getMaxHeldItemCount(pokemon: Pokemon): integer { return 1; } + + setTransferrableFalse(): void { + this.isTransferrable = false; + } } /** From 0e6c2952ca4e68fa25230b5bfb0ffaba57068b07 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 17 Aug 2024 21:05:04 -0700 Subject: [PATCH 13/26] Make Disguise properly reset form on arena reset when fainted (#3612) --- src/battle-scene.ts | 4 +- src/data/ability.ts | 2 + src/test/abilities/disguise.test.ts | 66 ++++++++++++++++++++++++----- src/test/utils/phaseInterceptor.ts | 2 + 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a8edf7b3af0..674b4e256f9 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -37,7 +37,7 @@ import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "./ui/ui-theme"; import PokemonData from "./system/pokemon-data"; import { Nature } from "./data/nature"; -import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem } from "./data/pokemon-forms"; +import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem, SpeciesFormChange } from "./data/pokemon-forms"; import { FormChangePhase, QuietFormChangePhase } from "./form-change-phase"; import { getTypeRgb } from "./data/type"; import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler"; @@ -2579,7 +2579,7 @@ export default class BattleScene extends SceneBase { // in case this is NECROZMA, determine which forms this const matchingFormChangeOpts = pokemonFormChanges[pokemon.species.speciesId].filter(fc => fc.findTrigger(formChangeTriggerType) && fc.canChange(pokemon)); - let matchingFormChange; + let matchingFormChange: SpeciesFormChange | null; if (pokemon.species.speciesId === Species.NECROZMA && matchingFormChangeOpts.length > 1) { // Ultra Necrozma is changing its form back, so we need to figure out into which form it devolves. const formChangeItemModifiers = (this.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]).filter(m => m.active).map(m => m.formChangeItem); diff --git a/src/data/ability.ts b/src/data/ability.ts index abc45273131..cfd900d621c 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5039,6 +5039,7 @@ export function initAbilities() { (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon) => Math.floor(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) + .bypassFaint() .ignorable(), new Ability(Abilities.BATTLE_BOND, 7) .attr(PostVictoryFormChangeAbAttr, () => 2) @@ -5191,6 +5192,7 @@ export function initAbilities() { .attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) .attr(PostBattleInitFormChangeAbAttr, () => 0) + .bypassFaint() .ignorable(), new Ability(Abilities.POWER_SPOT, 8) .attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL, MoveCategory.PHYSICAL], 1.3), diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index 183295f6f41..8b1b959bea8 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -2,12 +2,12 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; -import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import { StatusEffect } from "#app/data/status-effect.js"; -import { MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js"; +import { CommandPhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { Mode } from "#app/ui/ui.js"; const TIMEOUT = 20 * 1000; @@ -38,7 +38,7 @@ describe("Abilities - Disguise", () => { game.override.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]); }, TIMEOUT); - it("takes no damage from attacking move and transforms to Busted form, taking 1/8 max HP damage from the disguise breaking", async () => { + it("takes no damage from attacking move and transforms to Busted form, takes 1/8 max HP damage from the disguise breaking", async () => { await game.startBattle(); const mimikyu = game.scene.getEnemyPokemon()!; @@ -134,17 +134,30 @@ describe("Abilities - Disguise", () => { expect(mimikyu.formIndex).toBe(bustedForm); }, TIMEOUT); - it("reverts to Disguised on arena reset", async () => { - game.override.startingWave(4); + it("persists form change when wave changes with no arena reset", async () => { + game.override.starterSpecies(0); + game.override.starterForms({ + [Species.MIMIKYU]: bustedForm + }); + await game.startBattle([Species.FURRET, Species.MIMIKYU]); + const mimikyu = game.scene.getParty()[1]!; + expect(mimikyu.formIndex).toBe(bustedForm); + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.doKillOpponents(); + await game.toNextWave(); + + expect(mimikyu.formIndex).toBe(bustedForm); + }, TIMEOUT); + + it("reverts to Disguised form on arena reset", async () => { + game.override.startingWave(4); game.override.starterSpecies(Species.MIMIKYU); game.override.starterForms({ [Species.MIMIKYU]: bustedForm }); - game.override.enemySpecies(Species.MAGIKARP); - game.override.enemyAbility(Abilities.BALL_FETCH); - await game.startBattle(); const mimikyu = game.scene.getPlayerPokemon()!; @@ -153,10 +166,41 @@ describe("Abilities - Disguise", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); await game.doKillOpponents(); - await game.phaseInterceptor.to(TurnEndPhase); - game.doSelectModifier(); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextWave(); expect(mimikyu.formIndex).toBe(disguisedForm); }, TIMEOUT); + + it("reverts to Disguised form on biome change when fainted", async () => { + game.override.startingWave(10); + game.override.starterSpecies(0); + game.override.starterForms({ + [Species.MIMIKYU]: bustedForm + }); + + await game.startBattle([Species.MIMIKYU, Species.FURRET]); + + const mimikyu1 = game.scene.getPlayerPokemon()!; + + expect(mimikyu1.formIndex).toBe(bustedForm); + + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.killPokemon(mimikyu1); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.doKillOpponents(); + game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { // TODO: Make tests run in set mode instead of switch mode + game.setMode(Mode.MESSAGE); + game.endPhase(); + }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); + + game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { + game.setMode(Mode.MESSAGE); + game.endPhase(); + }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); + await game.phaseInterceptor.to("PartyHealPhase"); + + expect(mimikyu1.formIndex).toBe(disguisedForm); + }, TIMEOUT); }); diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 34f79f93b6e..5a8b4ae01b2 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -15,6 +15,7 @@ import { MovePhase, NewBattlePhase, NextEncounterPhase, + PartyHealPhase, PostSummonPhase, SelectGenderPhase, SelectModifierPhase, @@ -92,6 +93,7 @@ export default class PhaseInterceptor { [QuietFormChangePhase, this.startPhase], [SwitchPhase, this.startPhase], [SwitchSummonPhase, this.startPhase], + [PartyHealPhase, this.startPhase], ]; private endBySetMode = [ From 58bf18af88a96c8f011dc9b42fbfbd6f9af3cadd Mon Sep 17 00:00:00 2001 From: Opaque02 <66582645+Opaque02@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:39:08 +1000 Subject: [PATCH 14/26] Fixed issue with falsy issue within condition to get a stat for IV scanner --- src/ui/battle-message-ui-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 1c7dfb27630..7a30e2787df 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -226,7 +226,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { highestIv = ivs[s]; } }); - if (shownStat) { + if (shownStat !== null && shownStat !== undefined) { shownStats.push(shownStat); statsPool.splice(statsPool.indexOf(shownStat), 1); } From 69c1389ec4ceda8e9389129118a4456edbffe15c Mon Sep 17 00:00:00 2001 From: KimJeongSun Date: Sun, 18 Aug 2024 15:12:20 +0900 Subject: [PATCH 15/26] add fix setting code to prevent form/variant bug when default form/variant setting is wrong. in addition, that fix code include gender fix, so i revert old gender fix. update wrong log message. --- src/ui/starter-select-ui-handler.ts | 96 ++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5e942f3e75a..9f2df1f2329 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2916,14 +2916,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3); const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2); + const isDefaultVariantCaught = !!(isCaught & DexAttr.DEFAULT_VARIANT); const isVariantCaught = !!(isCaught & DexAttr.SHINY); const isMaleCaught = !!(isCaught & DexAttr.MALE); const isFemaleCaught = !!(isCaught & DexAttr.FEMALE); + const starterAttributes = this.starterPreferences[species.speciesId]; + + const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); + const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + if (!dexEntry.caughtAttr) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); if (shiny === undefined || shiny !== props.shiny) { shiny = props.shiny; } @@ -2942,6 +2946,83 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (natureIndex === undefined || natureIndex !== defaultNature) { natureIndex = defaultNature; } + } else { + // compare current shiny, formIndex, female, variant, abilityIndex, natureIndex with the caught ones + // if the current ones are not caught, we need to find the next caught ones + if (shiny) { + if (!(isVariantCaught || isVariant2Caught || isVariant3Caught)) { + shiny = false; + starterAttributes.shiny = false; + variant = 0; + starterAttributes.variant = 0; + } else { + shiny = true; + starterAttributes.shiny = true; + if (variant === 0 && !isDefaultVariantCaught) { + if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 1 && !isVariant2Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 2 && !isVariant3Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } + } + } + if (female) { + if (!isFemaleCaught) { + female = false; + starterAttributes.female = false; + } + } else { + if (!isMaleCaught) { + female = true; + starterAttributes.female = true; + } + } + + if (species.forms) { + const formCount = species.forms.length; + let newFormIndex = formIndex??0; + if (species.forms[newFormIndex]) { + const isValidForm = species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex); + if (!isValidForm) { + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex)) { + break; + } + } while (newFormIndex !== props.formIndex); + formIndex = newFormIndex; + starterAttributes.form = formIndex; + } + } + } } this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default? @@ -2993,12 +3074,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } if (dexEntry.caughtAttr && species.malePercent !== null) { - let gender: Gender; - if ((female && isFemaleCaught) || (!female && !isMaleCaught)) { - gender = Gender.FEMALE; - } else { - gender = Gender.MALE; - } + const gender = !female ? Gender.MALE : Gender.FEMALE; this.pokemonGenderText.setText(getGenderSymbol(gender)); this.pokemonGenderText.setColor(getGenderColor(gender)); this.pokemonGenderText.setShadowColor(getGenderColor(gender, true)); @@ -3479,7 +3555,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) { if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) { - console.log(`${species.name}'s variant icon does not exist. Replacing with default.`); + console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`); icon.setTexture(species.getIconAtlasKey(formIndex, false, variant)); icon.setFrame(species.getIconId(female, formIndex, false, variant)); } From 1b7a161934930c1de4493182f82386ff2b223073 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 18 Aug 2024 09:52:32 -0700 Subject: [PATCH 16/26] [Hotfix] Fix Memory Mushroom not showing relearner moves (#3619) * Fix Memory Mushroom not showing relearner moves * Fix rollout test --- src/field/pokemon.ts | 2 +- src/test/moves/rollout.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f1721299ad0..10851451a1a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -921,7 +921,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * by how many learnable moves there are for the {@linkcode Pokemon}. */ getLearnableLevelMoves(): Moves[] { - let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]); + let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index ad323c447f5..728fe1ecd45 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -12,6 +12,7 @@ import { SPLASH_ONLY } from "#test/utils/testUtils"; describe("Moves - Rollout", () => { let phaserGame: Phaser.Game; let game: GameManager; + const TIMEOUT = 20 * 1000; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -77,5 +78,5 @@ describe("Moves - Rollout", () => { // reset expect(turn6Dmg).toBeGreaterThanOrEqual(turn1Dmg - variance); expect(turn6Dmg).toBeLessThanOrEqual(turn1Dmg + variance); - }); + }, TIMEOUT); }); From 67da7956119afe32f7ec178a32c14d1488fec6cc Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sun, 18 Aug 2024 10:29:11 -0700 Subject: [PATCH 17/26] Rewrite player faint logic in FaintPhase (#3614) --- src/phases.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/phases.ts b/src/phases.ts index 88acfc825ef..565914879e4 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -4033,13 +4033,24 @@ export class FaintPhase extends PokemonPhase { } if (this.player) { - const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); - const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length; - if (!nonFaintedPartyMemberCount) { + /** The total number of Pokemon in the player's party that can legally fight */ + const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + /** The total number of legal player Pokemon that aren't currently on the field */ + const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); + if (!legalPlayerPokemon.length) { + /** If the player doesn't have any legal Pokemon, end the game */ this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { + } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + /** + * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon + * is already on the field, unshift a phase that moves that Pokemon to center position. + */ this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) { + } else if (legalPlayerPartyPokemon.length > 0) { + /** + * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, + * push a phase that prompts the player to summon a Pokemon from their party. + */ this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } } else { From c8ed89e186dfeb06f521caaee643ef30a2bfe653 Mon Sep 17 00:00:00 2001 From: cam Date: Sun, 18 Aug 2024 14:44:50 -0400 Subject: [PATCH 18/26] 867 runerigus sprite (#3629) cropped static frames, fixed cropped sprite set runerigus exp to use the shiny exp's animation verified all hex colors are unchanged - fixed ultra necrozma exp front variant swapped arrays. - xatu female eye color fix --- public/images/pokemon/867.png | Bin 2128 -> 1047 bytes public/images/pokemon/back/867.png | Bin 1617 -> 800 bytes public/images/pokemon/back/shiny/867.png | Bin 743 -> 793 bytes public/images/pokemon/exp/867.json | 1590 +++++++++-------- public/images/pokemon/exp/867.png | Bin 23716 -> 13409 bytes public/images/pokemon/exp/back/867.png | Bin 7341 -> 3724 bytes public/images/pokemon/exp/back/shiny/867.png | Bin 3270 -> 3715 bytes public/images/pokemon/shiny/867.png | Bin 976 -> 1043 bytes .../pokemon/variant/back/female/178_2.png | Bin 6304 -> 6305 bytes .../pokemon/variant/back/female/178_3.png | Bin 6304 -> 6305 bytes .../images/pokemon/variant/exp/800-ultra.json | 32 +- 11 files changed, 842 insertions(+), 780 deletions(-) diff --git a/public/images/pokemon/867.png b/public/images/pokemon/867.png index bd07a7a48375858f3ed2506c81e51befc2c02db8..2fe8856d74df6113ece362f16ddf144140f82469 100644 GIT binary patch literal 1047 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 P00000NkvXXu0mjf&XWG+ diff --git a/public/images/pokemon/back/shiny/867.png b/public/images/pokemon/back/shiny/867.png index fce3fe4e37eaa10d77894bfd02c7d8da05536297..accb87844f1c680b67e46ba371405a992adc6d83 100644 GIT binary patch delta 750 zcmV5(BMe+P6@PE!E?|NsC0 z|D}JX`v3p}pGibPR9Jqa*agmie?PDZ&UyddX7~rT!GZm%V?Es2RjnQSRPJDBlM4HuAJda8HmT((A8Rw` z{B#EfbhRs-=o2*qe^53nmdFk9(M=D zhCK|_cDY4py*!63u(0(_>Cmfr?`?j*UMAWSwuZ4COtVhEI=jj3fp)%wcj`U5UR$9@ zOs0<`=a}3T#(x`Kz--ztZ(jKIuXn=Cps8%e<^dk{s8Kxfd(AYtfz<$ z$1*oRz?;i<$J~?mZZyaf*e68$$;qYb^(4j<&}h*?YF|go5D+*CZ-w$CXi-3lYIjm- z0_ZbZMqo9Z2N|S9k8$x~yv~f(e_CjC^btIwHkPDK->^lEZ6Mu(H+|0m-uhfa4A&vKT$K#o#tv{ju%09~ zMOV7?+t>pNua>{M=p4!O8trMo)$Efxdq}Su1c`VRbY0FK$p~B$Eoj8_jVy}^{eTWL zqh)$M-pTm|%uP(0wr{qdn{6~pF3QYd+1wYb7J#g>e=G>;gKfEV9{nzK$|k>bZNR0v zYr?#iFYRyVkXhD+I*|K4&yA2V$=|qSpg(Se%uAOKTI!riI2_kxEqT}lopqU-7LEI| zq@&S0e}@=TewB7IjhE1ygYI^6i`Df4jdMC=4`OaGXRZZpG`J$IgJ7}7UhG4Z zJy86;Oe&h@SI4yetE5)E9#8JOwq^X*Jd|C-fP?=&e}58ozTs{!=e;NH7KHXR#*6lQ gHMYNUz5d|Ae==DedM}{`Q~&?~07*qoM6N<$f`sjD`Tzg` delta 700 zcmV;t0z>_o2ImEk7YbPf0{{R3rzMq~kuD^E0d!JMQvg8b*k%9#0)R!%Iy%a=nku)zv+9krb^hCtxO(GoZl3U5D`z{ulmBA-ZP+HM{B52`nw@84Nx#c$hBASTAqs6EDU*#pe&7*4Z9u>QCUCSd2O&X+{a2myz`wHNw5SQDalhkUW z44hIDeO)<&k*=^{jjmR|LlP5*%E-KGNa_xjpSKwxc2km!+;Dl&6bR}J4(_e zo|F^m+N@}hH->e$X@HjasGLZMh(a`f$r3}|?UMTiPq0zTztvaW46RX}D1$XrCm)Oj zI;w*!As;N3$uoj3l1s+P!@xdhHQr#+i2%_l^)ids6+dd{u<6lbnd*`V{S+Eq(vSwW z2(p}{5v^18l!4G3SgGC4wrb}BKAV2Pd=6BzH7ajPUi?DV;mYG75Q77Q5Wasl9YW ztVwR9^MJJM>(niylzE=hAWHe3j`UH^Sv&Xjz%mP`ToJ|}fZ diff --git a/public/images/pokemon/exp/867.json b/public/images/pokemon/exp/867.json index 1a9c7572f43..52e0127509b 100644 --- a/public/images/pokemon/exp/867.json +++ b/public/images/pokemon/exp/867.json @@ -4,8 +4,8 @@ "image": "867.png", "format": "RGBA8888", "size": { - "w": 344, - "h": 344 + "w": 361, + "h": 361 }, "scale": 1, "frames": [ @@ -31,7 +31,7 @@ } }, { - "filename": "0020.png", + "filename": "0021.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -52,7 +52,7 @@ } }, { - "filename": "0035.png", + "filename": "0037.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -94,70 +94,7 @@ } }, { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0034.png", + "filename": "0020.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -199,7 +136,7 @@ } }, { - "filename": "0003.png", + "filename": "0006.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -207,20 +144,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0018.png", + "filename": "0022.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -228,20 +165,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0033.png", + "filename": "0038.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -249,62 +186,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0026.png", + "filename": "0012.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -346,7 +241,7 @@ } }, { - "filename": "0041.png", + "filename": "0044.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -367,7 +262,7 @@ } }, { - "filename": "0043.png", + "filename": "0003.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -375,16 +270,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 5, + "x": 4, "y": 0, - "w": 103, - "h": 65 + "w": 107, + "h": 56 }, "frame": { - "x": 0, + "x": 103, "y": 56, - "w": 103, - "h": 65 + "w": 107, + "h": 56 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 } }, { @@ -402,14 +339,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0022.png", + "filename": "0023.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -423,14 +360,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0037.png", + "filename": "0039.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -444,12 +381,54 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, { "filename": "0002.png", "rotated": false, @@ -465,14 +444,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0017.png", + "filename": "0018.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -486,14 +465,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0032.png", + "filename": "0034.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -507,12 +486,138 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, { "filename": "0008.png", "rotated": false, @@ -524,266 +629,14 @@ "spriteSourceSize": { "x": 6, "y": 0, - "w": 102, + "w": 101, "h": 57 }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 8, - "y": 0, - "w": 99, - "h": 59 - }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { @@ -795,20 +648,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0039.png", + "filename": "0040.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -816,20 +669,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0015.png", + "filename": "0010.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -849,6 +702,195 @@ "h": 59 } }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 0, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 97, + "h": 65 + }, + "frame": { + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, { "filename": "0030.png", "rotated": false, @@ -858,20 +900,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 } }, { - "filename": "0045.png", + "filename": "0046.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -879,16 +921,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 } }, { @@ -906,8 +990,134 @@ "h": 59 }, "frame": { - "x": 0, - "y": 245, + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, "w": 95, "h": 59 } @@ -921,146 +1131,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 12, - "y": 1, - "w": 90, - "h": 59 - }, - "frame": { - "x": 95, - "y": 245, - "w": 90, - "h": 59 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, + "x": 8, "y": 0, - "w": 44, - "h": 54 + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0052.png", + "filename": "0032.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1068,100 +1152,16 @@ "h": 66 }, "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { @@ -1173,41 +1173,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 - }, - "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 - } - }, - { - "filename": "0049.png", + "filename": "0052.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1221,14 +1200,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0062.png", + "filename": "0065.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1242,56 +1221,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0053.png", + "filename": "0056.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1305,29 +1242,8 @@ "h": 53 }, "frame": { - "x": 255, - "y": 230, - "w": 44, - "h": 53 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 41, - "y": 4, - "w": 44, - "h": 53 - }, - "frame": { - "x": 255, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } @@ -1341,20 +1257,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 38, + "x": 41, "y": 4, "w": 44, "h": 53 }, "frame": { - "x": 299, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } }, { - "filename": "0054.png", + "filename": "0015.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1362,14 +1278,161 @@ "h": 66 }, "spriteSourceSize": { - "x": 42, + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 12, + "y": 1, + "w": 90, + "h": 59 + }, + "frame": { + "x": 100, + "y": 299, + "w": 90, + "h": 59 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 37, + "y": 4, + "w": 44, + "h": 53 + }, + "frame": { + "x": 190, + "y": 299, + "w": 44, + "h": 53 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 43, "y": 5, "w": 44, "h": 52 }, "frame": { - "x": 287, - "y": 283, + "x": 234, + "y": 299, "w": 44, "h": 52 } @@ -1379,7 +1442,6 @@ ], "meta": { "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:b8ca75f7f37906e78ed633b32d037b74:92bc79a7ca35490600679451c06105fc:58bcd289dd222ce646aec14ff657c9fc$" + "version": "3.0" } } diff --git a/public/images/pokemon/exp/867.png b/public/images/pokemon/exp/867.png index 689a72694ef90e5dd459be477119a20f39db8e6a..4f8f67842bdd6ee61b2ba0ca2afe8d7ddfd2c852 100644 GIT binary patch literal 13409 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

AEr)YqXKi=OLo?%#>feG5W8?bg!mY<> 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 z4));y diff --git a/public/images/pokemon/exp/back/867.png b/public/images/pokemon/exp/back/867.png index 766ce3f39ed80703b473d801230d7cedaf763998..b816f10a0def567345f8a9a7a87e041d4898b70a 100644 GIT binary patch literal 3724 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 M07*qoM6N<$g4SP4<^TWy delta 3246 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 g{(l;-f7U5(BVe+qO`PE!E?|NsC0 z|NsC0eu)%T000AxNklv-@}kI9IwyWjBS>=%FOxxDSo1p2<7xa(`${) z%q~PHUqqyC^rrMuC}$!{s>n%I^Y9R3FdYAq+!+hV#J`k9e;Qea#AqI+7=GMrI<7v8vXoDNN6!Jl8$5hU13p#+1}WtbaK3b{{ z>+?FKWe;BE1MBEMjRnxJ3XABgnrY2K8{v3af1=+h$w#B3vY>yfi}ZXN#{suw^VIR+ zeTP1Nu>Lms_ew5*)yCv?`mNgL@?$_6cV=Iv&j-%*id7vB(#!N)Cu(OFt&Nu6kxnc0 z+e4`i)lnNo8phNEtkdJ0MSNM%?!}snb1i#Fze+w@0T(A_>&^teXh?sZyP;s$UZV3` zKQ3bD?G(cb7k6T({kAszo~saPXotU1c<;{TYky$+CHsp1P4*n)l`H=Na`iFH*e8W{ P00000NkvXXu0mjfr?m7Y delta 926 zcmV;P17ZA=2+#+R7Ybbj0{{R3HD7n|kuEKN0d!JMQvg8b*k%9#17Jx+K~#8N?Uli< z+cpqI1LzAB*{-t8A5a(z$kq^5xJ$tzkZpj1<_Dw-x5D$Iraw?1f7?5hWIrQMcD*fp zVJ{4w;k_b<;}6RsQXd`=6)8Q0f8tC%gmX{-9nTrMYmg94cxUbLz5%5fk>E$>fsglp z43dKTGy^^`v&W|aPYWgl{pJ`IIXfN@hhIlty&Jqp)6_fetm{`fcZ@g&c?fz3Pt%P( z?wtb{Jog)h{%S}TN=W^RgX8Z5TP{8`lJvl*;i1>MHV0iro;FqvgZp!F=uSGtzO!{9SVkUAr7_zoMi9ae$BF;+6Td>WBLeT( zIVYV{5g8c72!-aa)(;cEGJpKTZB-~Zfw$HM7G4bMX5@EBGxKXx*ELDffauy418-wUKMaC_0&^D za`whlNYhr^ddyE3K2cZ{%Ywgu+yr^3+daRSXh7p$fuGmBz7({sP5V1TH8yAj4kh@J zAqDs?pA<jTw&6D>LyQ=0uA#F5+zG<=9%HxdH#x9Jq4#be*~K!Jn$*Fzfi8h%dZTH}{fqgofP2pBDJD(Iz&hWnO7f z-Q3}2FD-D!*db#~b}J85c#aBvNkpc}7EV`9dj#l<;LZ?~X0+H27cqD4qaaRWUDa>*H{imm(5+E%gE4e-Yw5sY1jKEH zcX6pmK4&c5x_=Exq2lIe7P)mSrV)VnMmN8j6yeLzt&0bRiV8c9KgFOUirm(8&Fc6j z97-5ix4l_gAn~c#b=@APEyPFjaJQmZ(MK}}&X_P8dIFFPtfPuy>ri)-G z4Eh=sVF3hbhlIkzrzF+9y;*kIt!O-PHmZxMRjX*yj`ZupIv^ z)2>%x0e=h!bZ`D@|JSW^yJv!dJ!a^EjUE{AXCm!0Do&5$)ZXrX#Jzu8A9;ovbJqc3 zmvbI>G-JoVa<$K?NFIf$J)St<+=#pXyZJ*7vJi%Hj0T6p0ZX~y*EZDN#(}M3qf}&% zf>X|?yGgHA%pVGfa6b>jGlT;e(}1pnK~NZUR)0Pme}=Sglp7Kihkd063p`r#+};xm z_l>~8Vi+P089ExfHVn^mP5Xq4|6^^DbEEP@qM|m>J`oJdGmZeVJ^uXJ%Taky4hJ|! zgo9I;eJKB<4RZ;^)aqp_s`2*uL@-za2lIIB&#f4>Ll_7g9ClnJ;#Oaz_$0?47ur{+ z9)Fo6R8dT=Et3i{w{NS%;qO+Df-q2Ho@^X?E~K>$w6CjE|94lbz~{A0oVL=JPWpQiL554q=RW?1!Q6 z0vsA=yeFQ^XpvK&Z4#^SIP3r=eCUdE`+tV->7Fw_^M+Ev;BlzN1nih6Y*TJ@*(N_X zrqq60y#9w#s@NQg06{o5%yjw^k?!e$_}Cxuo|b}v7-JlG4<-&B@jeh1y}t*e7B%dsRA%z1q#@RXj2p-~{vgrLV%H@gin*mA%FwRmi?6gnEU4KD9 zpd}K+ff|zssbP51FdQhqJ2Bf3>XwQ;vxb1-IEpoJNOv@Krf7_r$HDJShVmlfaM(4t zLt6!DTMBZ@r|6U+2Q+Z-;2HX6T#5H+S>(mq{wtEE3VM=KkT-F7--!6>XpR`W~1A)S! zHRcGRLD=?T2u9J`mqY*NW~cw2t*4@n)$G+V#0k9q_*dr+#vPs`8e`tK3O$*`XFx-0 zUrd-B+r^=)qP8t>fyQ4_im7i0PO^=XSt7zc>)_(;WaD)&M zfB7KM81qhzl1Ma)*6!|Whd1b|sOJd8LmlJCc=C03Qqve?N3%5$o1CDu`x}p5nvos^TkB$s!$1Ww)TSs8M=u;f>%g7nm^mDR7Y*&_o%`X?RY8u; zi^ctSw7)RiA;v6ID4eN21b-=^eO|@bI-~enNsO6-!J;=E8jHFrSZYjuHs{~d-O-)B z`C|qeAOjF2eel+$)frPI&FkiCweoyp|(>xr#P7PlJyNZY`ZlDa6t~gbob8(0!=()Eq22 zwnJU|IUfC@Hs{qKTz}JDA7_YPv)1RtrTE1o;bUXu#891iKA27g{M}!UwJy2s3kX_S z{FY~g7EyYRdFHDy;kHJQ^Bg*SrUudN?tf7v9!eZkjeE~=t8-H@0_oVh@UNh zq;)B2@9UYkQEV7L*`ZZ1dCcUQ;A`?inKRJ=2iFvlbmXCWAaakV!4(==E%GXpIj*itZETkTvD_= zXK$Xnh+z)Jj6G^`*Orq7^uMv)sigGOxm^ijXU^*82A&B^}IMz+Ido z^D0=zH6d3Je}AD?sDk0+W1hP>MPgO3j0=Y2jZaKI-(F#g)@kzW-Z_Os< z9x>S_0*ejQADzK%nIZEMP7PM-G2%ufxnWR;;#zaA409rJ-AkznGB4p&pJm)ga^uCF zJ`V#Ju6;A7C-YKH^;yOQ50yhqwtx~BJ~YU`I1QcIwSQz@+NsX<6c2TLN1GT65e63y zZx`bbKUQeSytGrD>z_3bmBU^|MfWjPMk3fGV-LELzM%;5CCIgX6wZ42M8M?50xG%( zHTRJSW?I}fzJPCfyOEN4OIGQ?sa*PJC0WispA-y$srELF17OYxXti-HO|E6!tIYQO z^yo-&`F~3^D6+mMA{arS5{R`J1{n^v-pM;7Z9%_WxLGa{p-jGOMNGQQ;9zt9T^0$w zINNt_VI!wb{ufLM;Tp_qSOY8~9BK)lMd;kqBJ+01l$2AKrhAf~^?y10 z0VD2yZHjD7=0&s1aSCr#6!%$_=FFNyAw|VxJj7)|5wn|Q-T}9ka0+cy7o@rM>UEKh z*%S;dGB4cBnNC@Z!*rU

WN*NJm5wnHO?OnN9&9A)8G(o#uQkoYuwUH;hC#uig3V zka^K!$38WkDwYMFO~rJY1jhV_9Dn#L44<`_7MZutt?fI7AlGNPNYHYINglZyx(eDo z_NGJTrMR^@EV^DH$hBGSVuIGWTePUc-tO+Zbp!q9c~z4<#mnMif>z!D^<-X+S45VR z8J-NfhM)z1dPYs=J-q_UiQ!ewax?#kWx3{dv(M zHCK>%Yhn9_b6@x3`epm=^Y2ydla`l|dD*_f4~8R-&#nG=!05^Lox=AmbH4nSvOZJg zR@D2Ot9s16NRV%%{VgWH;F#x9Hkm57g8DEQ3fklO%H9Af*E-Wyk$-$TOqHUq*bEHn zw-RhIRjh|$zXAsR@m$g-SJWku%6;@6uovysW^m9R&qZx=E7y8tq%!xd zEv8B!!dwy#>f^brO>X5{kBr35eUT94Ux`gV;;4`3vNpMuYdtbj*;BD`U3f`=8;h|_ z?|9>HCpn%A39L-l{C}lI?rXyE^zT=9|6L09@C=wIKJ6VF=WR8J1Gj?3R5z7qrRcx|XhG$tBxGHnl$MYw2U;8GjDX9kwsiv6Ni)x~;<2 zuxTW#O|B(jXzrID2fL3_U+PupzfMBZge!KJ@^eBA-W11Dg2~&8UchJ@W_5SKgXVlM%?X|VDh#Xaq?`K)ot`H^x|r#KEM#G*aE)TUBXL2tzktl zu-oKT?IOEbJ%3*vmV7hyfi8Vj)xR%xm+*67YuKE}S)Z%93%+2q-TGO*xVoMCDjuZ4 zFS;cBoT4?X;8ZGTll!crx0BT?&`|2jGYr~LR?8x8-)vqAZVfA=u)@688wd!)xrZF> z8gX@#=L6eNmN>*Vb8Mbc!L}B3yTwdiII#T6UCmmT!hcj;U0e)#K9I_ho6}*|1g~Pd zY7NVzuo8I>46K`N7BIA38-Rv*KCzk!{Iek*k;Xt!tzqvh3Tu_g5cd^#p~Y*s#sO29 zFazB0odr?KP@Vd+;u?oh>3XX*?3ltjXp>tqH=lUcN*EA@skh`)>O23l-BILtVr+%O zsC1od4S#z_(t1!Cw8`Bbc~l~LW^2*qrEAra-%G+6Onnw#YRIzxZs|JR8kR$0#l}*3 z@A3Gv-xi>^n6;{HW_GQyz8gX^~ z+P+SFop9(?v$k5pGAOJGL6n{f3M=X_3>mB$hJTi8_0^mm0F{TsYVCfRcfrR^^U9&H zGTP)~3TrOROd~t46)S$Hh*H%KkXI9Au9bDs1P*vfS~&_i#$uaMW_#75uLHSFfa@$9>x?_OToaRu)-Sdp=aP~0)}b}$U7)l7>qJ+OTniZ^0J{vwvm6hQ5z-+v z#IopWT`-?Ep@}>SD_+2@_1@mvC_+;08H%1_hSwg^=kUU0wwCCV)_G3}g>}H)Y*AR# zoQ*uomqVshD|+C-SQYKx91B3`?oc@(s(*g8aN}TXZ7{JQ4p_@GYl-OTtcxHsM22oS zG%2iuHn|jq^;E!F7YkbPW%RVdeTK-8!$Cq}Md3gV>^$5^JoDw7cwKZBeKJu&WPoSV zJWXk`8WdJ|j%W>R0|7W-1J9CsCj_j_ry(-H^zd1WX;4@prMb=V8+yhmT(RlH4}UlH z3EaDh^kQa4Cg#)F$^D*R-iGm@&Kz`eR!MmK5Io zIFqgmk!!ltC#dBFa_&%C3TtoRRj!FrpQM(vWg*%$xw%F)P~@5<^@(aZ8@JQen<)lr zvN}g(wVbq=oWEoSnzZDF1FsiZEq^B|;f2Po==y8oxEJ9dNdJmvB!!%L>)p4AwUWL1 z`nJhXMCo7Aj0DRTJEEE`bwqvgxOy)>>&Vi-sFL(Ym7%L;aHT0Kb%bR< z)kcRgDaTK69uSoB$G67AT7OQioIx8>QX~=gfRJ+h1m^*6`WMu4awRlUK&B2TT2hW5 z`#iue=B(+pZ|t|MFT+E(-n~>y7^dD-x=9zPx+AsFc5iVOYz_LLmjC5C$g4k98i9 zqxQ)_-z#Y()Jxg?^16@9=q`pEq80X0^Gxn?hcvqhXAa1u32UYr?sO9AL-+!#;w|~y59`6R{fr8qt zP5qJkX8Dz5 z&ZU1*EvKbW2!AjX+{JCr^m^K_=KkI|4-}N<`aCdI%D;$eIV;|xmtbq*7vP{bV8B6( zS?JYypu&wh4`|bWLW2E;v)zBQT8+-o48-W11NV3J&|01g0Yrs(BfJx|;U)}wk zI1g}G>)P}mWVUuMa$;*7vS-Y5PR&ABKMu;*#dPgne1GmFaVY83UB9aRedcjcSjs|wVAOKca^DmpkzHLGOLG@D zLtGmD8t(7?^T2Sue{GAc=v%`t#A!x;aq`&cJkVS3Uq{ahre>-;#-G3={X-`MbI^VSIrsbEd9?xa_d*?hbS{G5ih@N%s77f3u4-LsL;r=dO7ipj% z?dj=Rqqekd3#9vdb^T^y%~qkDuC z?(gt=o_(S^WMsUQo(21FV!y`L3fol5sa&GGH-Agd%6kI#YdmKn92DmwS>BtbXC*P` zYi#CxNjRU1bCEFbT}02?z>iW%;+)C(RGf=cyd;<-H1w?P5$)SH6FHoN0Xohn|6DZL zVnj#Ja=#qrIh-Dkz~SurrR99`w+nHLk!$E#<}A}VIh_6Vd-@~he6nS(#mF>E&3WhJ zmVf70fJ4CfWXnv8k&DQB#smksueCIs&!H$r7-8qVUVZ*u%TxO)!sjJ*>+^|1 zw_ha^UQ(|z9bC#Yn`VQO{$4(}r=1rp3tl<)m5X_WOX) zv=|xG=l@b126<1^V&trI`-M1+^Im&!v(D|;!NAFTz0uBRw2Xc!3JW9e^+y-=`M(#3 z8D7#C*XMtch}Xr7e^UJ0L|(W2%0QnK|4t$D9%IyNep37!#m_6ud%X4jueg88zq;O~ l^6##9o&0m_T`4bc|Np)hPyDE6RP+D<002ovPDHLkV1mAkLec;L delta 6214 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 diff --git a/public/images/pokemon/variant/back/female/178_3.png b/public/images/pokemon/variant/back/female/178_3.png index 32ffdd895c6950fdcec0edc08b2b41b37a01a7ae..9533621c6d696fda7b3f97abd127ba04cbcc78fd 100644 GIT binary patch delta 6215 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 delta 6214 zcmV-M7`f-6F`zM!k$?J0L_t(|oZVfEmh37Djg_u+9aG&s|NpfUA5lGW zdNYT7eSCcxDG)jpt3&SyBH69ty{<>%emb!}{&zTW$fpzsm4Av8ykis}ia5y0#cMFl zq1-YIZvylfa+HpdNsDPT=C;1&VUjRn z+p{M55aXaykz;5z591fpcrD%52!uyLoW{DU-|la~g;k+jqv8f*=;qeafx`%h+Y0aE zQjvVlSh{r^l7B+Q&Ce`y>sU-90P&4(el;n=m!Vr14+<3(b{v0-K}Qt1t?8Q8@k2P2 zFtBcWv$jCuQ?bc6bQcDFlZw(K77%tfD;jt#rjLtoC=AjlRj6uA>-TjDr|F%Bz3^nGi1Hvxn zJnm@5j(_E9pHq=M3R8PLalW|`cmH?uj~rwn4CNRN4u=Dla>1`{sJ)E?Tg67H$Q}i! zoKJU?UaOdY6cFKl9)@QK2Qa1qT?d1pFzBp&IDh^OY2PR}Bq|R3N(~lxwB)(HCm8M< zfrG^`L>w}7GK&#I2lwjU;gD~N zjwt7%8jN89L7#3s&?O9q`a?fl0WuYEYI9b>FyIdnUN-ajnGz|&jtGY^#ys}J(02h2 zjWga8&t!zfj34n=?<92;gjeThi-bU=LUk9bc@!9a{L4!j2whmLq32#em|gHen-5Gv5j zmXQ?Vp?Y2I?`o1y7Qm3gfg0oNoPGok@lILvei-HQ#Q4nsr2-gdDHwLzr{k_5Ab-#j ziQzzv$%E7|JZTsXl;540Z3uNsMV?thz;GPJ8aSjonmSW7#?0g3_a;Mm5pg)|8r-3+ zg0w9KIptGy%8&yZIC$_3eKW4ad$cU_Vr~BwNmB(qNh!#iIJ|E}{Pb~nA5T|bo{jX7 z0^;2qICO8(RN*4L0YfVs4xhFH_kUqHoHI5R5BnGg)5oEwqKShGhtE&iCxd}N;m{g$ zgwP;t`!EEfXzk0Pe{-|bf6vxaQO9cb>KNh#UVr?na|h!N&k>C=?^}hQOyV=3A+;|i zOpfj1&{a{}mbXCTFDb>;w*x2H#+dgSJ&~MCkJi4pfbjY|IP_JtZ~*H94SzU7h={*@ zkZ6o~r$$L68bxb&_qD?tbXC-I1mdBN@nbysx;v?9jIpEH8i-9!P}=>CM=$dGD(azm zwKqbjd&5@P&3(;C4}z_AF~(t_f*5L3l!v1ij-YkmPIJs04#A6t_VdpDaOkQa$L7W2 z{yW-V814{bmMIj@R3Czr(0@L!Vr-pJe61wLOu=B$n+}adT@@@fCO@0=@9FO7&ffen z1C4QQYb}B*+-v?r#l9?CsPR}>V2ba$ZQ+K-kFiep*0v;6vgFAWS%g#^k5t=%!hSqb z9F`s`nC6-D{>-~>kH=YP;P33dDp~T|nNYmF+i`q0F*K|=+MEjG-hXF?uhIS=%+3H} zi!mPNmUY*(*g{qRj!qpG<6^3kMH`!@(ZrjkFx06Fk}6L((n^ zaTgb1eN65ZNKPlbcIU*dS$1B_k-uC;o13S>M+LW*&ZQJ$X&*ud24CntRC{U;79HE6 zF8v&jeo>qAY7nmJu78g+#IITFbK+9`;*s#NF>+$4&O9GXrvm=&FUMM!-1Y?ott@`a zGeV0fJ;yxrRhV#FBglD<9q2@59osC4MQtT79FtgD;(N&C`Yd#z+`xCv*G9ze7C_Rv zl(hH#Ox&qSbf2}-2jA;Rd$VL-d9s?!aq;tB`ks0X5R+tH&wt33`Ls^1?TVzO(o#zP zV6P$bDtv-PEt;vsFkD62nkMioA=moST}06eoV{7@;uM)z;Z#M)62V3vtosqVnUoPA%mxxd%-?btp-DYsGIsYz;gkGHO zJGZcrQz!q~EO2?VvYdq|fN(%BJpNS-Kd#tlPtxyqPBvAzoZY@tkfkTia<&YHFdX7w z4j9-hwZaX^sSS6-RN)Q|kcyfpRvo8W*`Al>a(`_Gq?4m%VIEJ!@Z+O^S(E|vf+|ds zd4oA5xwhpLz3BoIR&?Vd<4ovI9aY> zL{~fyBf|lmNzxya^kRz4i>as-<-Bx?VjXI-obAKVAV0N~kuWS3&ALnG-60C)TCbdf ztV4idS+H z6ur14^OCd6atddAO_s~&DUzQhBP>$n>>jLuMp(gEO#+M>)b6`RAFy-_uaaI{`0)5NuJ_maWO$FZ-9C-uf{7P%gGE+ z23YbAP|ECvWV^ zA486nTl`@!p`lI+Jm93a3zD5k?$pa@sOvRk6cw3Q;nc-hF3Rc|2(w72$-EOzT}eY- zQ^EgjhKDgv=2bX#S(aO1S20mDGQy@wGVhg6DFnbUM@YGh%zL%4lHd8#+<$-a_1!ZJ z1amE!wD}{7&A3KYzgZUSyn}Y(p^@k$GteDUE{!NPhhb(aYjnsPF!~Xpx#L z$h@_%eZ#r0dvX1;{r35FRr{pnC1hTBDlaDy+9crMxixUTtY_r0KfH8#x50SPk0QqmvK zB^v+J;%(U45?kUg|sB z>zu)s&$YhTUC7U|XQB~zyCs;s?M0kC8)kJIy$ij#+NlpP#45IcFLsykQc!DH5e)1$ zxmCN!ZdT7%hkqsCOnsnBUsd(*i`^ys9M~E*=W*8OYVLwB7;U$HRxhq@r@o2@Y4D3K z2|uT34J$a63fkm8>*(!d^$IkU`tl5eHk8$}h}$=tmx5cv$|$Tb@AU=(!f@^(N4rK` z-Q@YeHk2g}vCSNtr&O@51>J5jlNSywzj9Zz)}=5NSAQ25L!J+$a^&W8m^Hzx*sfZ` zGAXP?-U9>cW}5{JZPx~%A)ZgHW&;0gh)1L`5L9c}JBz|vWirHl#a(Fe8m@7`6ei37 z_j_kSlrmJOzO1;$VN|-_Y7IN4unyYfmdwp3p0yGNL}BVJ`IP$3|7>>@Ii46>;V>#) zCtJhbk$_fQksauLT>OuI&0UB9-k z6JIABdeyA0*02l;YeEpEr-H(Y`U^t_YlflaT7P{tX9qy#;jmh}U*=u#anrnVD6EV& zxtPM53p3Nmj%&q=-zlP0wFBhU1et4PT{M9MUJ`kS8dF$p1w*~w4W_Un`m-O|bFI3X zCha26(ryvz!&gLS>@ExpLyCtnh1F6puqdpshI{B4xSD{W+5++p3Kj;V%-d2h>?{f^ zMSoalQCNH7KzR0qo)y;;k$!qqFN{HHJaJPNg%whoK{ynnY8=2W!|^P~17w7B2o13; zx>^^^r%h-gkHU%FB9RC|V^r!3|8MPWS^aMs0wR(u&ft#F?qGURZOP*_noPy;&;Hxkc$`6gZ$okgEaR1g{9nKVyR znydze6`mtn1KU6V4%oo6)?ykIR!C`XbNq&$aSB&#`tZX|eSZS? z^IhOG%38S8p|JL;yn~I~rdQ)b*S?Fxl_Fe9VI8%}y~Z_d>I-HJFN6Nr)`2C3cR$Xg z>q6w3F7*j&If0xzl$OHU8+es#qSPm;Q4JKiCP{swTF%DpwDo3+ftsw& z5m_xKEhgtLnSmxPdEvn8MOMp6N`H8vu`9a%nmF!7I0(|eq8UjcXWn}EEn=-?ufD!* zG89qzS2QERvc-<5W=kDWpFFPKi_bc;^e-yrr{v6AF^|XnjDf?;`07D~>0eOG*_Qyv zng1C;%sl3eMVkKEwVW(5`2fWp^P0BF!t~Fs<$RsI0eaKDUouvhWlXY*I)qaJitx=!dgy&2s%g7e)R{i&I9c9FMqD(WQ*r6TK*5L zc(Nm(`|GI2PElV zU(4AuGW8kpr4Wpt)Ie12F~^VG2niSj>0e#T*){T1$$V^;J?65`1Jd-bt>tXm*o1-Q z7vu4c)udCN{*|?y9e*29|4pG^ObDq=K+Y@t;9i<{wHcVx&1e*`G4)7bE?O?!Fiydc5BnW zx|Xxsf3uk1e?-U0&N&C|@5A!|*>Y2!{*|?y{r;Qfh7CA~0~m_^ojecpsNK*ES^C%2 za>g4~FjSP@H{{spoKx)Y!}CC+<>plS7p)6R!Vnic^2_6p==G#u&HcS`9zgo3mYZ|w zUsTI!DHH+>1%G#O+cUkM_N%$SH_ihErMW&2OqKF4qFT<1x9BCg=nGc*G+I_JRsUHv#XRm#7JYWEs2)Hq-gdgWJle<#iZ z9M-xv{Rf$?-HV*q8i(u|^PE$&(AAHF@^vv?yBD83Nq-zlI(65tYJZ=392A!F&)4pq zTyYAAt;C@d4ir^;>Tytz{-G#q_fBI8J4;KEUyS{IbRHPB+_c;`g-B#qSH{xZ#mx|x zM!$ypd;dHzT<_o8Vk`RA@C$L8kzbrVHaZXV*8BI-vx2Fa>Q4Dpv?oUMdroR+fA5_K z)_!+NU4Pos(X-AsZM$jtWvj)iez4&%Jn9^9;R`*SdG@?LMW^BFCpKZ?S_$b0?KMScF?#bJh* z^v(78-z4I7@#3Ep|1pu*Ex+<7#Xo;ih`h%b^_rg)|3UHd3iBRsz5iF-Kjpu=-lg*2 kUGF;i&#iZ*yukhc1MEppEu1$>ApigX07*qoM6N<$f{%th0{{R3 diff --git a/public/images/pokemon/variant/exp/800-ultra.json b/public/images/pokemon/variant/exp/800-ultra.json index 53dd9b55df0..cab917ec271 100644 --- a/public/images/pokemon/variant/exp/800-ultra.json +++ b/public/images/pokemon/variant/exp/800-ultra.json @@ -1,21 +1,5 @@ { "1": { - "b0a080": "e552ec", - "f8f8e8": "ffe2ed", - "9b8259": "b021c5", - "e5e4c2": "ffb9f9", - "000000": "000000", - "bc9b4e": "900090", - "f8f8d0": "ff8ae9", - "e8e088": "ff49e7", - "d0b868": "d10cc7", - "7d673b": "510059", - "282828": "282828", - "f84040": "f84040", - "f88888": "1ae2e6", - "c81010": "00c2d2" - }, - "2": { "b0a080": "d96b23", "f8f8e8": "ffe1b8", "9b8259": "b43c06", @@ -30,5 +14,21 @@ "f84040": "f84040", "f88888": "f88888", "c81010": "c81010" + }, + "2": { + "b0a080": "e552ec", + "f8f8e8": "ffe2ed", + "9b8259": "b021c5", + "e5e4c2": "ffb9f9", + "000000": "000000", + "bc9b4e": "900090", + "f8f8d0": "ff8ae9", + "e8e088": "ff49e7", + "d0b868": "d10cc7", + "7d673b": "510059", + "282828": "282828", + "f84040": "f84040", + "f88888": "1ae2e6", + "c81010": "00c2d2" } } \ No newline at end of file From 65af7a56993e19c29681ca1ea9e9799ed4385373 Mon Sep 17 00:00:00 2001 From: Mumble Date: Sun, 18 Aug 2024 13:51:08 -0700 Subject: [PATCH 19/26] [Bug] Preventing the MBH from being stolen in Endless (#3630) * Endless MBH Fix * add import * Revert "add import" This reverts commit 814a4059c2830e972c348d698259535e117850bf. * Revert "Endless MBH Fix" This reverts commit 8eb448130132ff9eed614a2ec576926814008df0. * removed newline --------- Co-authored-by: Frederico Santos Co-authored-by: frutescens --- src/battle-scene.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 674b4e256f9..ae6cea2dcd1 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2419,9 +2419,14 @@ export default class BattleScene extends SceneBase { count = Math.max(count, Math.floor(chances / 2)); } getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) - .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); + .map(mt => { + const enemyModifier = mt.newModifier(enemyPokemon); + if (enemyModifier instanceof TurnHeldItemTransferModifier) { + enemyModifier.setTransferrableFalse(); + } + enemyModifier.add(this.enemyModifiers, false, this); + }); }); - this.updateModifiers(false).then(() => resolve()); }); } From a97803b99b53ae2bec104571fa3e63e70a21824c Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Sun, 18 Aug 2024 23:27:38 +0200 Subject: [PATCH 20/26] [Bug] Fix type-hints for immunity (#3620) * enable mock containers to be found by name * enable mock text to be found by name * add test coverage for type-hints Only for "immunity" and "status moves" --- src/field/pokemon.ts | 6 +- src/test/ui/transfer-item.test.ts | 1 - src/test/ui/type-hints.test.ts | 89 +++++++++++++++++++ src/test/utils/gameManager.ts | 3 + src/test/utils/helpers/settingsHelper.ts | 15 ++++ .../mocks/mocksContainer/mockContainer.ts | 7 +- .../utils/mocks/mocksContainer/mockText.ts | 16 ++-- src/ui/fight-ui-handler.ts | 13 +-- 8 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 src/test/ui/type-hints.test.ts create mode 100644 src/test/utils/helpers/settingsHelper.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 10851451a1a..e38813ed3c0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1210,11 +1210,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @param source - The Pokémon using the move. * @param move - The move being used. - * @returns The type damage multiplier or undefined if it's a status move + * @returns The type damage multiplier or 1 if it's a status move */ - getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined { + getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { if (move.getMove().category === MoveCategory.STATUS) { - return undefined; + return 1; } return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed); diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index bbb9a823ad9..9315971e484 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -87,7 +87,6 @@ describe("UI - Transfer Items", () => { handler.processInput(Button.ACTION); // select Pokemon expect(handler.optionsContainer.list.some((option) => (option as BBCodeText).text?.includes("Transfer"))).toBe(true); - game.phaseInterceptor.unlock(); }); diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts new file mode 100644 index 00000000000..eb0191812e8 --- /dev/null +++ b/src/test/ui/type-hints.test.ts @@ -0,0 +1,89 @@ +import { Button } from "#app/enums/buttons.js"; +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { CommandPhase } from "#app/phases"; +import FightUiHandler from "#app/ui/fight-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import MockText from "../utils/mocks/mocksContainer/mockText"; +import { SPLASH_ONLY } from "../utils/testUtils"; + +describe("UI - Type Hints", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + game.settings.typeHints(true); //activate type hints + game.override.battleType("single").startingLevel(100).startingWave(1).enemyMoveset(SPLASH_ONLY); + }); + + it("check immunity color", async () => { + game.override + .battleType("single") + .startingLevel(100) + .startingWave(1) + .enemySpecies(Species.FLORGES) + .enemyMoveset(SPLASH_ONLY) + .moveset([Moves.DRAGON_CLAW]); + game.settings.typeHints(true); //activate type hints + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const dragonClawText = movesContainer + .getAll() + .find((text) => text.text === "Dragon Claw")! as unknown as MockText; + + expect.soft(dragonClawText.color).toBe("#929292"); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); + + it("check status move color", async () => { + game.override.enemySpecies(Species.FLORGES).moveset([Moves.GROWL]); + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const growlText = movesContainer + .getAll() + .find((text) => text.text === "Growl")! as unknown as MockText; + + expect.soft(growlText.color).toBe(undefined); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 27ba7a215eb..6333179e3b2 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -30,6 +30,7 @@ import { MoveHelper } from "./helpers/moveHelper"; import { vi } from "vitest"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; +import { SettingsHelper } from "./helpers/settingsHelper"; /** * Class to manage the game state and transitions between phases. @@ -44,6 +45,7 @@ export default class GameManager { public readonly move: MoveHelper; public readonly classicMode: ClassicModeHelper; public readonly dailyMode: DailyModeHelper; + public readonly settings: SettingsHelper; /** * Creates an instance of GameManager. @@ -63,6 +65,7 @@ export default class GameManager { this.move = new MoveHelper(this); this.classicMode = new ClassicModeHelper(this); this.dailyMode = new DailyModeHelper(this); + this.settings = new SettingsHelper(this); } /** diff --git a/src/test/utils/helpers/settingsHelper.ts b/src/test/utils/helpers/settingsHelper.ts new file mode 100644 index 00000000000..dec9e160d51 --- /dev/null +++ b/src/test/utils/helpers/settingsHelper.ts @@ -0,0 +1,15 @@ +import { GameManagerHelper } from "./gameManagerHelper"; + +/** + * Helper to handle settings for tests + */ +export class SettingsHelper extends GameManagerHelper { + + /** + * Disable/Enable type hints settings + * @param enable true to enabled, false to disabled + */ + typeHints(enable: boolean) { + this.game.scene.typeHints = enable; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockContainer.ts b/src/test/utils/mocks/mocksContainer/mockContainer.ts index d3672cb5235..5babd9e71b2 100644 --- a/src/test/utils/mocks/mocksContainer/mockContainer.ts +++ b/src/test/utils/mocks/mocksContainer/mockContainer.ts @@ -1,4 +1,5 @@ import MockTextureManager from "#test/utils/mocks/mockTextureManager"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { @@ -13,6 +14,7 @@ export default class MockContainer implements MockGameObject { public frame; protected textureManager; public list: MockGameObject[] = []; + private name?: string; constructor(textureManager: MockTextureManager, x, y) { this.x = x; @@ -159,9 +161,10 @@ export default class MockContainer implements MockGameObject { // Moves this Game Object to be below the given Game Object in the display list. } - setName(name) { + setName = vi.fn((name: string) => { + this.name = name; // return this.phaserSprite.setName(name); - } + }); bringToTop(obj) { // Brings this Game Object to the top of its parents display list. diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index 5d405efadfd..6b9ecf083fd 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -1,4 +1,5 @@ import UI from "#app/ui/ui"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockText implements MockGameObject { @@ -10,6 +11,8 @@ export default class MockText implements MockGameObject { public list: MockGameObject[] = []; public style; public text = ""; + private name?: string; + public color?: string; constructor(textureManager, x, y, content, styleOptions) { this.scene = textureManager.scene; @@ -190,10 +193,9 @@ export default class MockText implements MockGameObject { }; } - setColor(color) { - // Sets the tint of this Game Object. - // return this.phaserText.setColor(color); - } + setColor = vi.fn((color: string) => { + this.color = color; + }); setShadowColor(color) { // Sets the shadow color. @@ -219,9 +221,9 @@ export default class MockText implements MockGameObject { // return this.phaserText.setAlpha(alpha); } - setName(name) { - // return this.phaserText.setName(name); - } + setName = vi.fn((name: string) => { + this.name = name; + }); setAlign(align) { // return this.phaserText.setAlign(align); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 8279ab72a70..4ade6ca5d20 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -12,6 +12,8 @@ import {Button} from "#enums/buttons"; import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; export default class FightUiHandler extends UiHandler { + public static readonly MOVES_CONTAINER_NAME = "moves"; + private movesContainer: Phaser.GameObjects.Container; private moveInfoContainer: Phaser.GameObjects.Container; private typeIcon: Phaser.GameObjects.Sprite; @@ -35,7 +37,7 @@ export default class FightUiHandler extends UiHandler { const ui = this.getUi(); this.movesContainer = this.scene.add.container(18, -38.7); - this.movesContainer.setName("moves"); + this.movesContainer.setName(FightUiHandler.MOVES_CONTAINER_NAME); ui.add(this.movesContainer); this.moveInfoContainer = this.scene.add.container(1, 0); @@ -271,11 +273,10 @@ export default class FightUiHandler extends UiHandler { return undefined; } - const moveColors = opponents.map((opponent) => { - return opponent.getMoveEffectiveness(pokemon, pokemonMove); - }).filter((eff) => !!eff).sort((a, b) => b - a).map((effectiveness) => { - return getTypeDamageMultiplierColor(effectiveness, "offense"); - }); + const moveColors = opponents + .map((opponent) => opponent.getMoveEffectiveness(pokemon, pokemonMove)) + .sort((a, b) => b - a) + .map((effectiveness) => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense")); return moveColors[0]; } From 1db26dab961451b7185cf492e7666f5044bc2d0a Mon Sep 17 00:00:00 2001 From: Enoch Date: Mon, 19 Aug 2024 08:50:31 +0900 Subject: [PATCH 21/26] fix wrong message key of curse(ghost type) (#3631) Co-authored-by: Frederico Santos --- src/data/move.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index 79e67ece581..24651bacb2e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4441,7 +4441,7 @@ export class CurseAttr extends MoveEffectAttr { const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true); user.scene.queueMessage( - i18next.t("battle:cursedOnAdd", { + i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(target) }) From a46e35b8ddbabf0e7764dc73efe98c7267dd7440 Mon Sep 17 00:00:00 2001 From: Mumble Date: Sun, 18 Aug 2024 16:59:18 -0700 Subject: [PATCH 22/26] [Hotfix] Steal-able Mini Black Hole Pt 2 (#3632) * Still have no idea where Eternatus is given the MBH.... * typedocs --------- Co-authored-by: frutescens --- src/battle-scene.ts | 8 +------- src/phases.ts | 4 ++++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ae6cea2dcd1..4faf3863e3c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2419,13 +2419,7 @@ export default class BattleScene extends SceneBase { count = Math.max(count, Math.floor(chances / 2)); } getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) - .map(mt => { - const enemyModifier = mt.newModifier(enemyPokemon); - if (enemyModifier instanceof TurnHeldItemTransferModifier) { - enemyModifier.setTransferrableFalse(); - } - enemyModifier.add(this.enemyModifiers, false, this); - }); + .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); }); this.updateModifiers(false).then(() => resolve()); }); diff --git a/src/phases.ts b/src/phases.ts index 565914879e4..c50d25acf60 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -878,6 +878,10 @@ export class EncounterPhase extends BattlePhase { } else if (!(battle.waveIndex % 1000)) { enemyPokemon.formIndex = 1; enemyPokemon.updateScale(); + const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; + this.scene.removeModifier(bossMBH!); + bossMBH?.setTransferrableFalse(); + this.scene.addEnemyModifier(bossMBH!); } } From 747e4f9360c9a7f297aab82b3da85dbfc3c401e4 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 18 Aug 2024 17:05:53 -0700 Subject: [PATCH 23/26] [Hotfix] Abilities that prevent ATK drops no longer stop other stat drops (#3624) * Abilities that prevent ATK drops no longer stop other stat drops * Apply suggestions from code review Co-authored-by: Mumble * Add `isNullOrUndefined()` utility function --------- --- src/data/ability.ts | 6 +-- src/test/abilities/hyper_cutter.test.ts | 58 +++++++++++++++++++++++++ src/utils.ts | 8 ++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/test/abilities/hyper_cutter.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index cfd900d621c..38ca4eb25d0 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2395,16 +2395,16 @@ export class PreStatChangeAbAttr extends AbAttr { } export class ProtectStatAbAttr extends PreStatChangeAbAttr { - private protectedStat: BattleStat | null; + private protectedStat?: BattleStat; constructor(protectedStat?: BattleStat) { super(); - this.protectedStat = protectedStat ?? null; + this.protectedStat = protectedStat; } applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.protectedStat || stat === this.protectedStat) { + if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) { cancelled.value = true; return true; } diff --git a/src/test/abilities/hyper_cutter.test.ts b/src/test/abilities/hyper_cutter.test.ts new file mode 100644 index 00000000000..9637a80ddb4 --- /dev/null +++ b/src/test/abilities/hyper_cutter.test.ts @@ -0,0 +1,58 @@ +import { BattleStat } from "#app/data/battle-stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Hyper Cutter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .moveset([Moves.SAND_ATTACK, Moves.NOBLE_ROAR, Moves.DEFOG, Moves.OCTOLOCK]) + .ability(Abilities.BALL_FETCH) + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.HYPER_CUTTER) + .enemyMoveset(SPLASH_ONLY); + }); + + // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability) + + it("only prevents ATK drops", async () => { + await game.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.DEFOG)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.NOBLE_ROAR)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SAND_ATTACK)); + await game.toNextTurn(); + game.override.moveset([Moves.STRING_SHOT]); + game.doAttack(getMovePosition(game.scene, 0, Moves.STRING_SHOT)); + await game.toNextTurn(); + + expect(enemy.summonData.battleStats[BattleStat.ATK]).toEqual(0); + [BattleStat.ACC, BattleStat.DEF, BattleStat.EVA, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD].forEach((stat: number) => expect(enemy.summonData.battleStats[stat]).toBeLessThan(0)); + }); +}); diff --git a/src/utils.ts b/src/utils.ts index aa45c091286..c51ac2b5b0b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -552,3 +552,11 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole } return null; } + +/** + * Returns if an object is null or undefined + * @param object + */ +export function isNullOrUndefined(object: any): boolean { + return null === object || undefined === object; +} From bdde03b0d5b340e256e7800c5571f60ca2aa60a9 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Sun, 18 Aug 2024 18:06:52 -0700 Subject: [PATCH 24/26] Grip Claw now shows the proper pokemon nickname (#3634) Co-authored-by: frutescens --- src/modifier/modifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 1dff041a14e..f4ec6c499f4 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2414,7 +2414,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); + return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { From 098811c0068eca6068b135d0cf16581fb563bdac Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 18 Aug 2024 18:18:43 -0700 Subject: [PATCH 25/26] Main -> Beta (#3635) * Fixed issue with falsy issue within condition to get a stat for IV scanner * add fix setting code to prevent form/variant bug when default form/variant setting is wrong. in addition, that fix code include gender fix, so i revert old gender fix. update wrong log message. * [Hotfix] Fix Memory Mushroom not showing relearner moves (#3619) * Fix Memory Mushroom not showing relearner moves * Fix rollout test * Rewrite player faint logic in FaintPhase (#3614) * 867 runerigus sprite (#3629) cropped static frames, fixed cropped sprite set runerigus exp to use the shiny exp's animation verified all hex colors are unchanged - fixed ultra necrozma exp front variant swapped arrays. - xatu female eye color fix * [Bug] Preventing the MBH from being stolen in Endless (#3630) * Endless MBH Fix * add import * Revert "add import" This reverts commit 814a4059c2830e972c348d698259535e117850bf. * Revert "Endless MBH Fix" This reverts commit 8eb448130132ff9eed614a2ec576926814008df0. * removed newline --------- Co-authored-by: Frederico Santos Co-authored-by: frutescens * [Bug] Fix type-hints for immunity (#3620) * enable mock containers to be found by name * enable mock text to be found by name * add test coverage for type-hints Only for "immunity" and "status moves" * fix wrong message key of curse(ghost type) (#3631) Co-authored-by: Frederico Santos * [Hotfix] Steal-able Mini Black Hole Pt 2 (#3632) * Still have no idea where Eternatus is given the MBH.... * typedocs --------- Co-authored-by: frutescens * [Hotfix] Abilities that prevent ATK drops no longer stop other stat drops (#3624) * Abilities that prevent ATK drops no longer stop other stat drops * Apply suggestions from code review Co-authored-by: Mumble * Add `isNullOrUndefined()` utility function --------- * Grip Claw now shows the proper pokemon nickname (#3634) Co-authored-by: frutescens --------- Co-authored-by: Opaque02 <66582645+Opaque02@users.noreply.github.com> Co-authored-by: KimJeongSun Co-authored-by: Frederico Santos Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: cam Co-authored-by: Mumble Co-authored-by: frutescens Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Enoch Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com> --- public/images/pokemon/867.png | Bin 2128 -> 1047 bytes public/images/pokemon/back/867.png | Bin 1617 -> 800 bytes public/images/pokemon/back/shiny/867.png | Bin 743 -> 793 bytes public/images/pokemon/exp/867.json | 1590 +++++++++-------- public/images/pokemon/exp/867.png | Bin 23716 -> 13409 bytes public/images/pokemon/exp/back/867.png | Bin 7341 -> 3724 bytes public/images/pokemon/exp/back/shiny/867.png | Bin 3270 -> 3715 bytes public/images/pokemon/shiny/867.png | Bin 976 -> 1043 bytes .../pokemon/variant/back/female/178_2.png | Bin 6304 -> 6305 bytes .../pokemon/variant/back/female/178_3.png | Bin 6304 -> 6305 bytes .../images/pokemon/variant/exp/800-ultra.json | 32 +- src/battle-scene.ts | 1 - src/data/ability.ts | 6 +- src/data/move.ts | 2 +- src/field/pokemon.ts | 8 +- src/modifier/modifier.ts | 2 +- src/phases.ts | 25 +- src/test/abilities/hyper_cutter.test.ts | 58 + src/test/moves/rollout.test.ts | 3 +- src/test/ui/transfer-item.test.ts | 1 - src/test/ui/type-hints.test.ts | 89 + src/test/utils/gameManager.ts | 3 + src/test/utils/helpers/settingsHelper.ts | 15 + .../mocks/mocksContainer/mockContainer.ts | 7 +- .../utils/mocks/mocksContainer/mockText.ts | 16 +- src/ui/battle-message-ui-handler.ts | 2 +- src/ui/fight-ui-handler.ts | 13 +- src/ui/starter-select-ui-handler.ts | 96 +- src/utils.ts | 8 + 29 files changed, 1154 insertions(+), 823 deletions(-) create mode 100644 src/test/abilities/hyper_cutter.test.ts create mode 100644 src/test/ui/type-hints.test.ts create mode 100644 src/test/utils/helpers/settingsHelper.ts diff --git a/public/images/pokemon/867.png b/public/images/pokemon/867.png index bd07a7a48375858f3ed2506c81e51befc2c02db8..2fe8856d74df6113ece362f16ddf144140f82469 100644 GIT binary patch literal 1047 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 P00000NkvXXu0mjf&XWG+ diff --git a/public/images/pokemon/back/shiny/867.png b/public/images/pokemon/back/shiny/867.png index fce3fe4e37eaa10d77894bfd02c7d8da05536297..accb87844f1c680b67e46ba371405a992adc6d83 100644 GIT binary patch delta 750 zcmV5(BMe+P6@PE!E?|NsC0 z|D}JX`v3p}pGibPR9Jqa*agmie?PDZ&UyddX7~rT!GZm%V?Es2RjnQSRPJDBlM4HuAJda8HmT((A8Rw` z{B#EfbhRs-=o2*qe^53nmdFk9(M=D zhCK|_cDY4py*!63u(0(_>Cmfr?`?j*UMAWSwuZ4COtVhEI=jj3fp)%wcj`U5UR$9@ zOs0<`=a}3T#(x`Kz--ztZ(jKIuXn=Cps8%e<^dk{s8Kxfd(AYtfz<$ z$1*oRz?;i<$J~?mZZyaf*e68$$;qYb^(4j<&}h*?YF|go5D+*CZ-w$CXi-3lYIjm- z0_ZbZMqo9Z2N|S9k8$x~yv~f(e_CjC^btIwHkPDK->^lEZ6Mu(H+|0m-uhfa4A&vKT$K#o#tv{ju%09~ zMOV7?+t>pNua>{M=p4!O8trMo)$Efxdq}Su1c`VRbY0FK$p~B$Eoj8_jVy}^{eTWL zqh)$M-pTm|%uP(0wr{qdn{6~pF3QYd+1wYb7J#g>e=G>;gKfEV9{nzK$|k>bZNR0v zYr?#iFYRyVkXhD+I*|K4&yA2V$=|qSpg(Se%uAOKTI!riI2_kxEqT}lopqU-7LEI| zq@&S0e}@=TewB7IjhE1ygYI^6i`Df4jdMC=4`OaGXRZZpG`J$IgJ7}7UhG4Z zJy86;Oe&h@SI4yetE5)E9#8JOwq^X*Jd|C-fP?=&e}58ozTs{!=e;NH7KHXR#*6lQ gHMYNUz5d|Ae==DedM}{`Q~&?~07*qoM6N<$f`sjD`Tzg` delta 700 zcmV;t0z>_o2ImEk7YbPf0{{R3rzMq~kuD^E0d!JMQvg8b*k%9#0)R!%Iy%a=nku)zv+9krb^hCtxO(GoZl3U5D`z{ulmBA-ZP+HM{B52`nw@84Nx#c$hBASTAqs6EDU*#pe&7*4Z9u>QCUCSd2O&X+{a2myz`wHNw5SQDalhkUW z44hIDeO)<&k*=^{jjmR|LlP5*%E-KGNa_xjpSKwxc2km!+;Dl&6bR}J4(_e zo|F^m+N@}hH->e$X@HjasGLZMh(a`f$r3}|?UMTiPq0zTztvaW46RX}D1$XrCm)Oj zI;w*!As;N3$uoj3l1s+P!@xdhHQr#+i2%_l^)ids6+dd{u<6lbnd*`V{S+Eq(vSwW z2(p}{5v^18l!4G3SgGC4wrb}BKAV2Pd=6BzH7ajPUi?DV;mYG75Q77Q5Wasl9YW ztVwR9^MJJM>(niylzE=hAWHe3j`UH^Sv&Xjz%mP`ToJ|}fZ diff --git a/public/images/pokemon/exp/867.json b/public/images/pokemon/exp/867.json index 1a9c7572f43..52e0127509b 100644 --- a/public/images/pokemon/exp/867.json +++ b/public/images/pokemon/exp/867.json @@ -4,8 +4,8 @@ "image": "867.png", "format": "RGBA8888", "size": { - "w": 344, - "h": 344 + "w": 361, + "h": 361 }, "scale": 1, "frames": [ @@ -31,7 +31,7 @@ } }, { - "filename": "0020.png", + "filename": "0021.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -52,7 +52,7 @@ } }, { - "filename": "0035.png", + "filename": "0037.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -94,70 +94,7 @@ } }, { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 113, - "h": 56 - }, - "frame": { - "x": 114, - "y": 0, - "w": 113, - "h": 56 - } - }, - { - "filename": "0034.png", + "filename": "0020.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -199,7 +136,7 @@ } }, { - "filename": "0003.png", + "filename": "0006.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -207,20 +144,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0018.png", + "filename": "0022.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -228,20 +165,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0033.png", + "filename": "0038.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -249,62 +186,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 4, + "x": 2, "y": 0, - "w": 107, + "w": 112, "h": 56 }, "frame": { "x": 227, "y": 0, - "w": 107, + "w": 112, "h": 56 } }, { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 103, - "h": 65 - }, - "frame": { - "x": 0, - "y": 56, - "w": 103, - "h": 65 - } - }, - { - "filename": "0026.png", + "filename": "0012.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -346,7 +241,7 @@ } }, { - "filename": "0041.png", + "filename": "0044.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -367,7 +262,7 @@ } }, { - "filename": "0043.png", + "filename": "0003.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -375,16 +270,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 5, + "x": 4, "y": 0, - "w": 103, - "h": 65 + "w": 107, + "h": 56 }, "frame": { - "x": 0, + "x": 103, "y": 56, - "w": 103, - "h": 65 + "w": 107, + "h": 56 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 4, + "y": 0, + "w": 107, + "h": 56 + }, + "frame": { + "x": 103, + "y": 56, + "w": 107, + "h": 56 } }, { @@ -402,14 +339,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0022.png", + "filename": "0023.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -423,14 +360,14 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, { - "filename": "0037.png", + "filename": "0039.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -444,12 +381,54 @@ "h": 56 }, "frame": { - "x": 103, + "x": 210, "y": 56, "w": 107, "h": 56 } }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 1, + "w": 44, + "h": 56 + }, + "frame": { + "x": 317, + "y": 56, + "w": 44, + "h": 56 + } + }, { "filename": "0002.png", "rotated": false, @@ -465,14 +444,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0017.png", + "filename": "0018.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -486,14 +465,14 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, { - "filename": "0032.png", + "filename": "0034.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -507,12 +486,138 @@ "h": 57 }, "frame": { - "x": 210, - "y": 56, + "x": 103, + "y": 112, "w": 102, "h": 57 } }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 101, + "h": 65 + }, + "frame": { + "x": 0, + "y": 121, + "w": 101, + "h": 65 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 101, + "h": 61 + }, + "frame": { + "x": 205, + "y": 112, + "w": 101, + "h": 61 + } + }, { "filename": "0008.png", "rotated": false, @@ -524,266 +629,14 @@ "spriteSourceSize": { "x": 6, "y": 0, - "w": 102, + "w": 101, "h": 57 }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 102, - "h": 57 - }, - "frame": { - "x": 103, - "y": 112, - "w": 102, - "h": 57 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 101, - "h": 65 - }, - "frame": { - "x": 0, - "y": 121, - "w": 101, - "h": 65 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 7, - "y": 0, - "w": 101, - "h": 61 - }, - "frame": { - "x": 205, - "y": 113, - "w": 101, - "h": 61 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 8, - "y": 0, - "w": 99, - "h": 59 - }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { @@ -795,20 +648,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0039.png", + "filename": "0040.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -816,20 +669,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 6, "y": 0, - "w": 99, - "h": 59 + "w": 101, + "h": 57 }, "frame": { "x": 101, "y": 169, - "w": 99, - "h": 59 + "w": 101, + "h": 57 } }, { - "filename": "0015.png", + "filename": "0010.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -849,6 +702,195 @@ "h": 59 } }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 8, + "y": 0, + "w": 99, + "h": 59 + }, + "frame": { + "x": 0, + "y": 186, + "w": 99, + "h": 59 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 0, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 40, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 112, + "w": 44, + "h": 54 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 39, + "y": 3, + "w": 44, + "h": 54 + }, + "frame": { + "x": 306, + "y": 166, + "w": 44, + "h": 54 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 97, + "h": 65 + }, + "frame": { + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, { "filename": "0030.png", "rotated": false, @@ -858,20 +900,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 } }, { - "filename": "0045.png", + "filename": "0046.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -879,16 +921,58 @@ "h": 66 }, "spriteSourceSize": { - "x": 8, + "x": 5, "y": 0, - "w": 99, - "h": 59 + "w": 97, + "h": 65 }, "frame": { - "x": 0, - "y": 186, - "w": 99, - "h": 59 + "x": 202, + "y": 173, + "w": 97, + "h": 65 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 29, + "y": 1, + "w": 58, + "h": 57 + }, + "frame": { + "x": 299, + "y": 220, + "w": 58, + "h": 57 } }, { @@ -906,8 +990,134 @@ "h": 59 }, "frame": { - "x": 0, - "y": 245, + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, + "w": 95, + "h": 59 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 9, + "y": 1, + "w": 95, + "h": 59 + }, + "frame": { + "x": 99, + "y": 226, "w": 95, "h": 59 } @@ -921,146 +1131,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 9, - "y": 1, - "w": 95, - "h": 59 - }, - "frame": { - "x": 0, - "y": 245, - "w": 95, - "h": 59 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 12, - "y": 1, - "w": 90, - "h": 59 - }, - "frame": { - "x": 95, - "y": 245, - "w": 90, - "h": 59 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 22, - "y": 1, - "w": 70, - "h": 59 - }, - "frame": { - "x": 185, - "y": 228, - "w": 70, - "h": 59 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, + "x": 8, "y": 0, - "w": 44, - "h": 54 + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0052.png", + "filename": "0032.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1068,100 +1152,16 @@ "h": 66 }, "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 200, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 39, - "y": 3, - "w": 44, - "h": 54 - }, - "frame": { - "x": 244, - "y": 174, - "w": 44, - "h": 54 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { @@ -1173,41 +1173,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 + "x": 8, + "y": 0, + "w": 95, + "h": 59 }, "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 + "x": 0, + "y": 245, + "w": 95, + "h": 59 } }, { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 29, - "y": 1, - "w": 58, - "h": 57 - }, - "frame": { - "x": 185, - "y": 287, - "w": 58, - "h": 57 - } - }, - { - "filename": "0049.png", + "filename": "0052.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1221,14 +1200,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0062.png", + "filename": "0065.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1242,56 +1221,14 @@ "h": 56 }, "frame": { - "x": 288, - "y": 174, + "x": 0, + "y": 304, "w": 56, "h": 56 } }, { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 40, - "y": 1, - "w": 44, - "h": 56 - }, - "frame": { - "x": 243, - "y": 287, - "w": 44, - "h": 56 - } - }, - { - "filename": "0053.png", + "filename": "0056.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1305,29 +1242,8 @@ "h": 53 }, "frame": { - "x": 255, - "y": 230, - "w": 44, - "h": 53 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 114, - "h": 66 - }, - "spriteSourceSize": { - "x": 41, - "y": 4, - "w": 44, - "h": 53 - }, - "frame": { - "x": 255, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } @@ -1341,20 +1257,20 @@ "h": 66 }, "spriteSourceSize": { - "x": 38, + "x": 41, "y": 4, "w": 44, "h": 53 }, "frame": { - "x": 299, - "y": 230, + "x": 56, + "y": 304, "w": 44, "h": 53 } }, { - "filename": "0054.png", + "filename": "0015.png", "rotated": false, "trimmed": true, "sourceSize": { @@ -1362,14 +1278,161 @@ "h": 66 }, "spriteSourceSize": { - "x": 42, + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 7, + "y": 0, + "w": 94, + "h": 61 + }, + "frame": { + "x": 194, + "y": 238, + "w": 94, + "h": 61 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 22, + "y": 1, + "w": 70, + "h": 59 + }, + "frame": { + "x": 288, + "y": 277, + "w": 70, + "h": 59 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 12, + "y": 1, + "w": 90, + "h": 59 + }, + "frame": { + "x": 100, + "y": 299, + "w": 90, + "h": 59 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 37, + "y": 4, + "w": 44, + "h": 53 + }, + "frame": { + "x": 190, + "y": 299, + "w": 44, + "h": 53 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 114, + "h": 66 + }, + "spriteSourceSize": { + "x": 43, "y": 5, "w": 44, "h": 52 }, "frame": { - "x": 287, - "y": 283, + "x": 234, + "y": 299, "w": 44, "h": 52 } @@ -1379,7 +1442,6 @@ ], "meta": { "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:b8ca75f7f37906e78ed633b32d037b74:92bc79a7ca35490600679451c06105fc:58bcd289dd222ce646aec14ff657c9fc$" + "version": "3.0" } } diff --git a/public/images/pokemon/exp/867.png b/public/images/pokemon/exp/867.png index 689a72694ef90e5dd459be477119a20f39db8e6a..4f8f67842bdd6ee61b2ba0ca2afe8d7ddfd2c852 100644 GIT binary patch literal 13409 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

AEr)YqXKi=OLo?%#>feG5W8?bg!mY<> z^oX;FhrX;tNVrppuqpY;9C2>l^PUnp)pIzowzH4;H{B9Ds3Xma%1qp9pl(#Y(^~;a zV$^)bDT<VZY=)Gfu z4>L|J^?{T_!))wunXhL$6_c^Zltj`%;HnzCFboGGiV&Mh`Bg>kaD}gDi7WDWNEOyr zZTQkdB?OZ-m+?a2%RDF1nG$^u_}I#rLBaRuMS_T7s11+Nv|F!2gRME&YOYFv{@hh7 z|Fp>Tfov2?(jOcM8A9xETVY_$nJ8L=<9mE5tV0TAoEZaovJTSBK-6^*j2u_KZORN* z>%`{;jcRSDbcs2a+wC=hi`Bl0z|9PL-^4i=HTt3rhn${<2GL`Iy~@nQcy^`Xy%)jR zICj-|JR~-JCozwIzJ*Dayn)?sA(EQdg(EnSHNsiO+GU;mKaY-R^-6y)l#2=I_OITY z3v-Kxpk_@<7l@bnUg%W)JIdm9o-iX0+!%`ck#iF+d#gV&CIW1U;(inI;DXa5@SO2 zOJ6HO@y+Q(3l1bspqakx{uR4>d&6nO*NxUkE2g(ed`PVtorAZ}+l0#JsHhKvpIs+a zEwu`A$mL^ME*n~XkipnjCyF?H10{G9JBD7KrnA9?BDWIsj{h1>Pp7j&|VD) z3avgQSdA54>f#?-AjD{(*zZw=Gsd9&K2M<(z)?!0w^-iXt7XP*=Of|f8cn@#JjRa6 zw|DMwF+n9PyAG^=w^7G=c$K{BvkdU7hcpubY|h z9eb~p23E~-E-NzrWhEi4OFV0Ny%a2yS7Va*{yto-R#4$c}=M1p+6oZ22> z_PwZWjjm$@CD%_-iztn+*pd&WDnm}EPdzYgM6qR#Rto<*<}$_p-%wyC{B}t3_YvmG zWfs~t8~HK8gw5)$X~ud1bPV;YAT9Ct9+{KFlIgN7Db?Al6iou;Htk{}>hCQDzg6lW zM(U8lo_wBc$VW9VbDo}O1GduTzcZm*+J}<1p#uhH3|rcABL;QW}24*JFp9|H@w>pU}LN7|<;Ilp;21 zQ)JfXFdp#LEaw+VKTq&sP$Ats_uWRCw>m!6#B1^uMgtY>5RtDF(5$R17pdf8gSL3a zoE?Z!qX1K`PBR_kSc_!KXG`QWRfeBA5ps=adytX(osm)CWixl+UwAfKE57^{ZU|+m zZMA>O=bhuT5wgSEkKLOPrp`zsr(epUeEBdmsEgA+YrIpDFZ$kh2}b2Z~#r=WgNlf$YaV&I&ZlGo2uqDc^2cU-D*w7fx-2I zTh<^KP2=zUhv!gQlGg>j@ZX>w@j%)l3;`r9XFVCqJEz6^i{DSn_*JaImV0PoeP-K?l{*EIC+MM8%SCIcf00SUY zkk86}_Bwo`n_Tqy{BtYkW)>B1VH@HS-uw{AJuATZfnQ8?^3uUDJ;X;~g+qOtY`5}v z9tbfAfG(fKkPQ3vS5`vaIs{j`B+yf~NuySzyPtD&0>k=uozkWHQuvOHH~SiC5JA`` zCMLFUi$+J%XTb3NOf+oWl*{g@*mU~LnlL&&8vF-7)kJ$p|Q z9a5ibu84311`>ps@C#4<)rv|qxteKnT=LBJ*PUx-F6uL{;#X5OclskW?T^wl1TC2T zP)ut8IQu6DO&(z)zW1+FC_$)tP$`W!ZP5ks8ZSUvh_>hyG>*^C0tH55`_RV-*?3B5 z>!XjhpI1^jZ)an<+cfFY7?7+GkDKb}NL7SBHqGnUK7Bd*b(!4~OD>>BopDu#(D-U8 zKWg|Aowzfw8(+OpLFT~$gy;t!%lPbf32L~se2EuU6+J?;WGZ3n7oj)8T(&G~3e~?oH=(FI^j-IT0F3u-0@MGwQQr2Se~hu zV4whRCqF~^wFB=j301s;TMtRClqEi}Uiul8T=>0U z>)c2jp3eXNe1hW-ZYp~4Z5O}1%on==u{_d!3`pB$#rU=O`hCtO4)c5^X-INYDT~Ex zI%<*1qivdpa&g73wxj`7v{VXkR+fvFu(W20pVcWa<=rz`?T&r5A~xQdQLD`&b2!z7 zD91U&; z5;1wUY(MB#CshuT`-MnyKZ?zJv>2XsgVD)fM)dnV_&Qk5v%C*kQW3afr+gIq zbt`SG;(K9HqTolbuiHCfaNGnL)Rt`dM)ST_B%*8ZYSLJo$5CI9RA0u@I)SLWTrjR39;62A8bDAu1x)|zX| zszBM-T^XMIu@Adk*$4Dmar=>qR&4!{$2Esd0P z<9+#m-`x4`WoCz&<$a&i=RChSMCod)5aB<>2Z2CDYN|?lAP|Ph{Ra;R_=ehfO%C`4 zc2_m^0)Ys~?>`u03&A(Qm-KIxP2T9cy?W#O($fy4Xy@$Z=f$l5#@p7xjoHA>&AF@i zcOVGF3{q2)H+Y-57i3YAJz2k>zsp~^z+j?Fg>PBJiM|TUpr&A#W|1&Y*Hr=!G*d)V z29Q16Qa%)MB=M-`&rmBiQ@!uF8T0Gp%^nC=O=)qTfp~`FCCJz|QKq;OdCg9T?>P zxBvIAL03CGpX3NB8Us3Q0vSJ#ABH|(xn_9Rw(|$w&CqM7!#i$oL&Q~n{Y-)NL2Q5e zato#6RuJ?;zDJ8e{3_c{iSMo=Gyb@>AMP>E1l}yKoX*>dOTrmj5T!x$zTEQXQ;#Df zha+o-kYB(WmcFeQo*Dw-^^J?Bt%4omT)%$4hZQs1YFDLm`1HjHJ^&Fz7j#2pQzM7J zQQDf+ggZrat+T!idVXN_tLgn~D~XR5Esru==Q(wd`lPkas}Z>85E%j%Vob8$38rUJ zJpH;(l`v?ON7(N;8mr!=ki)ON=j(sI`|npfLm#|TN7{6nL^$*04SBA+QR{#uJg47np4!oOm#_X*mcfkhbZw1y9w_KT_{+$Gx~ZEq~e` zObJ5%tKp(P53nBzA~=7?J}W`7J7c>w+w@rUJkBYhaXFm<`gZ&BHE4?uUn`E>$*nCc z#&_ufv*xQ>H=C(QHHwNeFe~=dYWY8o!0eoTCa4(ZZVXOEU|SQ=EjD zZnDVT?>&u6-M|CXHXE$gV0^ivenPw)QpI%+t{%ieih>86zc5X1E}b?J!S9|R?RCZ= z6Z}sYw9Hg}*F5S|xuD3+qw?XD!f8)O`Mr(rfqnPY?|3f5n4HeObc%{eV@%VXHw$4Z z#1BAm)l3$&M0vvb*GEf39)G@gDYkgkk~=d+tDS>QjfnYeHBeEuBbm~T%yadUz95j8 z+d}tfjucU@R4&|DA+-4ioQ@?K8GZzc2RysZ9-ZdxVMv^#F9$aafjGv zG_Lx)v5Kn|B8tmpj2Zug$(JV#XrtiOzItB%frqIF`bqHJn48uwkM5?O-j(5PB_s#c zI4)Dy;B*k41P+UmMy{CG*iSOX5tm!liNrDDs1L3hi#Bc;jUtT9%7YFv6ctReDLZw zf}+Z)RL%YJNQA(in~r#U5v)eRHgrtCv{-{y6p-AfVP^4<2@F)&Jb(pK-a#=2!W8w;&6n_lBl|CQaTu@U9;B_@>v29&~HCYsFDe^=pf!Z)}^d!tHp}sIV+g>y^ zH=ldf9$e=ky`OL~boiRu#@B9j7Cz(9TGBS3HmbJ<346cBo#%ox3dITD#wgoG|$0<&WMmFhu!5eytXY z14zO1ICkS5|25*#-iTZ}k*bF+o=-xbmz@x|` zYTR}h9;(>uE(HM(E+d?~l4<$5*d5xA&J}zh$;jZkfN+SY==vM^<8=>0j_)$_l7Xjh z|IC0-{a*`a_FaW4@^)RtW}qi|LbT9}jj{B`DTvHikG7p`nSdRV6GODL_YP&CkIhjH1zQ3^60&R0g=RlWUY&t zPt=WcNR|TfMkcSv=i)UMpW@nJZ~c^d?CL1)zM=lE=TN1NZ`^z8uCv0lqu@MhQhqv{ z)UBf#1z)tR=V}U=T)wKEE1D#_`<-4K2KN^4;%u>~Z9duVfo#o{^zSTzb-r z>1irW9vC$+H`#~~wI>NCJiMwRNU9HByVXlcXJBcUies_e(wh?4XIvBrCP|R(Xh`C* z&~M(n5LLYDUgzRc?zFgIuqWqR7cjr`FvECYUjYqu5ogwllRF33jyN+C5y1KL(uAlX zq^~eW18OuhKeLsCKaYK|9cY-5DP-;SUOt*5Q#Y4AYOm4Ix|HrbTR)_lB#p)opNv_@ z^FHVoJ3LpY8uFDak}4nT9~FLEal)@%(U}0P-;x5|{OzS#-2A;V=T;V4lqNH9=Wwv; zzIow3H#KEs)D!EX6pfu!+r1_USM3+hirxSBv*qC1c?i@bL`|7P(}oE!4!6WO()&mx64_oO(Rb$8LsMtg5xsVmD~4 zt*_ysc4B(O3H;l`;4T*u*&C}#I|r+1aJQ*ksm71h9Tjth_KUNOm`rofM=ipWf3(D- zUm@nxzSp(EYeDseoJe-vX$i9w!B4MAFDONAsS;*Y2=c131kNWob@rNtMxwog;7+^j zZ!<2U4aNu1UQ^B52##8Bg##^n6Qs~k7BpJzL(MypijDToi>j7j z`j{%Cih6<(4t>-b)_n9;@17OEWnYDY-gSw*xnK9yu-Af0dyeGOBy#;x+t&kL=Q|x3 z=pgv+P}XYLXWWdkq#7c``LV=YXdOq9kyFxl!w*X2&T)mWa=3x5;kwDO)6<<};>!q2 zbb2#B5C8Wno9z}?fzRt$sj`C74pb%>Xa{&Ki^)=V>Uh?7GT^vM)>|B3cg7t{Njv(% z?9gWBis$Fnena?6Nr>~r?zUU=N4s8Hn#1Ffr^ z{J}KhL>^g=!?&(C6}f?(*W9%2NekjBMF`y@-?V#l%E zA~_yY??kdGy;xfC)7dn{d#M?eBMg<;oA>uN+{Mpg4&BExpY_LnmTiyj3)|M%-#`ZX zg5VX#d!muvO;$?2yqKh4N+gU}!g;?-ElccyCSY7Fkr`FC)T(hin9>Y$7WjGHd=a~B zCQj6o+SVn}rZ_UnlPM{sSQIwCofabg{>0;G z95#adPJB6RsG9teu0JcU^XQGk_9Dg@XFpHBs}I%?8LzD?o!VMel|C7+*89M5JVZNw zsW60ur^DAWgl!H>0{il3It-kSsDGH>dh^A|A#&3}TfWH)p2WiSYvMj?$a3&0)U}zX z-;Dz=r85FJ20^+z#v6+@Mx#V2U#1NEn^+#Cmai0Tg@k*V@5z(QKTEeSKZ37@Bk=V z3QG@?H!sX?FYqXL5W!|w&VBi905j+!OygoJJ}BlNFxn*V_Bj422JAnnR-DTT3Ea~^ zmejAfmZ#P>JmX7O4N1QO(jWIz6dd{m6)NYZ#wm@s(XY4oMne=7%l4V>i4hblsY zUuz-s1qYw-z$cUH$`PW|AlXAu2`W+O!@_hH@D9Vj&7gB*I)8Lwf7AxWbVEzCE!X(h z0>W+B5YKv>dTbm_KTbuBs#eU&^ZcFESEn+CeF7fkC!RciYO3!V-dfnvzR$#-s@La& z{BkJ%bkQ$b7-Z8~0v1!2SB$8LFMrjBIZoqg^&vUy#-i*C(5tgke7bfeEQXe|qL5!0 zdtN7XtDPvB;Rt9b9Vz6WRtcg|0aI(q$?%8Bn-?<%%JrOsyo$1(q>zp{4oPDd4Ad2< z0qi3noPphSw1f)ZjW!sot>u07teMxj;){{zUL2`-=!kLo6z?Yep)WO8CW&Cx&I%KdzGe`y~FW?hl5?^zh1(k`h{a-Yz4;xP(4VV*|pdWagQ&&ed?A zMQYvKVjHE&Jdq#iuyV{#6zR}=1afz?o&%b&sqYIfQ(Sng0k6cD(!N;x_QFmCQbxQk zPX=e?O)#N)n~7dqDce&o5&pwu7 ze}?fd5&r%b7VxlI%|oF5_EkfzhhblF@D!`jL)bHOm0|yR zPwe!7Zg7nq*pI#p^V5S?a%wcx(yNEOvqy=Fw!EKN+y@Sr$q4UhxkusJIH_r?m>unojl^ zTBjGyAd!pyZ!PmAZ&25@iF7&(3Bn~;xC0GZ*+>1f7DOB*%h3u0#rdt$LwE}1R!F~pJn z0&S~1$qOVZwo6i4b14WgSwX_5r?6A70t0Wkhcl%&*`ZNYiOesz4h23^cQsci!z3<5 z6659Uqzy3};nui0iVeji_aSlEDqn|^erd|9&AUg5cCS;*%uA^Qwl~?lSqo~4-hS}G z$KpRmy(r*6-!@~}HkIJ&;?a6nqYFL*VDrVr?NH45+k_g*VMf9BSYO}8uydKqR__#I zmW{s(L@ZUInfQ>oCmeYj#LvxAUI_GXC@%m=4@i7*LA#gxRXv-+s|6ZCrm3SK5N?&L zy)54|@Map`x$K$J_rJgUvX7k@q&%Bm7MjTz-boW#aLT6=UeemrdSYrShryztpQ@R9 zfK(ggEPJ66awGy;7VOJ0#oHb16?I;mH9`VaVURoj`X=xR?d93l1x-$3_N>}1-V2zq zWG}B0bI9X_T+`>C6*g3XNr@;?w?A+>2!MBdz@d?$GSN8U8yfg=ungC|N zY+Jw6b4=3j3j1dNJZGgoYYt}|Kxh6I7zwcJbgaIo7(Nl|$l|yxlh#H62~X|Mk6s0w zee(m&^}QkOBG=QQ#9j#slDn24DC`u7RZc5}$A6M>uXdnwkqLfm>Y>9ZIhsI#Od^sW zMh6_zMi-YG!_%oex7xG zI3Hyt5Zt%Q{f3dJSh)pX6K<0M^0JXfLQR2I)F5^5gPK(C;1ZW&(eDH#ul)u135;B! zfFay>%g3jq-(M(%_UX}lHF%uRpKPVb8o8owhjk;Y{tJT}oKm%_UNxeHJ$o`pZX)De?~VYw#`zb0 zISY*!1Tt1R4N#z`S=h~=v6`{c4h$p*ubP!@=s}6Ko(E!6W&9W(__!amv5V?yx?P>L z63=F-9I>Em>Q=JH4l0!SYnT(Ba?We~FbHT|8c1X1o}d$D?!+}AhqM91Tht6$o;zA2 zl0YR0x1@ZzhigPm1C3z>NN~z|1e`?Nm_XG^orf&&p2tK6_Y&*uR?@CyXhpIwD$A6T%KAd9`zyJ7??$SdplLyogft% zKV5i%(*xI`eq{iEy4lHBv0%E<2leJp>d8p(B2gbwHxI`q19unUWjri=fDvxI(%d2!PN^`xm$P@+31*jKjqK|()=cY z=?`_WBER~5`re(#2Y#mRa>V~RN9L%SG}s>{YOv)6PafAatg)q*S7Ur=eY8W+g;j3a zU}PN(>B7AFo@c5u<*Nuld~Wxs5B63V{WD*l-8_M7c%Wj`R_p(d?eC1MPNgcNi!oC_d^n?fmlWh~`dlblqoonT^yqT*-78FK zB;yu4HqH@m&HK{_kp?k|-yOe`CbBFThDf#n3b6~xKe7w;9uK11`S$_^gZ zX!~PwnGH?%HQJt4cB0t61YtAVF+qm<*Pm=g6F>$G33Nf7ByNGf`NzBt_rwjY%r$dt z|J9vKpvIg)`~*o$IgNP-1L!{*KYYCSL0nw4xvj*>nHTSs;%!_8K&Cf#}xr=t2)iBu7nex2CQahWae#MVFi@E-O1Mz|z<#=$3mtxgXJ)p~ahGM>q zeMt@xJtT^f2UFV%DqFP6z+WkQ3~gUrjmlFseNwfL0u^`%VaCg=zGPVB%)VPE&{gjb zx||=3sa)I`G))PrO*xipWlhZ_1o)DZC-2peC*9(dIBX_zR?*~onI{sIPx~PA#_V-U zq{3^Kv!@mv1mJX2&$-(xH;@S5`SEm~i-(IFagE4&n_^(WY@3GWdkbA_^4Q6|JkEBR z$J||*Ke5gUNcIn@{aIA4tjaKlB8vzHh{>?|$-;kNSG7VLjKr`(tsd9Lz*&gXD}K;U zu=KBM5gUUPAih=0n0~VwJNXHQ&_~N~eWQGnh1If!Mo?HW(PDRv{SzAY%XOFx=Y5QN zShq|oEjRUm+=+$kDh?jEj`7uqyUKvoGLNa#I$`~v*RR9acr+MIb$L$aHjg)R{%A-k zO&I+C4`O5Zkjy#fK4K;eTpC+j1^_P^w$|3eWOrNy8le{(hrl;kg3&{2#Af?DK z@VMF}p3Akcvg>yn*9@HpYs0h3$bIE$7!x?C6c;3TFa~6}+B4GtB*2Z7TD{GaJ}EaRnBAppuI$o)vg%nR|(M%9kr3X8O<~B)vf{w{By2I!|o!7;zRV?D)5an zp%}7V!xEUYRI|gJa@A8DU?bOzsj?z6^+)PYaV44%Wr_#;tz5sd68RGC@M}LybsbF$ zU~`5$+agqqAGj`&^<}c=O5|ZbDJ{q^s;Fu%(&ufG@>v_pqxIDuhW(xqa)7V9^C0IH zNjgm*#aGOj?0CD9p~(@R{+p-bnN#Iw>8k)&f^!gD?#^Zq*}FDOC+W{+^0QIz&s9OE z(E_H+l-6m-un?vh`TY4No5|3#1ip`kj6?H(x6$;CA)>s#!)c=gUGEEE>yQ2-)*OBc zM{}6$EZ&?Nu;p^FtbbRVYQ$O}b+YVoNbdWJCBitSw_PUL*K3bQ-LLVq>V#tYNWU3Z zZ&oxFk6R@wOG{vV9lS%X&#fNblNYKc$Si^@f9)ip`NWkUV z$a^Fy!bAhDWns!{Q2YLkcd%q)P37;luJ>km9&K0ARahX2wlOx&4fSr>&suVxJL{wA zc(H**pF+q7{KH8|hf}#A8|$mxs-zwesN?IWM*kpP>4);@)d1BxsAs zcMTQIbsny?h6uhBe+f!jd-G}rBNhVQ9-K9a0$0s`7Qvj@cV}u{v(a!q7mjI{6P6qk zMoiwWW;A^l<}Ct3TGVN@rq5>7eoHbF+`m=94U?gEsREC3u^9_r-6zvFR=1UhivWk* zafc$?J!iXo~-kv396|j5b5`H!g_>Wap%vd6L$7uGf_}A(*+Bs zY)bs}aBj(6dmi=-yzzbc6xJfe-KuW58&!v;x2(d{XYFx5QCd<69xnF=AzI^%itpY4YStlECJoR(xwQTEQgc`Ek zMD8h9k!5&bT#4{N3?R@c(R^E1evAi@*;c!(EB87NpSj#q+`8|l@6v@HvzKWO!i=!^ z;uV_Ae&bvEf2@F(d7%cAv#j%Y4*L$QdUHB%DBcyeV3*)8&;ZX`PhssSKrRQbQpzo# zh~da^Yyf{CDbu{_8YG9XB-wAXNO|vet5XwehZc3WD-6pSZY7imZ_^~h*!WG`1s|1= zWZ-B-mF}0|m=-!rQZ~P7cTXO8Y~ly!=ynBHl9k>(iNpm4k2xbdVXAE^!X$P zU;@&m7M^Rmf8yKfVvEdLO5K$9K(IO)4mRmUX78JS$qmpp=~lljc<{84B+0xAWVXhe zhpMTwX}m_LDN}sq1YU3ZXp+P9apZKzxGg26;;Y90g{J=tg>}S;UaSdlRiuRT<;HYc8sWBf zIDGREKo+hhr78d7LUbNn(uEeD^1a&11iSmUaG9>cZEN=+ViEqm_d6{^yO5Qyi`ek`E2oYdTk440KO3DoJ@0pg z_5MdE1Q!bjvONHdc>v08u9@D!M__5BjBZqB%m7FR1RL@7<(El)aXgVoC1S;XKp`8K z#l~TM&`hIc(CXeIWvaMn-tupM;ku#88KaIbKgkBpSXXfT&=)n}z1PJC>O>3G;R=rvT57FS{R+?#RO>Z-C7%^)M;@om)= zfoRoP96_r}#4iH#+p@LKSZl1dW3?ysaDO=9?;=-DqxKK))A%dp)0p~|gQ*uPV=CH5 zD+RN(iK?H&VP#j7*rQk(e5oW`!3~mb-+4K8HqR~(y#^rg^{JizKU%C5X&weV@KUOLFf0fg3Un|rE0@4F3qQt|0lx9CZwL6~DVz?a`2fgcH2zezGXDn95{~`8@ z%HO(*-S|@KI1IlqR%C%W2*QzAgTs9N{Bc)G|N4XGEKY(7*P}P!9Hx%!Umr3(`C*ff=WM_h{T}a74A$Gn`qL|-| zX$5GAajkkV>}*%AT0hCHkv<0dlt5*sJ%NC3`QalYR19kcWu`ruXmEO=Er4?QM7T=R zzzA7ErCLBOH;Fq8bgez<8>WfXFl_>ewkOr^mGDg20(BE0ls?8YUKdUEZ!ey@ps{nwfnSUj8=9 z)uoqXAKO&@f?iOqX<|_qj{PP98~rv*y*5SX7rwp6%`J`WcR(Yr3tY2hcksYdaQ*!E z4*f3(n>A7)2BuiqQ-ungm9{{?`b+7FUF3wqybKJMyQ0?k`DOjUoSwLVB&9JX=`&~8 z$C0X4bUPD9uGYPy=o1yRc@ z(ba6YeEB-^)*oT8A%O9MW|Vx*L#`@dXe?R8Con^(@g7_cw)b^2DL|hlNZCDl5=p3# zk`%S8Zx!0l0zj0@^5%;(&!-(6mH@%-R>h<;y|!J0S4|Rz@9QsJ3f1T_67-Pt=7a-FTsAiRY+Cjv51YM3PmGwO$hOZ%> z_4r4iU?xAc&;55v#7e|Of?b%GURPToP}vO(N= zAUt|S4iFgf{R5=VOA)fjb9!JF*7^W^T&$G_tFVgc9&E>i)`puW}}{s32Pf8S+p;HoQYdW79PLZc9a^l}!Yu$}+M7NWw2gyl?Jdt2c{@ zvAJiQEU9HoSQzW+eBpO-G8Ed7vzDn42-@XhZf@RoaT(ad3kneQwy!T5!y39~xvp5O zU{=4n@~qe5?Qr6&44>~NFGp8dC>kjQsloXp5OPA*nabh3vTRAfWOjADW*DH-#cK%9 z88NPa=WEn+swZ(KY1)%;h!CMF)5zP~8TJSUN{KiYX(N2J;4A{v{v@3WQIWLYb-VYE znG`tl_V}p+8SwdOh+x2ufDzD^_#WhFzq*O`chC`P0(c6bL(&9%7%nc z7w~&*o1y?6mdMo1AygNJL!42U-DXok88Oc!0{HF|02d}Qc! zv<^*C&7PX)Q>3n<4J%xaesg`~P=BMD>qp8KLeyle#X`qo<8hs&r2ScS^+TdF4V<6u zEd&0Gb$tDxpeif})DuE`0_i%l#2AHDqg>7i|Cm%9b^cYLbLy#t5%b62KO zE!^lTa|;Z>U2M87$LVq`1`W->{x;uNMHwi3NQW(SH;oVj9TP~MsUzQI3$Qj0y*F@5 zkU-g}W@iWdj%Gj`361$<&-YDKM4ysug`&vM|866S_)uUU*D1coq+OUk5>k9RNx6mA zL|IbOn_s9C;8aSon(KEmF z$Ms*R@}?3`&G#Sr{eLA>moMxzvG4CQ-u>y2anBn2O@D{v`J#$ei~J2EO}0o)kjU6N zdzYCcJJ>)Gv)`+^cj7U?QEW;2`3orXd2%q9Jm z>+hn^T5gtZOnl%YW~U2oRsGvWG09-GKqvb^a{|-*K6L-4gnbEGAoQ9GsmAmk#BFuZi4^h1)-JOo{e7YZC`l(qgBnCMHetdrK^!_GFL%~S`?XrVZx*(^ zzN|7!uVL?lt0ti{He9dt4SUt@H2dPM-wnckl*GG?745tuP{Sr6Y>9@)qn5oP zsgDO#Q%c18)a44qJ7DyKCZDHiA;Q_~>4rr>xzi!OIK(=+@5H@7HqMasS1I;&(bS?z z((aNyo{=I`A@TMjFF{MPoiAX&Er_-EI(v&}>ZW#WJB;aZRj^KFTg?{R#WP3vq%JE= ztZ{{^sjDz4hecnFO%Z6dF7c zo4T#v(&NEzp3{X^sI;qO{>#4B0o2*niyWF;_bghB?wk!tE;!Mt+P!NzBp^_>{a;vH zm|3L0lN;ncUb{A68?gvN>I+)tNk3(J3z$wag7tM z<|2c*RP0Rxb{L_p0WJ7uc&Iq(nUy28>%Zq#H_k$%Cw7e5Ku6Fj{`LMJ5<{Qc^0&V` zAY|K{#W5s6F+~0d{*}ufB3}=c0Rd(>N&rC+)RO9r$R^U7eYSxI)j~N#6K7RE?eaPP zPbB@n-v2{!#O1w>a4KDW_T$CA8JqnYFOvuirM9}ZP4l;9DJ1%TZK}_h%vhl@_5=JF z5H-_`lK0O&7SMj>87E1%q za5*o=xWB*MNzFC+B|B-2nrCvyI`cPn&mfmgyTF~b9O{l5$i3lrHEVjw#H2i<(banO z>kjCh^wPqlePSlkTE~dinktXK(t>IMf=;*wvI0>bvW;5e;;BVids9IayxQX004z3^ z!1(2D!~wHPd%-3>>sks7)2(B^2QcT>S)!w`M63vfaozpk8ocr{O%Z zaXyLPNzsHENg3AIAIJCfo$Y_47P_0^{5o~n$(SH$9h~xbV}R=&y^e;lu;>B$XD3#|8QXgcd5D#t9qX1T0lbN2BXCP}+bJ({m8 zjzOuaZ~Bu!zr+6p&qW?ZwAjBtlr;5JYEjSrsOHHuvN^vWHbB4?3szjyBoQVd1-V#> z0f(wK(t_m28&graCj@Xd_KZ-zy3F||ObvMLa12}Fl&VHHeX(g|JNTvg-pA0%Mgz}6 zu?KUnZ@uf$uQiZ1I@@4uX*sC~XQ1?o1yufNYAw^Fpyfqo4MY{uRu{K+*;vO;fXOyS zDi**U@RSIBuoe54j&q6jjA08u_e_J3wY6xkiI86xd7`iziORb7bQv zD!pMvpJ4Ut-0?)nx~r!zOz&h&<+#IPb(_f0LDO9$yCun)QB zRHK$#^`=Q6D&=GDVtQpvjiG~^-|*skK<}OqBHp_ud*>?An*hTQ5tFeO2&Dx)A)1*5 zTWzv3zQ$}jnCJzegrq8GsM95=P>%wNC0TgZ%RJ$~w{rK^cT-v(Yv<3BE!bzjC##U; zv#KIF<&yXFd+eOMb9wXT$QIA|F1PMs1re^2Ppd^8_UB?}HMmwh$pOK_`MnxV7#q@h zwPS=~X6 zX=ew&JcM|Agxs9}I}fSZY-urLr?<(|iG%eaJDN!PlOCtn_{medjslYkdnNzSD7%1f zPdNc8R_^7L)R?HqHjVaaxl)kq{$O7ZAyjL6r-c-VQKJ{kmb{sjW&Gap@3;f9|MK?z zrz~ZYbm5`H`%Z?Pe!PO=uq-{jSyT1lIdj;tOSO}`B4#uuqn1y#Cy1+2qc znzbxg8i<~NcVb|BR7$7VK;TKgjMPmR$G8<^x*tn&Jztl0X`7Jy>Brg6bGN_QOj0AV zHGz=FkI_)v8!x8}dzz4<@XW2JHAh5bSlZT7w-I62Ki^4(3h;K>1KX7KdK*7H*+HH9 zrN>ZC=K}xK)P(Lgbq|u*n*RxEK6v|khv~q^Gu?=PUkXyU*DRCpb3xW+QXYGV-_x5) zfD2J5ry3uh;+Myg1l>*-_%{1*o#sWy%USH7gw+9#Uro7&SR>!GvqYkA9*Z+ zfX3mjMtB^HlnM1~Tsoy_F^B$u{_Ak|1`WHUaJJ*eejeGkpQK-2U0v+ZD*m^BmAjcW zXp~)t>D4uxZ1HwEQiCHokY@9KqQ7~Y^Y+p)VP=G#M<}GK*sJxIj2H!Eu8=Ora(=aE zwV!G!v%^z#2K(4W!(235kAfSSvpoi>g!#nE@Orf5C&pP$?A*2p zFLVR@6=7={~G+aA(4)YUs`^+4nt2WxRFA1(8H$lro3w8Geb-pi-z>8P>;0<|F5%1_l9g}zx8Lq{4NF<>}Rb**l2r9f(cplC{)O zUtcJ2=;-lU_N|pCweLPfdSUAIRXf-);{)LFFLmb;Mea>q{Qf9NU2T&xEx?vv-eT+M zLL4FGlfAk*@^7%kERLuhQ@7IVr!0tv7yo&jh0zDTp8lG zdTq6zz|-(~bwVd+(f>@=QZ%Hap`ii0vyMFyza597;?*y+(O&|0no)*)`%wI;i!1W; zmQLlW&apo1+?sVWKsv3(Xs9e%sQ&lr&@YF&Qq<2qCyPq<{tT?Vi)hwpmMP|#bKWBy zs!^9Z9WQP?16+mq_7t$hQi32{tNi~gJUQ1G)AL~9P=?}LaLGHN*5TE$1$AbCu8K*@ z05}p)+R2e{Ur$r5gWFRQal=~th2HezrsM>deaE)_#WuGXqr4RQFe9PT;LCP+b=N7( z%dDIS+3)Pylcm9|a>vy{%@m@(SOj>dQCY*^2<+?;FC0I@>i1G-H|73yDNJj#@)c+J z{Ig%RzHOiIGm@CA=uvL2`wTH6s`c3c=z>J&0UvPtdrxJ+##2}^nHDetOg->gCM7>d zeZnxm>SqTmCl?2-*-NwyrwLry;Dmms+eje?i+=o7Igb3FITt(}n?0*N=qPpzM1r8|>wm7q&hFXe@#m6&t5^ z$yDWouP`sWR>IMZsW(zQ?M0eCiEJjOu%5;RqBKj7&H4Ve6?Yz9 zSYid0Ci5>8UP`dwjy@saW0UYz6R7?3^(XZG$#oLrYeDIMnyb=F?94f$PAyU@>VK(B zSm0R!KmhI<^C6X5IZ4t&{sWO6QTCZmp$XR}z5N6R+sW4%^^}sJQHjO*^Rhkm#Lw(g z>b;R^%TCji_cYAoMxZUfkG~gPI0Uipus)X&(!2V3c$)t)?D3_qg(-_7IYhVD0`m=9 zxy&4d3v1Dm`Nq_fk*jchYw&;!1zbn87AcSVMG7E+V#|3HrS&mX@j;Mqv2M{FX#xDN zg-}v3*KhR}HqS?8M)GRDV3u>syLlI!lL6+Uwt4^49(`!&a7`sArv@?{MGY*2BsCVS zHO0hz4EUP?QtnXb26;}KdvMK@PhsRE+l@BdX^G<&3fzX=9IrD5o@u&N8zmFJJ9jrA zwhVx2mPe_PLqepdSB7omcz?ZjRzM(BZtc4i;PakR?;Xu07C6Ea3`^AOBFJrKYcOmx~8+kEEl z0LYg#6cs->rUQ%DSidmL+}B+T8Qy+Q)7Jo|#l=WcunN~m+yW?rhY`WOA`NDwJUte| zZ(JhM#5V2S`kxe_qPOk>6T8xkDB8%Kqu#Li-AYjTWqkxXonXLY?f=cK9FG*8Ye!7wAQTt~r0w8j(!wm)Zp+Y+L4)5i%Iyb|2?^6+DgQow> zY`hw~G03hFawQ;<-}a&5nyD(K2bNn8bQ!2yDWbwIf};0fBc6-zv=|M?jF8dYXKSEB z4vPA(ax@5N#RuM>s1cBoERYztboiZHTv)kTbazO%Gu?Z^Ac=}X_lXpr5o3z*5UUcy zksl-dK(976Aa2@mR2X#EWi--liAluH0;~>n9casVXyK14s#GUCF`NYNQ#FOT=JV&5#iEo8J@*>?G@2^se%~0>B4Jc5zaor1ZL(felW*NgiJ^D56 z;7L3AJj`q!Wv%YMCw9cxPiJ0kLhiiHzg1)ORFTb|4@IKl z(lWXYNMxwI!WyvkC7lnFtAiHUjh8Tjr|5K9=435!|6OKV_yrkzKXVk1e3iS#3bD$l z_c5n{uj6vX{D;2hV|Wt3+YeF!sR5PEd%H?c4Z}xGTx$D;n>k?KJMGOL4F8|zuaEF8_3p5ElQ`;l0y{-VYl3%&~F)Bry?LAy%x3 zcT~bnT-#9jwM*fFZ$6hv6woJMC<6yJX&%jgMH0$N9ThYm>zMlw>g)8vFdaZ32Acc- z3vh2U2W&(J1W7kHHvuI(945*-Zu=h;GZ{N8Nv(eX;d>vA%7`~ zE#%!DpP(}t-ez#AAj5%jSh?1O$7{8DdUQ5a`Z8@bm-{r`7Mr60?ehWXEYNp)MY8n~ zPyHui+yVo|?WH6odUYgleNx+DV@*vX*hzt9jU7_?vy|f7cP6M~ed1(tcJB&q>v(tE?aP@&6GRf_Izj0f3=6@aE1A)CuU*_2)Md zF);pG&%(6Zd%>6~r-cCY@OEN9CkAPo;iZZ9uxSV0XsWSShGqUDuangn-cb1v8Kq}d zEdWoR`~{?0S_zYQDPliBLy**axAF;>&I5Z2frTMflbDJEg7C#_0D+5MfoEC@Jjtlt z1V1YO35V4iH*5p<0R97Bw@6Pca{}7)7@oMsY6OB^uIht82wzZ+>Sn+aifJV4g5%wp zUQ$w$s*dEur$h(}|7n+FcdOsd2HgE}2Pkn;%9Y9V5V-g;hbaZv|D{fIfb=%yJeiH_ z<)N5w7HLVFaHVFm=UM^j_@XJ9Ge2xzu;|&M&>DUnm>L~a#sWTPc*A7rB*$i|od$T1 z&dDMF7~kRiatvSpf10@RcqqTOji?zSyHc{RGe}8Ip=@D9rHnDwLXBmt*}e>8QVEG+ zD9IK@7=xLy3uVueWz>u{OHm_aC*Ehi@9+J6-hb!w%sJ;d&$-Vz_jO;_B@Zc`6sn>- z@4R*0+=$wfsFZ?%;~9MSvjtU#dlp>H!l^W_GkgI(<+$bNTC>ZvUf(|e3qVsDR^<6n z;nLuuE#9R-%z{iCMR6au7D(OxE_W|cBgO>NdsfyD)+t{QYQMFHWxX=V5I@z&)#loE zqK(<5NG&M>GPEavI$~7!PY>@GbQG9Gh%qL+VSOTkr5>;U1zk?@3(cZEItt{OR{t-9 z{hp{?{A}o2EkdKEOHa0cAN4+f5<=j<*v@X+p^aJL1T*lHRvpgH;kfxDc}WuLC4f#8 z8E%e4KvV~mM_XI*6rp%4^`ss@fSN94(g|`GJd@9HJ zI~Pu~Xin};4|~H8c;l&{n-6GUoC{Blb^xHrLB1B{&c+#4Qq@p+&gu%1o0}|$EHO|)r zOhCw^I(5O{1(Z8I#iq1df_Q35BCO35Rsu)DPryKs@4%`}cQ8UMo{1u1S*%&(15Z-n z#sz#wcNmxM0_t$qAyuX1V}1OyQGL$a>Nw1uL6A6F0zXA>@Jfi*Dd$3tN?-m-nPvka z$nwiff+GMm5>j4MoVLPMJy1hFRuu$42Ovg-5k=rBKwACiFx2*76f5u^$1xDyhS6iV z2sd^FdfTo*Z-C5bi6N4)#+wAxw#w}KEtmO|{iDjN4QYhuv6Gr5_pPjGEi6t0l#pTr!=7KQ@}d;l;iC{`R#!3 z=$_3w+*tOk4wfguJqk4HIrSUnivR`H*EO4v|Eo98f`tnJd7**nF@N;wZzd=QWl4*q zYv*I!s9LGn_E?$Xe%oX?K|rWyPxCzznO6XcXT0<>T-r)-?}utLqXjL=8F@Qt*??O| z<@I%8Vr+z}k!o7%TG#})eS6)BW-gXHMfYuwDVg3t*(ep^H~h=gu+X_1QL1BqD9%%X z&pUl*nre&1b{Zh-qa2mFBU|t>#I+jb$ zfFwhy-}Te#STeUn^W6C;OACHJenF2eX~PQDVh)Msu64V#!r{Bydi}4+hK%DEWziS% z#r}RaV~jXyHrEEoQY(CfvA5*D9nlQZ&Bv8HSZ#5uwHp|vf0!%T8CqV{A4lN29+HaO zN&gg}#t==t(mZLW%gqo#1Th`kNlP+Az*KN>4BmlSB2nRWS3&i9jl`Ha{1SCU0XecL zRa|nsU$;T*vL7xc$@)ob8a5zI!n*0XV8(st1#-=W@$Jx6t#OSR01uCEOJ^ufq|7v# z>$DqWrWNo*!Pb1Ymi3q4**SLd#s+#V>2&UJprz#HU4@5y_=ilLTxlshkb67Nu}opc zSEGz)&n_5%!1>n|zybjbZP5(&L+V zmNTH2JW5fBe5yOm%lVGFucwH?K^$22kRm~#HeyY!c|}T-Lvomq->>>`rs10Yzg7+! zhb(O6*d`M0vYe3x>3hxe^w#I8=l|Q+^-CZU$DLno{DxL|9!6iHR|<|w3D&z*vqSI< z+ZjEo@xX|73Ao>VMnoQdIEpZfJntmmCx5W4mafkY|TMo<$y!?4#ob(Dt(y@E~C-ex9dTb|TL8Lm07C)QH zxbly9Y3ZBBPRrU$+dnKy)-03$mS=^R_Su2O$yH*6yc#olz?GZIFl&eq=XItqs_FcY zCK6aIL(A(tJqw}F9=L!BXw@DNInWpe-*k3oQaWESl(Ho%?z*Qo{%-t;oH$XzLfDs% zq9)Fa$sqNc>J5*SsjF6r1tfDel`@MB#kTD0X7p^vq7!_UF)&FfSSO|K({o)3xcC_1H{b4s%*RoTBHJ|}8NlKvu!qPLb z`S5i``t~|?j5sd@>>OvZcD|OZ+yjbD?|eADB+N;0dCG`6~e|erdghq5x&Fi7ey7TuIxlSI?b|T!fadqDWE+Rgf<+cb*kkX=lH&GlpFr! zjvaI^KF_awuLsW`_QoCnaXKxs%=!4xi_FAM|I`C{4ZfIrL%XR2zZv1PG@QSgeIO)4 z$~VM*N9oJa0L7T`b`#RAt;5J|-hWe#y(8;7B>#mkPx83Cncw9AB;fkWxuWi0&uQ&f z1vCQqJ;KfE>@~Q2q60c4Qyf&cc4}6ZDLZwZVFTfptGL>h+jugASktV%pF zjgftbyy=n*Ipo|M0EuJUKxj!<9TS6K5)_M<;pK+ zy}*d!zOS%uQaYwfW3I#PFRP;EgX>+>7f+PW{HomM&^A*|b)fda0f{h$;>f(M-to!_ zn2j&A_AY}5fTp+Q+!bS?l89^J@7+)d2+>wXFtQe>)sF~fH5|pyZlGqD(>9dz6G8yF zJvS)=AuTQ6tuCXk^ul`RP_uOu$C%^{hC0dX*NTcRXC)>ZnK4Us4glBQ>XY8t^pr9^ z*}7E2CrxixHKYr!I`YE26O$_Lv%*W}$FYf3!BH~UdZRPqKlU>{3YB(5$Eug{Xuaa^_vyM1P^CX-_ittk{AoW}>xrdDi z?$GU%zf)7sOoX#|18S6m`X z=|pvH?#cJlhP{E+)-q=-?wsPT5w>QIQF{PnYVV(`JPD6`Nh;AcCw--(-wK!C^26|v zKg|ch$pHdhTY4G zetEOQJ#9n3d8Gi$34Hg&tlIYRn-22d$M=g~Cx)B|s6GK7&Mj1V)(Ob)6=a?GG?w}v zsgblyhL#hJtR@y6+7y)AGUQ;O<~ghQT(3u>r%t;1KOHEX*0b6)3+cu7@$>|>YEfDz zjyJb`nk?cx;tclV0_nJiiPrVpxEajFQa%~z|1=Yb zDlOX>K}b6w0@$o--QpJRSfqFB+1k9sjZm#w-FYp_m>fNc za@nfl@_YI(8SdkiUdl9F`9aI6c9g?wK43pTlJ?B6LzjL%A(y3bXBhul@(21TM}vK3 zD=0XYt4%WWv-6_J37~7yq2-ej+IQG0@XbHzQC#F-(x>zZQbtUpSQ|s}O(j3BJ-|7l z0ae)`kELVn`QfvH9550?G^m z-5c-|$ODE|YCN7A=M@8tekqo}QLycDg{xYWfD#=a(DOgQFdAyn|&uo;K`u>QYrS} zztQykgN@B!;0_W(!^W|jOE)n~T(;=DG#Dy{lHXw013sz0@z?VUGHSS|0!By6M?U+q z-B|PBO4{0j=S!QJS5@h6xgu9P8pq9Pn^q(sbqS2QWpkwUx#-yA3*%$ApXTUUk*7ktHN|R-|OJgf~=k&Zhn(j#>#fRxt>ezn#Rm> zc_p=95NRIpRY5C`9ptwD#>NLbC^@M*HM?oX$BoF6H7M}_P)*nREQsCwmIMb-kmkJh z_dA3i|93oOu{}^Z@~dZ=!e_uDVhNCl!N6D+lP6C!{O*)_9*0aBt z|2dMY;G*6@jkJqV0S~`}m9Nt;hEA`S;X&el_A!k^(0G@#cZi%wD|+TqU;;+vt|?he ztlUo(CRQ^g6YzJbBc&95@tW_7de9TUXvO(~wgd<6pTP0;5j!vZxg(|R3c=gEzr&Oa z)aCNdww`sp#( z{d8yTl7$|7CW5uso^=Xa_J1P;JMSr~Dqnl293y%>Icb8Zfd|O`lV5GEG$Y8l3W6{7 z1g*7SF(8B46pHfVAWK-gf&ne#gMx{PX9F`;#TY0Cp<9xj*W z!l^eYZ{7j_Cq%sr*6g`qE{nYU?9(4wS9r&?Ek%aVx1S-Y9WWd{sXy@7Ig*p(4PUcO z9l|S$y#TpDYdf6{8^Vl$lpNq{eLhFZf#7B|_=>XUArd<`?zd%weY@{DVg{Bp#}{~U zb!!do7DfzA&h*N*XCB&``7)<(`TE~c%tRI!Pm5lRDJu=5u%asveBwJ%ox zZY$|p4b{ZgnXzZqZgb&%zyzB!doTx^shuOejW!U507j|l;XB|=x0?|$TFGI~-x{xm z=WPV=Jk`br&6atdJ)%(3GCAX!)QW*;x;cPpcieNO3huCIc?I?Bj25$! zV?eGbBYng+Iod%?`(vw!9S*_}@?mk$C3i2sRRhvxV0y(&(SCqxtH(s$*6u(W=+lMg zNiy*yj@s7ZWT@#UgU7PswM0~#5J8wInKX?dQjcy1VR<@Stp{*#ih*f0G2x6kXbYH+ zY9H}aXc^W|IV8&!T%cV0o$d1~-q|JJXA~s zY}Wkh6z^uiLpu*x8I|bL0hF<%>&EDYSrB;hJEGbDLiC8(@VXSU#axHTZ3w-;)kr}b zF?w$n=CAQ?f)QTD?l#?QZ>wvsW8*^W@4(6AN)yl4?7QW& z{7Gy~puC@25Cvy)KIg1SqCFOdzTanC5*d&gAyhE1P)%AnWb%mp9b#ngr}WXj_wDK$ z>MdhL4hQiIn!H)BS;AVF4Jlk9W;6$vF(=orBOwUKK$Cz7zVVsUn9ObKj|uzuL~OQK zY-(;X9!NB+l1;bE(t9U#+Gu}VB*?3LgZl%h;SQUh4}1r&fZ2{XD%LJk1Nsd7m)cF;+pDuam3^_HYKV*8;S} zl1tJIyfyWhTdrt7V9s_E$dhT2KOz3l&$LLTSad1Jj_qTjT<>tSbe%~{?A?Ld3G~#? zU7QAC0roc#5+*`Mq!{2MNDv$fT2j_N68D>i`IT?XCit~0{X76t8X3TZtUamfw0ViE z5^Bns;bn)TR=yx)QrUJ(-}w!ybC(1rvQ@@xar zRA5tyYnDSg%L+SDc>147O-=sZ3AU(_8*QZ7v3OR`)I^BA=Lk6oyknirPJsbEK<4m& zVh(?Wl-sq=uJ3QMg>j$5B%0Tg4L^3qexMrhBD1VrZvR+SE_T-c@AH5;mHRaJ@e1uA zp%UU^YMp)&t@{(zNNskv)EW~aJKQsPu7-=~0zq(Vc_4*#1Dl9%@>!`X$zIN|4B3nM z5egUI><$#MM-0b-luYJf&D#U13b8Jadp}cx(VdM*lcvYsJE?#i`;Ou%?q;Wm9l+8g zoD_8HQxYtr5mPv%oLEl5#<5Q;;UC}RkN`XlplL%}QZHIsA#jgl+)aM449E5DBV+4D zxj-*nGK%L{rXHvJ0HP`;L0O8UJFucKKlc`hO*)Ph@SFUH0rUO=TUp+TRi*VjUT!%&6nvmdxNQQ*!+4?SV0V& z4));y diff --git a/public/images/pokemon/exp/back/867.png b/public/images/pokemon/exp/back/867.png index 766ce3f39ed80703b473d801230d7cedaf763998..b816f10a0def567345f8a9a7a87e041d4898b70a 100644 GIT binary patch literal 3724 zcmV;74s-E|P)Px#7*I@9MF0Q*5D*YXJU=-(L4j##l75ZFxx3lW(Uf*x;s5{u2y{|TQvm<}|NsC0 z|JBM=G5`P$4oO5oRCt{2or{v>C=NwU0`C9+6#=@96vVQb~o$houmWYMzI_)XBNE+<@=^% zKY*1kOXG`YoJ*1v-$5n(WoxE#gk~o-VrezW_qUc&yJkj?%Kq`Mw8wqYcBi~k;+_3C z<@)qnTNTx;b6mBH<*FT0;$NjReGc3Eb&Fm!%C(>7r|qk(|q!&3r*l#^=D(mjnG?~29Vkw(QbUKu@y*fucZ`Dpv}oN_Iqp-uo7 zmaAjsxm7-#g~M_EIcUoiaNUv>t>c46EYNZB9N@oZpy7_sH^T^E)5*}q7fa0uTWCE; zIm4gvI)_Me7hRB*9ON@aLlmosR=MiZA5Y8i9J<=Gy6tniOj(b~OUE!w z&~!TS&L%kU}@S!dkP~P(CmrH(Z;~X{_ z&f0&0rgfuQ)L0bpl%!>_ecqE?|JL9AsuPNYWx05+F_!77EX&$d*L`J}9MOQ@v+7%_ z0j?z!2SngO0way9LbY6fag+eEVM*^*Mh=KN+oQS~-x%gHNMMxM{ffe2FqQ2r)`m1 zh>AI?c1#?Exdkb{t1&+Qk$Gq->lkx(#F6DP^#r>TWs!1Nj!(xj%(AaA zn}7AQF$Sj;Wv*?eV=j&y`V6B98icZcZJ9Pbwq?8(wCqeI!m(+&-&dUi5Db`GvzyEM zaxANXO<|^mh?R4-R`gl1LE^Pbfq-6qwD|BH2N=I%hSmjNTjtsvu7W&T3wMK(8l zcyiTOzub5?;Z1!vPIcr!v3I~}u)`(C#@Q6Sihptv@OO}fCxRts(;7PIm;il!SF1Mm5JJ|jUjj?{M_@bnygmcqP%R^bG&lmJ- zK{@*GSY%dhR=%eVmR;FLereuAZCLH5=J~@NVN3Uw192E4=f4l_l*#f_U9zlnpBtvd zMJj9EnBTLE2S6|DigS(fUzlKDkj7B_E_IhjOgS_@SGctW49AVRy4B6J;2-I>>(5Ec zbyY6IF4ImiOCF=VmZg6x%(*Xf_d?rEMUWKBa+JQ&f!+RI31>rQ#dBjBk2_ge zFS93w`&h7W5iv^AVrs|*xr}K4@MhJsA)L(Z-~8}~r$POC{Gl=(+zpQ#C?Q*3D<>vP{gCT|QYPr(m>qX^9 zekV-7to+=rC;gG~Te4ET{`i6NyORH}*Jq*lEWQTZ_mb{cv4}m~hrf8J`w@%?drS%U z;V+&wR}sMvU_^fUz;GY8c=N1F>b?#ms^=vP_mwm(?8M#ut7HZrn(h|D%_0%uzJRdY z%`v2OpP23%cgV1Uh6#vfouM4ShzmRR`b?vLIBJ>RcjAiz8mC$~)tb|ND_s^#&CxKT zp^Oa(Yzbc@m~nFFYIa{(sC&py<_XW`XaG-7I)u_R-M!>^F6S9j_@o5o_IARs*uI?s zC(T)DHia~dQPPopXHS*xEBNGPPHP%b#--CPj!T5gp|(X(woF`)_BjumG2x2k@goBP zP&Vt=PP;fRs1@=NNnOy%dYWv}z6-|_$S3DTY-mKoapBqIDNjRCVw=icgZ4QO^d*ak zPZGR9OPYoo`40uvvm=<9cXrIp_f^Kg)?Oly5*Dq&5Bf4{+AgyRn$HsCJ%&qRfiN3?k5{Z^_ zH(P*bd?G-JI6&|cKD#6%%5&IMkg)?dGnac7@w~kt75ClqtZ~E5{H>BdqK7)e;jUVt#6`zrej%csB&sXwfQSHg1T;? zbklJg;SJ_X;alm-aCnXbRG{S2lkLiwD_s!1YG`|_S!`vK%i|91^BqSR1eA8YW&P;5 z#}RGu3%1W%%WJgHbj7=c zyg(l-OVbe@S}w|gc|@PVTXVdrq+1Px1U@^@=bvy!t@r21-+ML!S?4m2&X9%+;%qFP zqnv_2LHpK3sb)f?P0Je(`Y+HvLv$Ditn$@0Q?*_r@1uQN352TCmnOze$;rKl;0YK}s9GETh?&2p z`~lim^a`&u5ang}d~@h{0_|H8#?z7?i=t(=81*%6xxk3mXy2l;h7qHPLzEk^yOv?$ zHA4HArJKJ6$DUJgDY;3{@~>26?U~WO8}Vp2C_=Tq+^Vc}U~0nbpnWPHOS-LUHYvHy z*fCs6570g>JmYx;_7w#a3ZApvF`ow7*GW%U_Q5`zl3N*n1S3wOeUstv!9J6cn>V~c z7cQZFb4gb`o3bV)H*a{W>mmr~t7u;o8-J;4QgTa<`OK%0*JvMwVxfSF>=LqfLp#v# z(7yJ1D*K9GEpA`iY<-&&e(rZ@`hDRS%(~iNMB^pNPQCv4zVNGx-z$H0Ex{oxx^D}z zGJ|sO%6`^)0+f5Rh4>lS$D7x8O@BxBsploF=?5MA9og4D;o%mFUGrJmqHL9X({j5^ zBsi?lefv!uu_OE593&3g7vPKEXchKIcK>%jIjrYNooA-Q>QhDi2zaucM)qz-TkSf4 z!!Ew~4Sg?!+k+7oXO{?U*tO@RzxWMbULpytL9*Ocz=n0(+>XK1j$?x(Is|2Vs?w8v z`@fZY#>$>kPxjd`;x15NjU$=_Wg9*_-4s1}nIsL3$ZJj4u~_Zu?@;Rr$~NVEkQC@i zur&5;(1?*=SIU6))h~W4HWdfe6O@#3Q8i{bCU|laoAFD_l}5x}zRAf|JWRoT9offY zf_SLi?KpVI!|)_J8b7E)G>jmfYS{oXTC}eQCbS1`3iZK^t6X>Qc$eQ1iGO1{fV3TG zgGMY5hHaw8a4Zk0SvU|zRM99Yw|QZ%CK;ZjGe>wse{Km+mccizFE7Fj_9*v~ z;aSs&erA*cR0kp+>Bz3^4{{Y6mNWgeoC~6j@w3)41C%=DJg^U0#F|uF*~;RGuD#Gj z85;T}q}lxM?JG_bK7pqKuD?W$9!>Nq=Z69RK;MALf*nY}zl1NYP`zq6HWzL!K*|1e z%DKtbTHeHvv6gG>IAbD?$v)#G-vB7vl=Cn-NH(_IRH5CA^G|plId0uTN78ujEE55H zfg*}1Zw-uSg($J(#_SpEc+smCwO;JpQ^Da;z*9jW`7R z>h~fFcyiNKD0h^q_FR+I9wir)9F1Ek%b>9f_TfRkVs>TC$Q`AsJO?|EtLF$&ncE{wRm5$0g|U>^lmf3kg4Z#{4f$b33szQ>$hgMHoyuN>Ds zYXpY!ZL)72?4wc-RnA<4M%a#=|JY>TeM~Oh0sC-;=UPVF^#_yev!=Z{%UFhl=0zB& zVvfQr@9y1i*MmLO8rVlithHkX&DRXF$-dhcOim8iH`tXzxgHz%i0q4Cc@6BtCFKxO z#rk$l_L&PhzwtG&FLtjEEKibsV&yjFxaeA7AFD)tHBDVeUy^;c_Q-Nw?6+Xw93?ky zgb6&0>_aVI-PcWA-x!i>C^=j8qJc5l*KRv5JFVBv%Ck+$Ev4KA*@pp9pO_NY`%4ma zDY<)=4YIEf$aa-!ovs!|!`J`2o*By)+Gk^f6P}~a3?Zs0Ig($P1!A8W?|iu)EkTuN zRFvFm8R|S+XrBnkqU8-SIVD;WPMkSWd0udgo^7;`1JZnCrU^KiZ$-s3<|Uu4w1@UR z;^d+w`VMneJ|FuAf(VFN^VeO!O34Xh9fL=0+-OUSzUWtbz8}>&-FN&t;_t|R<+m6A qA|5Xa{+G1>uD|Q=`n&$F_WBP`-6M0000O5pmdP{hK?Xb zl+Xkf66w8*bOC8M-kE!U?4Ft3oikg{KKpL$J!2gS^1iKKl1f^h;asJI(uRRup;KZPo3PnMJ&9% zJv&Ngg8=|RWj%z3g@4Y5?LfKh2=Q#^0?U8PK)CKFPCZQrfiq6|*ER?)qfKA$dD6bU z!95JGClCjD&!&N|rVG~%9FOMwFSZ&OfWu~te}I`o5|``YnJl({+ZXrD4IWaFP6o5w9cY4_TO#o}Xs z&d;me&d3|cSq_ur+1cI}K3uA)+WP(C0RT|ddc^qr zF%Ag&<}36HyNn(PAKCvCRD95(uO@@Wf)rQj3!u0edn8EEAque9csz?Hb%RTM7A z7yBP+o}P@AHtx>IKR+CxFlTCtAODHZE)SvsF!^yPrQOAFT=fj?|GUa#b2K9!>~nfI zIPBDbYCm)wl!W3V1w?twaA`9qv_GCaWX_DE1eU4)DzV1PcJ!QWww>SP-07J7L^zis zCM2AxU%>$ zZ13KHK)k$Y4Hr=d5SxBx5Tn{cUf4Mn8w$BH0qYuJacNL3bnKCsvxG8l92mHEIJ2XU zvRe;7QWtLOcwrtafBv^`&^kv~v`}+;MDtLGF z!DTKF2WzzuoG^Q+KqJ3Ib(f9I0vQyi-9A_qBJeCiz?9hJY(QdROoFFc@~uZ!_geEq_Fz=DE4( z2BX#{=>|c_aHp@{YELm3?)op^&lf<~P`mfmA=V2hBn$hh)NW9z=nb8rZ|QdxKA#P^ z*ggI%tR4!0GI7y8Q*l6g$0j}B9xgWU&h`J)`Y2vHCP`D8za||kCy_;J-RaN)RBuzv z$Qd(iZYNXv6YLw#=@bzw6@U2)x!t>k)(j(gF^Ul#iXCBXd{%U;Gg;atWf={Tp1H%j z5fv{GYKTy@!L0_A4GHrsL5+20l#b?vUhM#EP<Vw9C@f?GLd1cmMRvdMDI|Ey9 zpE~xrqr~%6Hq`CI3(6QzG&pGRL z_$2_&CG&{GUGG+GCQGKZL;OTc%qm(Oz64>x8-w-QGBm%4PefT)QA&=Q3351At zMo2v(CXKNyg)^Cfq|z8(S@vs%IKi)iME7oknpVvoU)^p@9a_2$fMws*ZR<%+_IQ{X zNVLgon{W&qN(P_Lt`yi-mEI>SzluRIBX?J^pIKLK_C{pF zRviRkcNm|8)h@N$lc0@3E7bGyH&Ss(_j+9ysN!{MVFSMSvJMX$85jQERvD+5MQTq^ zXIgH)xHFLxQG^eF*xs#yBa;@F9~<3xVMwUU=x*rB)Q6-E=eUL^ExX_QYS%YAs71;$ zun@TC0?fSblsh4yU;7prU|4lYN+eDAdU)!UneG%rVJ!QC_blV5Y}3?yu0Vk}!L8sx z4^eCROyn20qh7(pISy#b9w#QjT}J(2ePvpLVgSsvXP`_uAvU>8SV~}4g^q5Os;Mu{ zqkh>&vfLp$G&LyxmT+LHd(2qy@nKi4_#R09Xp!E#?!t@ork?#|8oT~cxFlkymT-C- zNq1w!JyHO1xQ{TLd9UZqS)t52BKFWf$mR|H&W?U*hY7+^8xsEVk;uf@ES0fl(Cm3&G2RRcGA?I z2n^>G0WxYlnICBOg#qNocPdHZO~>}P_VHW({5_kF<__xtI% zl-Erj6zrIA`^p!wZn(#R#qcq~lJw%hi{?2pX5pbevHLmSg${-!!#zMg5n#|~vp z{9{+uUp_g?wFN2M*vk!{rPqXbEjmO(pIp}4P~^cl-H*|(O}#ntYUEMM68xC~B3S1& zM*|^!H6f}5UT-Fe_%qvT-FVX5WR$r`jimR9;`_XU2BLSFO-M=eLKD{$2PNTu9&H`= zdd7R&E1w{5`m0aXvwooOgH+Bc)!97ObCq_=HxaPrW_K@t8l~Sp;(*dPrd?!LRJ^{(E9rwNnwA@{S#p`D{b=TI4P@&xHqQ@IpdNH- z;f1uT_OHl-I`UE$Yn4MTinlH6{Z9rB69rLj z!ca4n6@ClDSQ8u>bLw0WcR& z%bkg0m`ZF?-tY&p6+GVB;zgb32_Z3zT1#-GZWywARTd@@>=;mu4$pa)5;IZN*`WVY5Z`9W?X{@d>Jxo zSHRFCbRDhQcGTb;56r^XIX>SIKp!24AF1fR26v+wV;!$U8k(XY8mgqM0kgmuqz8{Z zFf*-7cmg(oItEP^JU#=6N-g zrTs4Nt1-rPp+n7~^_0n>rtLQ?BdDhiUq1qSHD~L5P00> zICR!)FY{x|e-!cMSQTo=WqOLUFy}=#aDlld^a{4P{ozT`SN-{6o97wIC7UK5Q&{Wgi=qH0=r;~QRV`L z+qV*b6lwk*-%8-|-*p|W6mI{5a$SKIF2^Fph#uvSHuTMW(ds<=iNLqYh`SHiFgkbI z{i<5n2|FKl#-I-W+hx5r{2!V`ezMopE=xEO?F1@-*Wb=fIq)zVTz+}5*;~EhN2}1Z0M1{6=yhbCk+_J0r)99Jz09DvOEE@^)@Q+yI?!3)CkHo7NGAnj46UuwfW}p2_+TV# z%h1qwI_)|3VVzHEOeJJ*f>~Yokwn?ds_^;Wp>AH z7InWN@H^J7NY=f&FJMbstX1b8X5sC6Nu(j(OR%GtQV;Usg-Q=_Q`@Xt^(z=}IAmow zN2(UbSbv@MxS0Z-`}6hkB!jH8a$uYlmZI#`do$~y^yskEqI-rcqCmRIg8PGxh0%(H z15Qu~)}ieYTNqv^;V=`hkM`y=w+F8HJRgA+zQEI{G2V<4Y;x7tg{3K(em`k7QOzsL z`0#Z5?hQi&Q$zTWc85Gd?qPiWl>APkX;eSN%Qjt>GB5@!8ovDz@zTnso+qr*@*qG_;nqAqBikS)at^{ z#xJlF<}6*t;g#Tn%H<16(GRyUyHnq5QZka^OXl%Yx7@169D(4rW)=rVhu?xj&$5Ke z>?{PeWFX!n&aV5iJ7=A_(Jj6fUa$T9H$i~1rtiFsc)uPQo(lCFbwR%-pz%f5i&Oth zjJk_17vAHH*jO9tzngnjk)AdkXhvmOt3lOB-0lHGhNO z7+Z1z9Dy5;n30gzjQ{>aB|BrD9UI*|TGWiqCl9C0v6L;F3oJ&FIj9zy-Td`ZlPAj^ zj@W1JkcDOHbBGtqUiI(h?tH6c1Y05@B^Gp@vn8GEtx_OV_F+LP{U7;E{0%lVF031C zR|d7ANO>`CjWv&oY9)mu$v2I^(WXN?Zt_xn55N98cT2*_B;(t>*-DQekLa$V?N~XJ zLH}FY4A4^m^qoqYpoVB%6ygud=lzQjI-ICW!6?X5N67S|lJyKi}b9!x*i+O21I+_1< zyj0kcuRLd1{q?f5?vVsXY`Ifq9HnoD8?R$8@^$U~cVi4jng6Hczt z1Uxvnn0AmRj#!H>+~5u@e2YIi@B3@Y2JPbcmU3{lsKVzeKD#iiLbRKE36q`NoFlGg z%d#G>eDqB_e1U^U%u@U%R8@q7(6oQU&x;C0uHr<2qDLo7XLzxdj_oA>OIoDJDIA)6 zyLC(Km0>#xE?LXH#|;!_X%d9w_UT2+CEmP$A_m5~$J#!9d!dGJ=Xzp`PMG0!CqmTs z97-~n4bpqz_FMcSbBv!_3oXEZV?YV;i@`QR+8tNgO;E}eQb9Se#xoaxeIVrXnhWAfbCdJBU8 zBJS+?{A_w=6MH=Q{CyPUG<#Xh+AE`zaya7Ia9_sZBg1v?1HM+tus7lL9Q9wKJU+`- zZzvh`Z#z?gC%wm4ecFY#I-RXJO=twgX40inEp7$7+yZ`IvHqQf?RDmBk96yO%;<_A z=3c3kx6j(oN|HLsa=Ck0l9I?IpB95111|irOwJ8(XRuxG+~=&OjKrcH@_K9Yp1!-y z#>ESS%J6O&J(h3~%V6@eF>K$}zDlmVM>V0Q_qt6tJ}(_`2KWarHZ6WE{yL&i3|tYb zC#t@EMlh{_#XxLf|lcD1Bk)O4OLw3KK z(t0HS<26aTM+0;ESA0T)kSpEk>Kr%hII_BcG=_kBU3$=p| za43>Q6-x83ypDs;-6c1B6DEPsPMUFenk`KHz{NSTC`xkpLf|%BatB+(LEi^^$&5rx z)g3kL#X*Z$q$V54WGgfUGoqiU@7=3%8rWLNgXtpR^Jzk-72h#G_LRpWXhGmO&hYv; zm>NW<*VXmJ%y5i)VQE5AOpPtax3WD@>7={)^TLpzP*4uHihsK-ndr?)$p{;Q&ZDeDuw!^Bvp6HMBD$92~?d&*3vT9+X9;R#XKtP$;Lk>%m%Q{A~FMbEbBZ>j^wi1_M5f zA~3mZ2qLOaI^_`)?7pj>u7Tn2}_wh5tNnWoLL~4itz6 zd@I!H25^FE`g{I>eAY(`a;Ds%b&`2_;KCPVha-fml#@?^2_n+_)}v>@KEpXD&zUhA zS8s+i;FY(3Q0SJwY*D#rB_*8`@!>^bjMbORi7zR%8!!6TAx?T_R3#|LX>S~l@pbLJb8MH0UiBXov(^-4LhvIxR` zCry$~N~3$Rtg1ep>Z{>vg6>SOW05sotpuWF)dwf4stt;ZK1Bz=>TovUqRWIcM&CbI za5q6NehO*;q_Cxbka5Df+(`Rcnx5RSGxAm+{z^{N!?f8_gkB}vrQs#MHoJaiU=G}B zm&rJ{y%tqtU5J)dT|DUK#K=fN;^LZtqs{MiKMot5S=U5aqApz29-(?fHTIbHN_GE? z7EL1V^}-4ryz2SuP7FZ_!c!lj`l@oqEtG9rpIAz$CV|Ejw=u5UjQ74u-DrKNyEsqt zVc}+$>01zS@%W!OZ4C^L_iskBHw~X_aou1VWOrqOVwDHvS&Naef>vIPl!OWgK_il- zh?q-(^7aEr&izxU5ef40xkN4NJ+@M;JS%z5$^K^_%0SX}xq=%bGg2S=*9lKoOzZ9u zUH$@Zz;k5&NrBs}ADTneZW?>M50nX+xAaGdzC6Tq2sx7V@CN}LB}lN(E-V9!`%pnH z^n87gFz9}`r~FbV9sc!~U5{TlvuJ6BJX-*{BE<&%)ztcdDg5t5SH&eDck%r=EoGgq=ps&hBW?2dn2Z1&UG{;%g(O9a`i*80tG>&n$_&5wzHx)+lRvYJ*7U09 zIg{@>z?9vCYtUuVL>dQ&rEdM<`pC!ke+zuR250xE9 z`rTWZb;An8n7S*Dn5aTMasjzj#5F{r#qmNNIl|G?fV+vms--5<{3x?d$c)G3>nQ|I zQn%@UYleumDD4(pPmF^CFI5}drQyZO#M3Vx1K+Y6o_Hs=C4`sMfhZsx;-$Nj4W$pT7W?J$}VU>IsRt6p4G+(`o z&gT0dvI0&*^*PA(^Jdb~Wh%IkacG5YST)OMv*L^QNs+eFzmT*`X!GyP$rq4wTB1P- zp&3#5gP(<|i5^~$oq@Z8)f8bM&Qq}xBLU+SksSSW9YD8Ku@r_B`8|qgh NKu^mUQ3i<0Ch4n@tw{r{i5Y0wiS3^v}4jVMRVOm$Y~aIj-8pa@7tg@vqXAK0n*%eT!Z+%5`4mr|VeVSb1)hf6l_sas4@H%M@_kk`=Ar2aQ;uSPUR9A8r}2cAXB$BDV@MHE5I0uhZ6~MTz-z_^U!(t`!j0G87*TP z)&~1vq-%skXd_YiZ&RAqAnaIXoS|D{anieLeU_Ggv+AXA4fST^z=yhEKzYlTUoQEv zjdM6;IBWj}n%0eKQDafWQ<9d!_H|Ek{ab$*s}qWZWx05+F_!77EX&$d*L`J}{GtK9 zXVte<16)fe4v4^m1V$QHg=)F};#UI5h9!Mg895;8Y>(<{d}El)Ac0Y0m-~#%#!THE zWF&Hb8NxKC(Q|n(huKg@&v0Yp8AM?DFXE56EJleR3uRfzVp3DxfqRJ%eC$?xz6Q%( zeWz`aT8N4{s&-5qgt-VS%W7noSJ`vz&>G|4KQa$3WgTPAjySShrk-F|qAXGl%kkw{ zhFSI%W-}y0jKL{InQNQrn2RHaKEr5&2BGYK-&>|lk8K%m1uZ)hiEwOM?)O!f00aZ( z*6il8z8uSHU{jcBA!6lRtrdM%9FTbJQXrt0NQ)2Oae(nFW@ugTwPmi&;V#IdwQx5m zx%6=tdX8x`CQJU|dFGo5x;}NvGaCkBnF=UM3>@=>Zr^nhD*@IHv~scMsB_J6t$nqB z97RYw-?6X2(;%;(OU{HOHpx!y%u9w3osBi^Oxb~B$bU$2vC2FSsPxb+&OFlXk(NBC z;LNDLEEP|#`s$Yl?-fRPGUSdi9*xC9DGH06blh) z=bohHX7hKWJ@Opt;C>`aMS9#yT^;a$q*(v((DG52ThWspZ2yJESie?$QBqUFx#^+h zp{&#A3;MO7Tqi8Q9@JaChuUD-m3`%x<~`Jg)oyB@KRgk(bhjLc!w|Xt9dWC2x44+n zCCf_pym5#0F>tu|iS3T<;QKOq+AD})kFU@O3kQB>ulzz~G-Tqz)XG3Pi zb7L8gJ6TyTvnPf7Sg>#rF-p>ZVrs|*xr}K4@MhJsA)L(Z-~8}~r$POC{G~D-+zpQ# zCvPfgCT{V zYPr(mYoqcjzZ0f6E5EnvNq?lgOIC{4A3spuEBXI=eHMz(;%mTtn{>Z_ibd?obl1;izT$Jc%y~Xq;-{RBKN6 zt#nx|HAlmUhB7uFuqAwdjbO&fovYb>VWI9JKba>ym!knZJ?Rii({%Tehn8L>ZS(yErZpE{EC{LD@2KJ=*6y zY{rBumdB3_1VGuWUpwvMxS&?ZM(MXOpKq4MB-* zDt8Uq=RDAtEFwNh@B%GqHexqyZD9L2eRE4tD$5qy=hBnqM`A+2vj?njDoeD_6fbRy z7XT&Go_QBzpnaBO!6M>e7u~(#+5#wHm^%z!79Ri(N#_O)2LtW%xT4C$^BJYYC)=`D z9vbmu-nTG616jpZbm zTit&XeTSJP5-sI!wgAugM1T@;fZ!#3c1cE*=WwVXV+S5)F83_rb$dZ7o~P$oe;%9eGmmBG-K49tvrvOe%!JXv^uUvt7H1Qgmgw9G}aiGs0^ z5CkQcC>NCLKyxZF5sIUzaS#lF_KnjGEbBs)VJec%B@Q!N_JV9p{y5y!THi$bOvkd? zQ03H&YV%iY1a;j&>89g0!UxQk!ne|u;qV*>s6ff3C)<@VSGpj2)zJ1@QA}nd%j6k(L>ILh7`AUhCWi_3>8_JY zZT%3r8$eZ>?$!g^mU)3bR+gqCI<#Dr1M`SJgSY1RP)WBM1_^w2p07XQj9Tx{k$?7V z1hUR$9GxKz7sT0EI!8GLe}eX{iBipkNSl@q9`s*-pnZnuFb-Jdt81oey+-znhCgEFuPJ|k_Eo(w7UgC4d~?J=-J^X=!gyK|Srjd^#i*}o%LPWfNBb6) zHH;X4MI55sfIYPg3$GE{w=CWKEjad^f=kIwdX|5sB5Ti#_C1J4yFn4E@UnF*y=r-k z_NjO*>9(rbq~s1`$8ak>K>M`tjOP*9R}@Src+PUid>Uw9Cp}@=2m5SFZe{!tj5vw* zO@_k<`%Fr1-tYlkxP}#L!a0|tM zuK6r&QMO9%wA?Ne2@WfC-}w+n?8v^IgT!I`0(|ist-@Z(p8uXFhxI(E^UQQueX6J* z0Z+Em$Ueygq3?xodobeS>=Jplriur<>q;5WzWT*)#irt*dV-QNE~>^1#{^FvVl#egxzdQZ z%Qrc>iiatxj93}3axMzPUv_1GkgL$JoawLS+z@SypS6}5pwubn zfqlp#)}%VhRu)He?S(GN&`?ZBv-#iKSDYq%0#5~8e~B7Bn&?x`4+H*zz5$a3JCJ~X z313{Hdev}jF5FsxlKttFbCa#Le25`qE!Ws_#zY*Gea1cR z%I9D^9)H~nIaZeGk5V} z;24nkbi(|MIlBk@ybsEjqa)VZF@xrRYX;e5-{T7=CkN~s z>`I|rj}3f8_C>I~2KM2SatNtneY+?7%mtm__!`(3yH^L6C&@msa+`8onLY#TW0k0{ zrl}k0Te8pA9$Bu7{TA$-qvXboFo9>0eW>ND`?`ti8$)spC1er}es7 zdA2FJrIfoN`!FDX>JwAqdViV4WRhQgm)s56*9YXd%d}2ci=yHC|6R|FWee@IvB3$? zQD=q_Rg@gbFU$h5&x}1^u18ByB^nhaw_1ie&lcJz0D&&dTRw-#`#e0WoX-y6df!oG{igc+|#& zwzTL*zuNQjsLtu`@#~21k^jnXFaAY5HVXciwEwQZ>+kxz{;u}=4@^<2Wuy^>nE(I) M07*qoM6N<$g4SP4<^TWy delta 3246 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 g{(l;-f7U5(BVe+qO`PE!E?|NsC0 z|NsC0eu)%T000AxNklv-@}kI9IwyWjBS>=%FOxxDSo1p2<7xa(`${) z%q~PHUqqyC^rrMuC}$!{s>n%I^Y9R3FdYAq+!+hV#J`k9e;Qea#AqI+7=GMrI<7v8vXoDNN6!Jl8$5hU13p#+1}WtbaK3b{{ z>+?FKWe;BE1MBEMjRnxJ3XABgnrY2K8{v3af1=+h$w#B3vY>yfi}ZXN#{suw^VIR+ zeTP1Nu>Lms_ew5*)yCv?`mNgL@?$_6cV=Iv&j-%*id7vB(#!N)Cu(OFt&Nu6kxnc0 z+e4`i)lnNo8phNEtkdJ0MSNM%?!}snb1i#Fze+w@0T(A_>&^teXh?sZyP;s$UZV3` zKQ3bD?G(cb7k6T({kAszo~saPXotU1c<;{TYky$+CHsp1P4*n)l`H=Na`iFH*e8W{ P00000NkvXXu0mjfr?m7Y delta 926 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 delta 6214 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 diff --git a/public/images/pokemon/variant/back/female/178_3.png b/public/images/pokemon/variant/back/female/178_3.png index 32ffdd895c6950fdcec0edc08b2b41b37a01a7ae..9533621c6d696fda7b3f97abd127ba04cbcc78fd 100644 GIT binary patch delta 6215 zcmV-N7`W%4F`+S#k$?M1L_t(|oZVduo8&4Ajg_u+jj8V0`~Uy!#zzzs6)@G0VfVX_ zxskvT(IlK=Svaz;_w|0ev95icitm=gg+t!n*3~Q@%g4uG^JOa5by?;2av~kY(0ljt zw%*Jke?R{IHBumSDprTy5k#_E#d}?k#Qk(){qKK=6Nh|Caeq*$IKewc@u7%=oLsyH z(_F6R=KlBj@ga=|m5MckXWU|P{~yXN!|*0Rk0GZyYZUXzvF8F6z@N03Mq_U4TOK9} zBep$jk`FNsDit|~R`Z|ni)p-;ZfgX>qaaRWUDa>*H{imm(5+E%gE4e-Yw5sY1jKEH zcX6pmK4&c5x_=Exq2lIe7P)mSrV)VnMmN8j6yeLzt&0bRiV8c9KgFOUirm(8&Fc6j z97-5ix4l_gAn~c#b=@APEyPFjaJQmZ(MK}}&X_P8dIFFPtfPuy>ri)-G z4Eh=sVF3hbhlIkzrzF+9y;*kIt!O-PHmZxMRjX*yj`ZupIv^ z)2>%x0e=h!bZ`D@|JSW^yJv!dJ!a^EjUE{AXCm!0Do&5$)ZXrX#Jzu8A9;ovbJqc3 zmvbI>G-JoVa<$K?NFIf$J)St<+=#pXyZJ*7vJi%Hj0T6p0ZX~y*EZDN#(}M3qf}&% zf>X|?yGgHA%pVGfa6b>jGlT;e(}1pnK~NZUR)0Pme}=Sglp7Kihkd063p`r#+};xm z_l>~8Vi+P089ExfHVn^mP5Xq4|6^^DbEEP@qM|m>J`oJdGmZeVJ^uXJ%Taky4hJ|! zgo9I;eJKB<4RZ;^)aqp_s`2*uL@-za2lIIB&#f4>Ll_7g9ClnJ;#Oaz_$0?47ur{+ z9)Fo6R8dT=Et3i{w{NS%;qO+Df-q2Ho@^X?E~K>$w6CjE|94lbz~{A0oVL=JPWpQiL554q=RW?1!Q6 z0vsA=yeFQ^XpvK&Z4#^SIP3r=eCUdE`+tV->7Fw_^M+Ev;BlzN1nih6Y*TJ@*(N_X zrqq60y#9w#s@NQg06{o5%yjw^k?!e$_}Cxuo|b}v7-JlG4<-&B@jeh1y}t*e7B%dsRA%z1q#@RXj2p-~{vgrLV%H@gin*mA%FwRmi?6gnEU4KD9 zpd}K+ff|zssbP51FdQhqJ2Bf3>XwQ;vxb1-IEpoJNOv@Krf7_r$HDJShVmlfaM(4t zLt6!DTMBZ@r|6U+2Q+Z-;2HX6T#5H+S>(mq{wtEE3VM=KkT-F7--!6>XpR`W~1A)S! zHRcGRLD=?T2u9J`mqY*NW~cw2t*4@n)$G+V#0k9q_*dr+#vPs`8e`tK3O$*`XFx-0 zUrd-B+r^=)qP8t>fyQ4_im7i0PO^=XSt7zc>)_(;WaD)&M zfB7KM81qhzl1Ma)*6!|Whd1b|sOJd8LmlJCc=C03Qqve?N3%5$o1CDu`x}p5nvos^TkB$s!$1Ww)TSs8M=u;f>%g7nm^mDR7Y*&_o%`X?RY8u; zi^ctSw7)RiA;v6ID4eN21b-=^eO|@bI-~enNsO6-!J;=E8jHFrSZYjuHs{~d-O-)B z`C|qeAOjF2eel+$)frPI&FkiCweoyp|(>xr#P7PlJyNZY`ZlDa6t~gbob8(0!=()Eq22 zwnJU|IUfC@Hs{qKTz}JDA7_YPv)1RtrTE1o;bUXu#891iKA27g{M}!UwJy2s3kX_S z{FY~g7EyYRdFHDy;kHJQ^Bg*SrUudN?tf7v9!eZkjeE~=t8-H@0_oVh@UNh zq;)B2@9UYkQEV7L*`ZZ1dCcUQ;A`?inKRJ=2iFvlbmXCWAaakV!4(==E%GXpIj*itZETkTvD_= zXK$Xnh+z)Jj6G^`*Orq7^uMv)sigGOxm^ijXU^*82A&B^}IMz+Ido z^D0=zH6d3Je}AD?sDk0+W1hP>MPgO3j0=Y2jZaKI-(F#g)@kzW-Z_Os< z9x>S_0*ejQADzK%nIZEMP7PM-G2%ufxnWR;;#zaA409rJ-AkznGB4p&pJm)ga^uCF zJ`V#Ju6;A7C-YKH^;yOQ50yhqwtx~BJ~YU`I1QcIwSQz@+NsX<6c2TLN1GT65e63y zZx`bbKUQeSytGrD>z_3bmBU^|MfWjPMk3fGV-LELzM%;5CCIgX6wZ42M8M?50xG%( zHTRJSW?I}fzJPCfyOEN4OIGQ?sa*PJC0WispA-y$srELF17OYxXti-HO|E6!tIYQO z^yo-&`F~3^D6+mMA{arS5{R`J1{n^v-pM;7Z9%_WxLGa{p-jGOMNGQQ;9zt9T^0$w zINNt_VI!wb{ufLM;Tp_qSOY8~9BK)lMd;kqBJ+01l$2AKrhAf~^?y10 z0VD2yZHjD7=0&s1aSCr#6!%$_=FFNyAw|VxJj7)|5wn|Q-T}9ka0+cy7o@rM>UEKh z*%S;dGB4cBnNC@Z!*rU

WN*NJm5wnHO?OnN9&9A)8G(o#uQkoYuwUH;hC#uig3V zka^K!$38WkDwYMFO~rJY1jhV_9Dn#L44<`_7MZutt?fI7AlGNPNYHYINglZyx(eDo z_NGJTrMR^@EV^DH$hBGSVuIGWTePUc-tO+Zbp!q9c~z4<#mnMif>z!D^<-X+S45VR z8J-NfhM)z1dPYs=J-q_UiQ!ewax?#kWx3{dv(M zHCK>%Yhn9_b6@x3`epm=^Y2ydla`l|dD*_f4~8R-&#nG=!05^Lox=AmbH4nSvOZJg zR@D2Ot9s16NRV%%{VgWH;F#x9Hkm57g8DEQ3fklO%H9Af*E-Wyk$-$TOqHUq*bEHn zw-RhIRjh|$zXAsR@m$g-SJWku%6;@6uovysW^m9R&qZx=E7y8tq%!xd zEv8B!!dwy#>f^brO>X5{kBr35eUT94Ux`gV;;4`3vNpMuYdtbj*;BD`U3f`=8;h|_ z?|9>HCpn%A39L-l{C}lI?rXyE^zT=9|6L09@C=wIKJ6VF=WR8J1Gj?3R5z7qrRcx|XhG$tBxGHnl$MYw2U;8GjDX9kwsiv6Ni)x~;<2 zuxTW#O|B(jXzrID2fL3_U+PupzfMBZge!KJ@^eBA-W11Dg2~&8UchJ@W_5SKgXVlM%?X|VDh#Xaq?`K)ot`H^x|r#KEM#G*aE)TUBXL2tzktl zu-oKT?IOEbJ%3*vmV7hyfi8Vj)xR%xm+*67YuKE}S)Z%93%+2q-TGO*xVoMCDjuZ4 zFS;cBoT4?X;8ZGTll!crx0BT?&`|2jGYr~LR?8x8-)vqAZVfA=u)@688wd!)xrZF> z8gX@#=L6eNmN>*Vb8Mbc!L}B3yTwdiII#T6UCmmT!hcj;U0e)#K9I_ho6}*|1g~Pd zY7NVzuo8I>46K`N7BIA38-Rv*KCzk!{Iek*k;Xt!tzqvh3Tu_g5cd^#p~Y*s#sO29 zFazB0odr?KP@Vd+;u?oh>3XX*?3ltjXp>tqH=lUcN*EA@skh`)>O23l-BILtVr+%O zsC1od4S#z_(t1!Cw8`Bbc~l~LW^2*qrEAra-%G+6Onnw#YRIzxZs|JR8kR$0#l}*3 z@A3Gv-xi>^n6;{HW_GQyz8gX^~ z+P+SFop9(?v$k5pGAOJGL6n{f3M=X_3>mB$hJTi8_0^mm0F{TsYVCfRcfrR^^U9&H zGTP)~3TrOROd~t46)S$Hh*H%KkXI9Au9bDs1P*vfS~&_i#$uaMW_#75uLHSFfa@$9>x?_OToaRu)-Sdp=aP~0)}b}$U7)l7>qJ+OTniZ^0J{vwvm6hQ5z-+v z#IopWT`-?Ep@}>SD_+2@_1@mvC_+;08H%1_hSwg^=kUU0wwCCV)_G3}g>}H)Y*AR# zoQ*uomqVshD|+C-SQYKx91B3`?oc@(s(*g8aN}TXZ7{JQ4p_@GYl-OTtcxHsM22oS zG%2iuHn|jq^;E!F7YkbPW%RVdeTK-8!$Cq}Md3gV>^$5^JoDw7cwKZBeKJu&WPoSV zJWXk`8WdJ|j%W>R0|7W-1J9CsCj_j_ry(-H^zd1WX;4@prMb=V8+yhmT(RlH4}UlH z3EaDh^kQa4Cg#)F$^D*R-iGm@&Kz`eR!MmK5Io zIFqgmk!!ltC#dBFa_&%C3TtoRRj!FrpQM(vWg*%$xw%F)P~@5<^@(aZ8@JQen<)lr zvN}g(wVbq=oWEoSnzZDF1FsiZEq^B|;f2Po==y8oxEJ9dNdJmvB!!%L>)p4AwUWL1 z`nJhXMCo7Aj0DRTJEEE`bwqvgxOy)>>&Vi-sFL(Ym7%L;aHT0Kb%bR< z)kcRgDaTK69uSoB$G67AT7OQioIx8>QX~=gfRJ+h1m^*6`WMu4awRlUK&B2TT2hW5 z`#iue=B(+pZ|t|MFT+E(-n~>y7^dD-x=9zPx+AsFc5iVOYz_LLmjC5C$g4k98i9 zqxQ)_-z#Y()Jxg?^16@9=q`pEq80X0^Gxn?hcvqhXAa1u32UYr?sO9AL-+!#;w|~y59`6R{fr8qt zP5qJkX8Dz5 z&ZU1*EvKbW2!AjX+{JCr^m^K_=KkI|4-}N<`aCdI%D;$eIV;|xmtbq*7vP{bV8B6( zS?JYypu&wh4`|bWLW2E;v)zBQT8+-o48-W11NV3J&|01g0Yrs(BfJx|;U)}wk zI1g}G>)P}mWVUuMa$;*7vS-Y5PR&ABKMu;*#dPgne1GmFaVY83UB9aRedcjcSjs|wVAOKca^DmpkzHLGOLG@D zLtGmD8t(7?^T2Sue{GAc=v%`t#A!x;aq`&cJkVS3Uq{ahre>-;#-G3={X-`MbI^VSIrsbEd9?xa_d*?hbS{G5ih@N%s77f3u4-LsL;r=dO7ipj% z?dj=Rqqekd3#9vdb^T^y%~qkDuC z?(gt=o_(S^WMsUQo(21FV!y`L3fol5sa&GGH-Agd%6kI#YdmKn92DmwS>BtbXC*P` zYi#CxNjRU1bCEFbT}02?z>iW%;+)C(RGf=cyd;<-H1w?P5$)SH6FHoN0Xohn|6DZL zVnj#Ja=#qrIh-Dkz~SurrR99`w+nHLk!$E#<}A}VIh_6Vd-@~he6nS(#mF>E&3WhJ zmVf70fJ4CfWXnv8k&DQB#smksueCIs&!H$r7-8qVUVZ*u%TxO)!sjJ*>+^|1 zw_ha^UQ(|z9bC#Yn`VQO{$4(}r=1rp3tl<)m5X_WOX) zv=|xG=l@b126<1^V&trI`-M1+^Im&!v(D|;!NAFTz0uBRw2Xc!3JW9e^+y-=`M(#3 z8D7#C*XMtch}Xr7e^UJ0L|(W2%0QnK|4t$D9%IyNep37!#m_6ud%X4jueg88zq;O~ l^6##9o&0m_T`4bc|Np)hPyDE6RP+D<002ovPDHLkV1mAkLec;L delta 6214 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 diff --git a/public/images/pokemon/variant/exp/800-ultra.json b/public/images/pokemon/variant/exp/800-ultra.json index 53dd9b55df0..cab917ec271 100644 --- a/public/images/pokemon/variant/exp/800-ultra.json +++ b/public/images/pokemon/variant/exp/800-ultra.json @@ -1,21 +1,5 @@ { "1": { - "b0a080": "e552ec", - "f8f8e8": "ffe2ed", - "9b8259": "b021c5", - "e5e4c2": "ffb9f9", - "000000": "000000", - "bc9b4e": "900090", - "f8f8d0": "ff8ae9", - "e8e088": "ff49e7", - "d0b868": "d10cc7", - "7d673b": "510059", - "282828": "282828", - "f84040": "f84040", - "f88888": "1ae2e6", - "c81010": "00c2d2" - }, - "2": { "b0a080": "d96b23", "f8f8e8": "ffe1b8", "9b8259": "b43c06", @@ -30,5 +14,21 @@ "f84040": "f84040", "f88888": "f88888", "c81010": "c81010" + }, + "2": { + "b0a080": "e552ec", + "f8f8e8": "ffe2ed", + "9b8259": "b021c5", + "e5e4c2": "ffb9f9", + "000000": "000000", + "bc9b4e": "900090", + "f8f8d0": "ff8ae9", + "e8e088": "ff49e7", + "d0b868": "d10cc7", + "7d673b": "510059", + "282828": "282828", + "f84040": "f84040", + "f88888": "1ae2e6", + "c81010": "00c2d2" } } \ No newline at end of file diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 674b4e256f9..4faf3863e3c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2421,7 +2421,6 @@ export default class BattleScene extends SceneBase { getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); }); - this.updateModifiers(false).then(() => resolve()); }); } diff --git a/src/data/ability.ts b/src/data/ability.ts index cfd900d621c..38ca4eb25d0 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2395,16 +2395,16 @@ export class PreStatChangeAbAttr extends AbAttr { } export class ProtectStatAbAttr extends PreStatChangeAbAttr { - private protectedStat: BattleStat | null; + private protectedStat?: BattleStat; constructor(protectedStat?: BattleStat) { super(); - this.protectedStat = protectedStat ?? null; + this.protectedStat = protectedStat; } applyPreStatChange(pokemon: Pokemon, passive: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!this.protectedStat || stat === this.protectedStat) { + if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) { cancelled.value = true; return true; } diff --git a/src/data/move.ts b/src/data/move.ts index 79e67ece581..24651bacb2e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4441,7 +4441,7 @@ export class CurseAttr extends MoveEffectAttr { const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true); user.scene.queueMessage( - i18next.t("battle:cursedOnAdd", { + i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(target) }) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f1721299ad0..e38813ed3c0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -921,7 +921,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * by how many learnable moves there are for the {@linkcode Pokemon}. */ getLearnableLevelMoves(): Moves[] { - let levelMoves = this.getLevelMoves(1, true).map(lm => lm[1]); + let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } @@ -1210,11 +1210,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @param source - The Pokémon using the move. * @param move - The move being used. - * @returns The type damage multiplier or undefined if it's a status move + * @returns The type damage multiplier or 1 if it's a status move */ - getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier | undefined { + getMoveEffectiveness(source: Pokemon, move: PokemonMove): TypeDamageMultiplier { if (move.getMove().category === MoveCategory.STATUS) { - return undefined; + return 1; } return this.getAttackMoveEffectiveness(source, move, !this.battleData?.abilityRevealed); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 1dff041a14e..f4ec6c499f4 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2414,7 +2414,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); + return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: getPokemonNameWithAffix(pokemon), typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { diff --git a/src/phases.ts b/src/phases.ts index 88acfc825ef..c50d25acf60 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -878,6 +878,10 @@ export class EncounterPhase extends BattlePhase { } else if (!(battle.waveIndex % 1000)) { enemyPokemon.formIndex = 1; enemyPokemon.updateScale(); + const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; + this.scene.removeModifier(bossMBH!); + bossMBH?.setTransferrableFalse(); + this.scene.addEnemyModifier(bossMBH!); } } @@ -4033,13 +4037,24 @@ export class FaintPhase extends PokemonPhase { } if (this.player) { - const nonFaintedLegalPartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); - const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length; - if (!nonFaintedPartyMemberCount) { + /** The total number of Pokemon in the player's party that can legally fight */ + const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + /** The total number of legal player Pokemon that aren't currently on the field */ + const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); + if (!legalPlayerPokemon.length) { + /** If the player doesn't have any legal Pokemon, end the game */ this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { + } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + /** + * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon + * is already on the field, unshift a phase that moves that Pokemon to center position. + */ this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) { + } else if (legalPlayerPartyPokemon.length > 0) { + /** + * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, + * push a phase that prompts the player to summon a Pokemon from their party. + */ this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } } else { diff --git a/src/test/abilities/hyper_cutter.test.ts b/src/test/abilities/hyper_cutter.test.ts new file mode 100644 index 00000000000..9637a80ddb4 --- /dev/null +++ b/src/test/abilities/hyper_cutter.test.ts @@ -0,0 +1,58 @@ +import { BattleStat } from "#app/data/battle-stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { SPLASH_ONLY } from "#test/utils/testUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Hyper Cutter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .moveset([Moves.SAND_ATTACK, Moves.NOBLE_ROAR, Moves.DEFOG, Moves.OCTOLOCK]) + .ability(Abilities.BALL_FETCH) + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.HYPER_CUTTER) + .enemyMoveset(SPLASH_ONLY); + }); + + // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability) + + it("only prevents ATK drops", async () => { + await game.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.DEFOG)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.NOBLE_ROAR)); + await game.toNextTurn(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SAND_ATTACK)); + await game.toNextTurn(); + game.override.moveset([Moves.STRING_SHOT]); + game.doAttack(getMovePosition(game.scene, 0, Moves.STRING_SHOT)); + await game.toNextTurn(); + + expect(enemy.summonData.battleStats[BattleStat.ATK]).toEqual(0); + [BattleStat.ACC, BattleStat.DEF, BattleStat.EVA, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD].forEach((stat: number) => expect(enemy.summonData.battleStats[stat]).toBeLessThan(0)); + }); +}); diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index ad323c447f5..728fe1ecd45 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -12,6 +12,7 @@ import { SPLASH_ONLY } from "#test/utils/testUtils"; describe("Moves - Rollout", () => { let phaserGame: Phaser.Game; let game: GameManager; + const TIMEOUT = 20 * 1000; beforeAll(() => { phaserGame = new Phaser.Game({ @@ -77,5 +78,5 @@ describe("Moves - Rollout", () => { // reset expect(turn6Dmg).toBeGreaterThanOrEqual(turn1Dmg - variance); expect(turn6Dmg).toBeLessThanOrEqual(turn1Dmg + variance); - }); + }, TIMEOUT); }); diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index bbb9a823ad9..9315971e484 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -87,7 +87,6 @@ describe("UI - Transfer Items", () => { handler.processInput(Button.ACTION); // select Pokemon expect(handler.optionsContainer.list.some((option) => (option as BBCodeText).text?.includes("Transfer"))).toBe(true); - game.phaseInterceptor.unlock(); }); diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts new file mode 100644 index 00000000000..eb0191812e8 --- /dev/null +++ b/src/test/ui/type-hints.test.ts @@ -0,0 +1,89 @@ +import { Button } from "#app/enums/buttons.js"; +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { CommandPhase } from "#app/phases"; +import FightUiHandler from "#app/ui/fight-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import MockText from "../utils/mocks/mocksContainer/mockText"; +import { SPLASH_ONLY } from "../utils/testUtils"; + +describe("UI - Type Hints", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + game.settings.typeHints(true); //activate type hints + game.override.battleType("single").startingLevel(100).startingWave(1).enemyMoveset(SPLASH_ONLY); + }); + + it("check immunity color", async () => { + game.override + .battleType("single") + .startingLevel(100) + .startingWave(1) + .enemySpecies(Species.FLORGES) + .enemyMoveset(SPLASH_ONLY) + .moveset([Moves.DRAGON_CLAW]); + game.settings.typeHints(true); //activate type hints + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const dragonClawText = movesContainer + .getAll() + .find((text) => text.text === "Dragon Claw")! as unknown as MockText; + + expect.soft(dragonClawText.color).toBe("#929292"); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); + + it("check status move color", async () => { + game.override.enemySpecies(Species.FLORGES).moveset([Moves.GROWL]); + + await game.startBattle([Species.RAYQUAZA]); + + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + const { ui } = game.scene; + const handler = ui.getHandler(); + handler.processInput(Button.ACTION); // select "Fight" + game.phaseInterceptor.unlock(); + }); + + game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { + const { ui } = game.scene; + const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); + const growlText = movesContainer + .getAll() + .find((text) => text.text === "Growl")! as unknown as MockText; + + expect.soft(growlText.color).toBe(undefined); + ui.getHandler().processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(CommandPhase); + }); +}); diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 27ba7a215eb..6333179e3b2 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -30,6 +30,7 @@ import { MoveHelper } from "./helpers/moveHelper"; import { vi } from "vitest"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; +import { SettingsHelper } from "./helpers/settingsHelper"; /** * Class to manage the game state and transitions between phases. @@ -44,6 +45,7 @@ export default class GameManager { public readonly move: MoveHelper; public readonly classicMode: ClassicModeHelper; public readonly dailyMode: DailyModeHelper; + public readonly settings: SettingsHelper; /** * Creates an instance of GameManager. @@ -63,6 +65,7 @@ export default class GameManager { this.move = new MoveHelper(this); this.classicMode = new ClassicModeHelper(this); this.dailyMode = new DailyModeHelper(this); + this.settings = new SettingsHelper(this); } /** diff --git a/src/test/utils/helpers/settingsHelper.ts b/src/test/utils/helpers/settingsHelper.ts new file mode 100644 index 00000000000..dec9e160d51 --- /dev/null +++ b/src/test/utils/helpers/settingsHelper.ts @@ -0,0 +1,15 @@ +import { GameManagerHelper } from "./gameManagerHelper"; + +/** + * Helper to handle settings for tests + */ +export class SettingsHelper extends GameManagerHelper { + + /** + * Disable/Enable type hints settings + * @param enable true to enabled, false to disabled + */ + typeHints(enable: boolean) { + this.game.scene.typeHints = enable; + } +} diff --git a/src/test/utils/mocks/mocksContainer/mockContainer.ts b/src/test/utils/mocks/mocksContainer/mockContainer.ts index d3672cb5235..5babd9e71b2 100644 --- a/src/test/utils/mocks/mocksContainer/mockContainer.ts +++ b/src/test/utils/mocks/mocksContainer/mockContainer.ts @@ -1,4 +1,5 @@ import MockTextureManager from "#test/utils/mocks/mockTextureManager"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { @@ -13,6 +14,7 @@ export default class MockContainer implements MockGameObject { public frame; protected textureManager; public list: MockGameObject[] = []; + private name?: string; constructor(textureManager: MockTextureManager, x, y) { this.x = x; @@ -159,9 +161,10 @@ export default class MockContainer implements MockGameObject { // Moves this Game Object to be below the given Game Object in the display list. } - setName(name) { + setName = vi.fn((name: string) => { + this.name = name; // return this.phaserSprite.setName(name); - } + }); bringToTop(obj) { // Brings this Game Object to the top of its parents display list. diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index 5d405efadfd..6b9ecf083fd 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -1,4 +1,5 @@ import UI from "#app/ui/ui"; +import { vi } from "vitest"; import { MockGameObject } from "../mockGameObject"; export default class MockText implements MockGameObject { @@ -10,6 +11,8 @@ export default class MockText implements MockGameObject { public list: MockGameObject[] = []; public style; public text = ""; + private name?: string; + public color?: string; constructor(textureManager, x, y, content, styleOptions) { this.scene = textureManager.scene; @@ -190,10 +193,9 @@ export default class MockText implements MockGameObject { }; } - setColor(color) { - // Sets the tint of this Game Object. - // return this.phaserText.setColor(color); - } + setColor = vi.fn((color: string) => { + this.color = color; + }); setShadowColor(color) { // Sets the shadow color. @@ -219,9 +221,9 @@ export default class MockText implements MockGameObject { // return this.phaserText.setAlpha(alpha); } - setName(name) { - // return this.phaserText.setName(name); - } + setName = vi.fn((name: string) => { + this.name = name; + }); setAlign(align) { // return this.phaserText.setAlign(align); diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 1c7dfb27630..7a30e2787df 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -226,7 +226,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { highestIv = ivs[s]; } }); - if (shownStat) { + if (shownStat !== null && shownStat !== undefined) { shownStats.push(shownStat); statsPool.splice(statsPool.indexOf(shownStat), 1); } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 8279ab72a70..4ade6ca5d20 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -12,6 +12,8 @@ import {Button} from "#enums/buttons"; import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; export default class FightUiHandler extends UiHandler { + public static readonly MOVES_CONTAINER_NAME = "moves"; + private movesContainer: Phaser.GameObjects.Container; private moveInfoContainer: Phaser.GameObjects.Container; private typeIcon: Phaser.GameObjects.Sprite; @@ -35,7 +37,7 @@ export default class FightUiHandler extends UiHandler { const ui = this.getUi(); this.movesContainer = this.scene.add.container(18, -38.7); - this.movesContainer.setName("moves"); + this.movesContainer.setName(FightUiHandler.MOVES_CONTAINER_NAME); ui.add(this.movesContainer); this.moveInfoContainer = this.scene.add.container(1, 0); @@ -271,11 +273,10 @@ export default class FightUiHandler extends UiHandler { return undefined; } - const moveColors = opponents.map((opponent) => { - return opponent.getMoveEffectiveness(pokemon, pokemonMove); - }).filter((eff) => !!eff).sort((a, b) => b - a).map((effectiveness) => { - return getTypeDamageMultiplierColor(effectiveness, "offense"); - }); + const moveColors = opponents + .map((opponent) => opponent.getMoveEffectiveness(pokemon, pokemonMove)) + .sort((a, b) => b - a) + .map((effectiveness) => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense")); return moveColors[0]; } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5e942f3e75a..9f2df1f2329 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2916,14 +2916,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isCaught = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); const isVariant3Caught = !!(isCaught & DexAttr.VARIANT_3); const isVariant2Caught = !!(isCaught & DexAttr.VARIANT_2); + const isDefaultVariantCaught = !!(isCaught & DexAttr.DEFAULT_VARIANT); const isVariantCaught = !!(isCaught & DexAttr.SHINY); const isMaleCaught = !!(isCaught & DexAttr.MALE); const isFemaleCaught = !!(isCaught & DexAttr.FEMALE); + const starterAttributes = this.starterPreferences[species.speciesId]; + + const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); + const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + if (!dexEntry.caughtAttr) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); if (shiny === undefined || shiny !== props.shiny) { shiny = props.shiny; } @@ -2942,6 +2946,83 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (natureIndex === undefined || natureIndex !== defaultNature) { natureIndex = defaultNature; } + } else { + // compare current shiny, formIndex, female, variant, abilityIndex, natureIndex with the caught ones + // if the current ones are not caught, we need to find the next caught ones + if (shiny) { + if (!(isVariantCaught || isVariant2Caught || isVariant3Caught)) { + shiny = false; + starterAttributes.shiny = false; + variant = 0; + starterAttributes.variant = 0; + } else { + shiny = true; + starterAttributes.shiny = true; + if (variant === 0 && !isDefaultVariantCaught) { + if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 1 && !isVariant2Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant3Caught) { + variant = 2; + starterAttributes.variant = 2; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } else if (variant === 2 && !isVariant3Caught) { + if (isVariantCaught) { + variant = 0; + starterAttributes.variant = 0; + } else if (isVariant2Caught) { + variant = 1; + starterAttributes.variant = 1; + } else { + variant = 0; + starterAttributes.variant = 0; + } + } + } + } + if (female) { + if (!isFemaleCaught) { + female = false; + starterAttributes.female = false; + } + } else { + if (!isMaleCaught) { + female = true; + starterAttributes.female = true; + } + } + + if (species.forms) { + const formCount = species.forms.length; + let newFormIndex = formIndex??0; + if (species.forms[newFormIndex]) { + const isValidForm = species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex); + if (!isValidForm) { + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (species.forms[newFormIndex].isStarterSelectable && dexEntry.caughtAttr & this.scene.gameData.getFormAttr(newFormIndex)) { + break; + } + } while (newFormIndex !== props.formIndex); + formIndex = newFormIndex; + starterAttributes.form = formIndex; + } + } + } } this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default? @@ -2993,12 +3074,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } if (dexEntry.caughtAttr && species.malePercent !== null) { - let gender: Gender; - if ((female && isFemaleCaught) || (!female && !isMaleCaught)) { - gender = Gender.FEMALE; - } else { - gender = Gender.MALE; - } + const gender = !female ? Gender.MALE : Gender.FEMALE; this.pokemonGenderText.setText(getGenderSymbol(gender)); this.pokemonGenderText.setColor(getGenderColor(gender)); this.pokemonGenderText.setShadowColor(getGenderColor(gender, true)); @@ -3479,7 +3555,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) { if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) { - console.log(`${species.name}'s variant icon does not exist. Replacing with default.`); + console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`); icon.setTexture(species.getIconAtlasKey(formIndex, false, variant)); icon.setFrame(species.getIconId(female, formIndex, false, variant)); } diff --git a/src/utils.ts b/src/utils.ts index aa45c091286..c51ac2b5b0b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -552,3 +552,11 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole } return null; } + +/** + * Returns if an object is null or undefined + * @param object + */ +export function isNullOrUndefined(object: any): boolean { + return null === object || undefined === object; +} From ae2ab120dce57cc2ff065f3c756a4a3696a3e909 Mon Sep 17 00:00:00 2001 From: Frederico Santos Date: Mon, 19 Aug 2024 03:23:52 +0100 Subject: [PATCH 26/26] Deleted phases.ts (#3618) --- src/battle-scene.ts | 18 +- src/data/ability.ts | 5 +- src/data/arena-tag.ts | 5 +- src/data/battler-tags.ts | 7 +- src/data/berry.ts | 3 +- src/data/move.ts | 10 +- src/field/arena.ts | 2 +- src/field/pokemon.ts | 10 +- src/modifier/modifier.ts | 6 +- src/phases.ts | 5861 ----------------- src/phases/add-enemy-buff-modifier-phase.ts | 26 + src/phases/attempt-capture-phase.ts | 288 + src/phases/attempt-run-phase.ts | 56 + src/phases/battle-end-phase.ts | 55 + src/phases/battle-phase.ts | 47 + src/phases/berry-phase.ts | 52 + src/phases/check-switch-phase.ts | 61 + src/phases/command-phase.ts | 288 + src/phases/common-anim-phase.ts | 26 + src/phases/damage-phase.ts | 84 + src/{ => phases}/egg-hatch-phase.ts | 27 +- src/phases/egg-lapse-phase.ts | 35 + src/phases/encounter-phase.ts | 379 ++ src/phases/end-card-phase.ts | 40 + src/phases/end-evolution-phase.ts | 16 + src/phases/enemy-command-phase.ts | 86 + .../enemy-party-member-pokemon-phase.ts | 13 + src/{ => phases}/evolution-phase.ts | 36 +- src/phases/exp-phase.ts | 35 + src/phases/faint-phase.ts | 171 + src/phases/field-phase.ts | 44 + src/{ => phases}/form-change-phase.ts | 148 +- src/phases/game-over-modifier-reward-phase.ts | 27 + src/phases/game-over-phase.ts | 203 + src/phases/hide-party-exp-bar-phase.ts | 14 + src/phases/learn-move-phase.ts | 103 + src/phases/level-cap-phase.ts | 20 + src/phases/level-up-phase.ts | 59 + src/phases/login-phase.ts | 116 + src/phases/message-phase.ts | 38 + src/phases/modifier-reward-phase.ts | 30 + src/phases/money-reward-phase.ts | 34 + src/phases/move-anim-test-phase.ts | 44 + src/phases/move-effect-phase.ts | 447 ++ src/phases/move-end-phase.ts | 23 + src/phases/move-header-phase.ts | 30 + src/phases/move-phase.ts | 329 + src/phases/new-battle-phase.ts | 11 + src/phases/new-biome-encounter-phase.ts | 38 + src/phases/next-encounter-phase.ts | 46 + src/phases/obtain-status-effect-phase.ts | 48 + src/phases/outdated-phase.ts | 13 + src/phases/party-heal-phase.ts | 40 + src/phases/party-member-pokemon-phase.ts | 27 + src/phases/party-status-cure-phase.ts | 48 + .../player-party-member-pokemon-phase.ts | 13 + src/phases/pokemon-heal-phase.ts | 104 + src/phases/pokemon-phase.ts | 29 + src/phases/post-game-over-phase.ts | 46 + src/phases/post-summon-phase.ts | 24 + src/phases/post-turn-status-effect-phase.ts | 61 + src/phases/quiet-form-change-phase.ts | 133 + src/phases/reload-session-phase.ts | 39 + src/phases/return-phase.ts | 26 + src/phases/ribbon-modifier-reward-phase.ts | 33 + src/phases/scan-ivs-phase.ts | 69 + src/phases/select-biome-phase.ts | 84 + src/phases/select-challenge-phase.ts | 17 + src/phases/select-gender-phase.ts | 46 + src/phases/select-modifier-phase.ts | 234 + src/phases/select-starter-phase.ts | 112 + src/phases/select-target-phase.ts | 32 + src/phases/shiny-sparkle-phase.ts | 16 + src/phases/show-ability-phase.ts | 29 + src/phases/show-party-exp-bar-phase.ts | 56 + src/phases/show-trainer-phase.ts | 24 + src/phases/stat-change-phase.ts | 234 + src/phases/summon-missing-phase.ts | 15 + src/phases/summon-phase.ts | 194 + src/phases/switch-biome-phase.ts | 65 + src/phases/switch-phase.ts | 65 + src/phases/switch-summon-phase.ts | 168 + src/phases/test-message-phase.ts | 8 + src/phases/title-phase.ts | 303 + src/phases/toggle-double-position-phase.ts | 31 + src/phases/trainer-message-test-phase.ts | 41 + src/phases/trainer-victory-phase.ts | 65 + src/phases/turn-end-phase.ts | 71 + src/phases/turn-init-phase.ts | 65 + src/phases/turn-start-phase.ts | 172 + src/phases/unavailable-phase.ts | 17 + src/phases/unlock-phase.ts | 27 + src/phases/victory-phase.ts | 151 + src/phases/weather-effect-phase.ts | 67 + src/system/game-data.ts | 3 +- src/system/settings/settings.ts | 10 +- src/system/voucher.ts | 67 +- src/test/abilities/ability_timing.test.ts | 4 +- src/test/abilities/aura_break.test.ts | 2 +- src/test/abilities/battery.test.ts | 3 +- src/test/abilities/battle_bond.test.ts | 4 +- src/test/abilities/costar.test.ts | 3 +- src/test/abilities/disguise.test.ts | 6 +- src/test/abilities/dry_skin.test.ts | 2 +- src/test/abilities/flash_fire.test.ts | 3 +- src/test/abilities/gulp_missile.test.ts | 10 +- src/test/abilities/heatproof.test.ts | 2 +- src/test/abilities/hustle.test.ts | 3 +- src/test/abilities/ice_face.test.ts | 7 +- src/test/abilities/intimidate.test.ts | 7 +- src/test/abilities/intrepid_sword.test.ts | 2 +- src/test/abilities/libero.test.ts | 2 +- src/test/abilities/magic_guard.test.ts | 2 +- src/test/abilities/moxie.test.ts | 4 +- src/test/abilities/mycelium_might.test.ts | 3 +- src/test/abilities/parental_bond.test.ts | 7 +- src/test/abilities/pastel_veil.test.ts | 3 +- src/test/abilities/power_construct.test.ts | 4 +- src/test/abilities/power_spot.test.ts | 3 +- src/test/abilities/protean.test.ts | 2 +- src/test/abilities/quick_draw.test.ts | 2 +- src/test/abilities/sand_veil.test.ts | 4 +- src/test/abilities/sap_sipper.test.ts | 3 +- src/test/abilities/schooling.test.ts | 4 +- src/test/abilities/screen_cleaner.test.ts | 3 +- src/test/abilities/serene_grace.test.ts | 3 +- src/test/abilities/sheer_force.test.ts | 3 +- src/test/abilities/shield_dust.test.ts | 3 +- src/test/abilities/shields_down.test.ts | 4 +- src/test/abilities/stall.test.ts | 2 +- src/test/abilities/steely_spirit.test.ts | 3 +- src/test/abilities/sturdy.test.ts | 3 +- src/test/abilities/sweet_veil.test.ts | 4 +- src/test/abilities/unseen_fist.test.ts | 2 +- src/test/abilities/volt_absorb.test.ts | 2 +- src/test/abilities/wind_power.test.ts | 2 +- src/test/abilities/wind_rider.test.ts | 2 +- src/test/abilities/wonder_skin.test.ts | 2 +- src/test/abilities/zen_mode.test.ts | 13 +- src/test/abilities/zero_to_hero.test.ts | 4 +- src/test/arena/arena_gravity.test.ts | 3 +- src/test/arena/weather_fog.test.ts | 2 +- src/test/arena/weather_strong_winds.test.ts | 2 +- src/test/battle/battle-order.test.ts | 5 +- src/test/battle/battle.test.ts | 30 +- src/test/battle/double_battle.test.ts | 3 +- src/test/battle/special_battle.test.ts | 2 +- src/test/battlerTags/octolock.test.ts | 2 +- src/test/battlerTags/stockpiling.test.ts | 2 +- src/test/items/grip_claw.test.ts | 4 +- src/test/items/leek.test.ts | 2 +- src/test/items/leftovers.test.ts | 3 +- src/test/items/lock_capsule.test.ts | 2 +- src/test/items/scope_lens.test.ts | 2 +- src/test/items/toxic_orb.test.ts | 5 +- src/test/moves/astonish.test.ts | 5 +- src/test/moves/aurora_veil.test.ts | 2 +- src/test/moves/baton_pass.test.ts | 3 +- src/test/moves/beak_blast.test.ts | 4 +- src/test/moves/beat_up.test.ts | 2 +- src/test/moves/belly_drum.test.ts | 2 +- src/test/moves/ceaseless_edge.test.ts | 3 +- src/test/moves/clangorous_soul.test.ts | 2 +- src/test/moves/crafty_shield.test.ts | 3 +- src/test/moves/double_team.test.ts | 2 +- src/test/moves/dragon_rage.test.ts | 2 +- src/test/moves/dragon_tail.test.ts | 4 +- src/test/moves/dynamax_cannon.test.ts | 4 +- src/test/moves/fillet_away.test.ts | 2 +- src/test/moves/fissure.test.ts | 3 +- src/test/moves/flame_burst.test.ts | 3 +- src/test/moves/flower_shield.test.ts | 2 +- src/test/moves/focus_punch.test.ts | 6 +- src/test/moves/follow_me.test.ts | 4 +- src/test/moves/foresight.test.ts | 2 +- src/test/moves/freezy_frost.test.ts | 3 +- src/test/moves/fusion_flare.test.ts | 2 +- src/test/moves/fusion_flare_bolt.test.ts | 5 +- src/test/moves/glaive_rush.test.ts | 3 +- src/test/moves/growth.test.ts | 4 +- src/test/moves/hard_press.test.ts | 2 +- src/test/moves/haze.test.ts | 3 +- src/test/moves/hyper_beam.test.ts | 3 +- src/test/moves/light_screen.test.ts | 2 +- src/test/moves/lucky_chant.test.ts | 3 +- src/test/moves/magnet_rise.test.ts | 3 +- src/test/moves/make_it_rain.test.ts | 3 +- src/test/moves/mat_block.test.ts | 4 +- src/test/moves/miracle_eye.test.ts | 2 +- src/test/moves/multi_target.test.ts | 2 +- src/test/moves/octolock.test.ts | 4 +- src/test/moves/parting_shot.test.ts | 5 +- src/test/moves/protect.test.ts | 2 +- src/test/moves/purify.test.ts | 2 +- src/test/moves/quick_guard.test.ts | 3 +- src/test/moves/rage_powder.test.ts | 4 +- src/test/moves/reflect.test.ts | 2 +- src/test/moves/rollout.test.ts | 2 +- src/test/moves/roost.test.ts | 3 +- src/test/moves/shell_trap.test.ts | 4 +- src/test/moves/spikes.test.ts | 2 +- src/test/moves/spit_up.test.ts | 3 +- src/test/moves/spotlight.test.ts | 4 +- src/test/moves/stockpile.test.ts | 3 +- src/test/moves/swallow.test.ts | 3 +- src/test/moves/tackle.test.ts | 4 +- src/test/moves/tail_whip.test.ts | 4 +- src/test/moves/tailwind.test.ts | 2 +- src/test/moves/thousand_arrows.test.ts | 3 +- src/test/moves/tidy_up.test.ts | 3 +- src/test/moves/u_turn.test.ts | 3 +- src/test/moves/wide_guard.test.ts | 3 +- src/test/phases/phases.test.ts | 4 +- src/test/ui/starter-select.test.ts | 4 +- src/test/ui/transfer-item.test.ts | 3 +- src/test/ui/type-hints.test.ts | 2 +- src/test/utils/gameManager.ts | 13 +- src/test/utils/helpers/classicModeHelper.ts | 3 +- src/test/utils/helpers/dailyModeHelper.ts | 3 +- src/test/utils/helpers/moveHelper.ts | 2 +- src/test/utils/phaseInterceptor.ts | 74 +- src/ui/ball-ui-handler.ts | 2 +- src/ui/challenges-select-ui-handler.ts | 3 +- src/ui/command-ui-handler.ts | 2 +- src/ui/egg-hatch-scene-handler.ts | 2 +- src/ui/fight-ui-handler.ts | 2 +- src/ui/party-ui-handler.ts | 3 +- src/ui/starter-select-ui-handler.ts | 3 +- 228 files changed, 7037 insertions(+), 6279 deletions(-) delete mode 100644 src/phases.ts create mode 100644 src/phases/add-enemy-buff-modifier-phase.ts create mode 100644 src/phases/attempt-capture-phase.ts create mode 100644 src/phases/attempt-run-phase.ts create mode 100644 src/phases/battle-end-phase.ts create mode 100644 src/phases/battle-phase.ts create mode 100644 src/phases/berry-phase.ts create mode 100644 src/phases/check-switch-phase.ts create mode 100644 src/phases/command-phase.ts create mode 100644 src/phases/common-anim-phase.ts create mode 100644 src/phases/damage-phase.ts rename src/{ => phases}/egg-hatch-phase.ts (95%) create mode 100644 src/phases/egg-lapse-phase.ts create mode 100644 src/phases/encounter-phase.ts create mode 100644 src/phases/end-card-phase.ts create mode 100644 src/phases/end-evolution-phase.ts create mode 100644 src/phases/enemy-command-phase.ts create mode 100644 src/phases/enemy-party-member-pokemon-phase.ts rename src/{ => phases}/evolution-phase.ts (96%) create mode 100644 src/phases/exp-phase.ts create mode 100644 src/phases/faint-phase.ts create mode 100644 src/phases/field-phase.ts rename src/{ => phases}/form-change-phase.ts (57%) create mode 100644 src/phases/game-over-modifier-reward-phase.ts create mode 100644 src/phases/game-over-phase.ts create mode 100644 src/phases/hide-party-exp-bar-phase.ts create mode 100644 src/phases/learn-move-phase.ts create mode 100644 src/phases/level-cap-phase.ts create mode 100644 src/phases/level-up-phase.ts create mode 100644 src/phases/login-phase.ts create mode 100644 src/phases/message-phase.ts create mode 100644 src/phases/modifier-reward-phase.ts create mode 100644 src/phases/money-reward-phase.ts create mode 100644 src/phases/move-anim-test-phase.ts create mode 100644 src/phases/move-effect-phase.ts create mode 100644 src/phases/move-end-phase.ts create mode 100644 src/phases/move-header-phase.ts create mode 100644 src/phases/move-phase.ts create mode 100644 src/phases/new-battle-phase.ts create mode 100644 src/phases/new-biome-encounter-phase.ts create mode 100644 src/phases/next-encounter-phase.ts create mode 100644 src/phases/obtain-status-effect-phase.ts create mode 100644 src/phases/outdated-phase.ts create mode 100644 src/phases/party-heal-phase.ts create mode 100644 src/phases/party-member-pokemon-phase.ts create mode 100644 src/phases/party-status-cure-phase.ts create mode 100644 src/phases/player-party-member-pokemon-phase.ts create mode 100644 src/phases/pokemon-heal-phase.ts create mode 100644 src/phases/pokemon-phase.ts create mode 100644 src/phases/post-game-over-phase.ts create mode 100644 src/phases/post-summon-phase.ts create mode 100644 src/phases/post-turn-status-effect-phase.ts create mode 100644 src/phases/quiet-form-change-phase.ts create mode 100644 src/phases/reload-session-phase.ts create mode 100644 src/phases/return-phase.ts create mode 100644 src/phases/ribbon-modifier-reward-phase.ts create mode 100644 src/phases/scan-ivs-phase.ts create mode 100644 src/phases/select-biome-phase.ts create mode 100644 src/phases/select-challenge-phase.ts create mode 100644 src/phases/select-gender-phase.ts create mode 100644 src/phases/select-modifier-phase.ts create mode 100644 src/phases/select-starter-phase.ts create mode 100644 src/phases/select-target-phase.ts create mode 100644 src/phases/shiny-sparkle-phase.ts create mode 100644 src/phases/show-ability-phase.ts create mode 100644 src/phases/show-party-exp-bar-phase.ts create mode 100644 src/phases/show-trainer-phase.ts create mode 100644 src/phases/stat-change-phase.ts create mode 100644 src/phases/summon-missing-phase.ts create mode 100644 src/phases/summon-phase.ts create mode 100644 src/phases/switch-biome-phase.ts create mode 100644 src/phases/switch-phase.ts create mode 100644 src/phases/switch-summon-phase.ts create mode 100644 src/phases/test-message-phase.ts create mode 100644 src/phases/title-phase.ts create mode 100644 src/phases/toggle-double-position-phase.ts create mode 100644 src/phases/trainer-message-test-phase.ts create mode 100644 src/phases/trainer-victory-phase.ts create mode 100644 src/phases/turn-end-phase.ts create mode 100644 src/phases/turn-init-phase.ts create mode 100644 src/phases/turn-start-phase.ts create mode 100644 src/phases/unavailable-phase.ts create mode 100644 src/phases/unlock-phase.ts create mode 100644 src/phases/victory-phase.ts create mode 100644 src/phases/weather-effect-phase.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 4faf3863e3c..b72e79c866d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,6 +1,5 @@ import Phaser from "phaser"; import UI from "./ui/ui"; -import { NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, TurnInitPhase, ReturnPhase, LevelCapPhase, ShowTrainerPhase, LoginPhase, MovePhase, TitlePhase, SwitchPhase, SummonPhase, ToggleDoublePositionPhase } from "./phases"; import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species"; import { Constructor } from "#app/utils"; @@ -38,7 +37,7 @@ import { addUiThemeOverrides } from "./ui/ui-theme"; import PokemonData from "./system/pokemon-data"; import { Nature } from "./data/nature"; import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem, SpeciesFormChange } from "./data/pokemon-forms"; -import { FormChangePhase, QuietFormChangePhase } from "./form-change-phase"; +import { FormChangePhase } from "./phases/form-change-phase"; import { getTypeRgb } from "./data/type"; import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler"; import CharSprite from "./ui/char-sprite"; @@ -69,6 +68,21 @@ import i18next from "i18next"; import {TrainerType} from "#enums/trainer-type"; import { battleSpecDialogue } from "./data/dialogue"; import { LoadingScene } from "./loading-scene"; +import { LevelCapPhase } from "./phases/level-cap-phase"; +import { LoginPhase } from "./phases/login-phase"; +import { MessagePhase } from "./phases/message-phase"; +import { MovePhase } from "./phases/move-phase"; +import { NewBiomeEncounterPhase } from "./phases/new-biome-encounter-phase"; +import { NextEncounterPhase } from "./phases/next-encounter-phase"; +import { QuietFormChangePhase } from "./phases/quiet-form-change-phase"; +import { ReturnPhase } from "./phases/return-phase"; +import { SelectBiomePhase } from "./phases/select-biome-phase"; +import { ShowTrainerPhase } from "./phases/show-trainer-phase"; +import { SummonPhase } from "./phases/summon-phase"; +import { SwitchPhase } from "./phases/switch-phase"; +import { TitlePhase } from "./phases/title-phase"; +import { ToggleDoublePositionPhase } from "./phases/toggle-double-position-phase"; +import { TurnInitPhase } from "./phases/turn-init-phase"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; diff --git a/src/data/ability.ts b/src/data/ability.ts index 38ca4eb25d0..8e020849a17 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3,7 +3,6 @@ import { Type } from "./type"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import { Weather, WeatherType } from "./weather"; import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags"; @@ -26,6 +25,10 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; export class Ability implements Localizable { public id: Abilities; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index fdfcd4d076a..3394df827fb 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -4,7 +4,6 @@ import * as Utils from "../utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { HitResult, PokemonMove } from "../field/pokemon"; -import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { StatusEffect } from "./status-effect"; import { BattlerIndex } from "../battle"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; @@ -15,6 +14,10 @@ import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; export enum ArenaTagSide { BOTH, diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index b059b4cf6b2..ede8d029327 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,5 +1,4 @@ import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; -import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import { Stat, getStatName } from "./pokemon-stat"; @@ -18,6 +17,12 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import i18next from "#app/plugins/i18n.js"; +import { CommonAnimPhase } from "#app/phases/common-anim-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase, StatChangeCallback } from "#app/phases/stat-change-phase.js"; export enum BattlerTagLapseType { FAINT, diff --git a/src/data/berry.ts b/src/data/berry.ts index 30b89848452..e962219ca46 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,4 +1,3 @@ -import { PokemonHealPhase, StatChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { HitResult } from "../field/pokemon"; import { BattleStat } from "./battle-stat"; @@ -8,6 +7,8 @@ import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } import i18next from "i18next"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; export function getBerryName(berryType: BerryType): string { return i18next.t(`berry:${BerryType[berryType]}.name`); diff --git a/src/data/move.ts b/src/data/move.ts index 24651bacb2e..af3f49bea0d 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,5 +1,4 @@ import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; -import { BattleEndPhase, MoveEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchPhase, SwitchSummonPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; @@ -28,6 +27,15 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { MoveUsedEvent } from "#app/events/battle-scene.js"; +import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase.js"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { NewBattlePhase } from "#app/phases/new-battle-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; export enum MoveCategory { PHYSICAL, diff --git a/src/field/arena.ts b/src/field/arena.ts index 923a0a4e286..eb3770d61d5 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -4,7 +4,6 @@ import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather"; -import { CommonAnimPhase } from "../phases"; import { CommonAnim } from "../data/battle-anims"; import { Type } from "../data/type"; import Move from "../data/move"; @@ -21,6 +20,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; +import { CommonAnimPhase } from "#app/phases/common-anim-phase.js"; export class Arena { public scene: BattleScene; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e38813ed3c0..6a445a83b4e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -17,7 +17,6 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; -import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases"; import { BattleStat } from "../data/battle-stat"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; @@ -51,6 +50,15 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { LearnMovePhase } from "#app/phases/learn-move-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase.js"; export enum FieldPosition { CENTER, diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index f4ec6c499f4..8a6598f5849 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,5 +1,4 @@ import * as ModifierTypes from "./modifier-type"; -import { LearnMovePhase, LevelUpPhase, PokemonHealPhase } from "../phases"; import BattleScene from "../battle-scene"; import { getLevelTotalExp } from "../data/exp"; import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball"; @@ -7,7 +6,7 @@ import Pokemon, { PlayerPokemon } from "../field/pokemon"; import { Stat } from "../data/pokemon-stat"; import { addTextObject, TextStyle } from "../ui/text"; import { Type } from "../data/type"; -import { EvolutionPhase } from "../evolution-phase"; +import { EvolutionPhase } from "../phases/evolution-phase"; import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions"; import { getPokemonNameWithAffix } from "../messages"; import * as Utils from "../utils"; @@ -28,6 +27,9 @@ import i18next from "i18next"; import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; +import { LearnMovePhase } from "#app/phases/learn-move-phase.js"; +import { LevelUpPhase } from "#app/phases/level-up-phase.js"; +import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; export type ModifierPredicate = (modifier: Modifier) => boolean; diff --git a/src/phases.ts b/src/phases.ts deleted file mode 100644 index c50d25acf60..00000000000 --- a/src/phases.ts +++ /dev/null @@ -1,5861 +0,0 @@ -import BattleScene, { bypassLogin } from "./battle-scene"; -import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; -import * as Utils from "./utils"; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr, MoveHeaderAttr, MoveCategory } from "./data/move"; -import { Mode } from "./ui/ui"; -import { Command } from "./ui/command-ui-handler"; -import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier } from "./modifier/modifier"; -import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; -import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; -import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; -import { StatusEffect, getStatusEffectActivationText, getStatusEffectCatchRateMultiplier, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./data/status-effect"; -import { SummaryUiMode } from "./ui/summary-ui-handler"; -import EvolutionSceneHandler from "./ui/evolution-scene-handler"; -import { EvolutionPhase } from "./evolution-phase"; -import { Phase } from "./phase"; -import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat"; -import { biomeLinks, getBiomeName } from "./data/biomes"; -import { ModifierTier } from "./modifier/modifier-tier"; -import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; -import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags"; -import { getPokemonNameWithAffix } from "./messages"; -import { Starter } from "./ui/starter-select-ui-handler"; -import { Gender } from "./data/gender"; -import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; -import { ArenaTagSide, ArenaTrapTag, ConditionalProtectTag, MistTag, TrickRoomTag } from "./data/arena-tag"; -import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, ChangeMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, PreventBypassSpeedChanceAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr, ReduceBurnDamageAbAttr } from "./data/ability"; -import { Unlockables, getUnlockableName } from "./system/unlockables"; -import { getBiomeKey } from "./field/arena"; -import { BattleType, BattlerIndex, TurnCommand } from "./battle"; -import { ChallengeAchv, HealAchv, LevelAchv, achvs } from "./system/achv"; -import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; -import { EggHatchPhase } from "./egg-hatch-phase"; -import { Egg } from "./data/egg"; -import { vouchers } from "./system/voucher"; -import { clientSessionId, loggedInUser, updateUserInfo } from "./account"; -import { SessionSaveData } from "./system/game-data"; -import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; -import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; -import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; -import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; -import { SettingKeys } from "./system/settings/settings"; -import { Tutorial, handleTutorial } from "./tutorial"; -import { TerrainType } from "./data/terrain"; -import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; -import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; -import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; -import { GameMode, GameModes, getGameMode } from "./game-mode"; -import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; -import i18next from "./plugins/i18n"; -import Overrides from "#app/overrides"; -import { TextStyle, addTextObject, getTextColor } from "./ui/text"; -import { Type } from "./data/type"; -import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./events/battle-scene"; -import { Abilities } from "#enums/abilities"; -import { ArenaTagType } from "#enums/arena-tag-type"; -import { BattleSpec } from "#enums/battle-spec"; -import { BattleStyle } from "#enums/battle-style"; -import { BattlerTagType } from "#enums/battler-tag-type"; -import { Biome } from "#enums/biome"; -import { ExpNotification } from "#enums/exp-notification"; -import { Moves } from "#enums/moves"; -import { PlayerGender } from "#enums/player-gender"; -import { Species } from "#enums/species"; -import { TrainerType } from "#enums/trainer-type"; -import { applyChallenges, ChallengeType } from "./data/challenge"; -import { pokemonEvolutions } from "./data/pokemon-evolutions"; - -const { t } = i18next; - -export class LoginPhase extends Phase { - private showText: boolean; - - constructor(scene: BattleScene, showText?: boolean) { - super(scene); - - this.showText = showText === undefined || !!showText; - } - - start(): void { - super.start(); - - const hasSession = !!Utils.getCookie(Utils.sessionIdKey); - - this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); - Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { - const success = response ? response[0] : false; - const statusCode = response ? response[1] : null; - if (!success) { - if (!statusCode || statusCode === 400) { - if (this.showText) { - this.scene.ui.showText(i18next.t("menu:logInOrCreateAccount")); - } - - this.scene.playSound("menu_open"); - - const loadData = () => { - updateUserInfo().then(success => { - if (!success[0]) { - Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); - return; - } - this.scene.gameData.loadSystem().then(() => this.end()); - }); - }; - - this.scene.ui.setMode(Mode.LOGIN_FORM, { - buttonActions: [ - () => { - this.scene.ui.playSelect(); - loadData(); - }, () => { - this.scene.playSound("menu_open"); - this.scene.ui.setMode(Mode.REGISTRATION_FORM, { - buttonActions: [ - () => { - this.scene.ui.playSelect(); - updateUserInfo().then(success => { - if (!success[0]) { - Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); - return; - } - this.end(); - } ); - }, () => { - this.scene.unshiftPhase(new LoginPhase(this.scene, false)); - this.end(); - } - ] - }); - }, () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; - window.open(discordUrl, "_self"); - }, () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; - window.open(googleUrl, "_self"); - } - ] - }); - } else if (statusCode === 401) { - Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); - } else { - this.scene.unshiftPhase(new UnavailablePhase(this.scene)); - super.end(); - } - return null; - } else { - this.scene.gameData.loadSystem().then(success => { - if (success || bypassLogin) { - this.end(); - } else { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(t("menu:failedToLoadSaveData")); - } - }); - } - }); - } - - end(): void { - this.scene.ui.setMode(Mode.MESSAGE); - - if (!this.scene.gameData.gender) { - this.scene.unshiftPhase(new SelectGenderPhase(this.scene)); - } - - handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); - } -} - -export class TitlePhase extends Phase { - private loaded: boolean; - private lastSessionData: SessionSaveData; - public gameMode: GameModes; - - constructor(scene: BattleScene) { - super(scene); - - this.loaded = false; - } - - start(): void { - super.start(); - - this.scene.ui.clearText(); - this.scene.ui.fadeIn(250); - - this.scene.playBgm("title", true); - - this.scene.gameData.getSession(loggedInUser?.lastSessionSlot ?? -1).then(sessionData => { - if (sessionData) { - this.lastSessionData = sessionData; - const biomeKey = getBiomeKey(sessionData.arena.biome); - const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBg.setTexture(bgTexture); - } - this.showOptions(); - }).catch(err => { - console.error(err); - this.showOptions(); - }); - } - - showOptions(): void { - const options: OptionSelectItem[] = []; - if (loggedInUser && loggedInUser.lastSessionSlot > -1) { - options.push({ - label: i18next.t("continue", {ns: "menu"}), - handler: () => { - this.loadSaveSlot(this.lastSessionData || !loggedInUser ? -1 : loggedInUser.lastSessionSlot); - return true; - } - }); - } - options.push({ - label: i18next.t("menu:newGame"), - handler: () => { - const setModeAndEnd = (gameMode: GameModes) => { - this.gameMode = gameMode; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - this.end(); - }; - if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - const options: OptionSelectItem[] = [ - { - label: GameMode.getModeName(GameModes.CLASSIC), - handler: () => { - setModeAndEnd(GameModes.CLASSIC); - return true; - } - }, - { - label: GameMode.getModeName(GameModes.CHALLENGE), - handler: () => { - setModeAndEnd(GameModes.CHALLENGE); - return true; - } - }, - { - label: GameMode.getModeName(GameModes.ENDLESS), - handler: () => { - setModeAndEnd(GameModes.ENDLESS); - return true; - } - } - ]; - if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { - options.push({ - label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), - handler: () => { - setModeAndEnd(GameModes.SPLICED_ENDLESS); - return true; - } - }); - } - options.push({ - label: i18next.t("menu:cancel"), - handler: () => { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); - super.end(); - return true; - } - }); - this.scene.ui.showText(i18next.t("menu:selectGameMode"), null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); - } else { - this.gameMode = GameModes.CLASSIC; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - this.end(); - } - return true; - } - }, - { - label: i18next.t("menu:loadGame"), - handler: () => { - this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, - (slotId: integer) => { - if (slotId === -1) { - return this.showOptions(); - } - this.loadSaveSlot(slotId); - }); - return true; - } - }, - { - label: i18next.t("menu:dailyRun"), - handler: () => { - this.initDailyRun(); - return true; - }, - keepOpen: true - }, - { - label: i18next.t("menu:settings"), - handler: () => { - this.scene.ui.setOverlayMode(Mode.SETTINGS); - return true; - }, - keepOpen: true - }); - const config: OptionSelectConfig = { - options: options, - noCancel: true, - yOffset: 47 - }; - this.scene.ui.setMode(Mode.TITLE, config); - } - - loadSaveSlot(slotId: integer): void { - this.scene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.resetModeChain(); - this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : undefined).then((success: boolean) => { - if (success) { - this.loaded = true; - this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); - } else { - this.end(); - } - }).catch(err => { - console.error(err); - this.scene.ui.showText(i18next.t("menu:failedToLoadSession"), null); - }); - } - - initDailyRun(): void { - this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { - this.scene.clearPhaseQueue(); - if (slotId === -1) { - this.scene.pushPhase(new TitlePhase(this.scene)); - return super.end(); - } - this.scene.sessionSlotId = slotId; - - const generateDaily = (seed: string) => { - this.scene.gameMode = getGameMode(GameModes.DAILY); - - this.scene.setSeed(seed); - this.scene.resetSeed(1); - - this.scene.money = this.scene.gameMode.getStartingMoney(); - - const starters = getDailyRunStarters(this.scene, seed); - const startingLevel = this.scene.gameMode.getStartingLevel(); - - const party = this.scene.getParty(); - const loadPokemonAssets: Promise[] = []; - for (const starter of starters) { - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); - const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - const starterGender = starter.species.malePercent !== null - ? !starterProps.female ? Gender.MALE : Gender.FEMALE - : Gender.GENDERLESS; - const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); - starterPokemon.setVisible(false); - party.push(starterPokemon); - loadPokemonAssets.push(starterPokemon.loadAssets()); - } - - regenerateModifierPoolThresholds(party, ModifierPoolType.DAILY_STARTER); - const modifiers: Modifier[] = Array(3).fill(null).map(() => modifierTypes.EXP_SHARE().withIdFromFunc(modifierTypes.EXP_SHARE).newModifier()) - .concat(Array(3).fill(null).map(() => modifierTypes.GOLDEN_EXP_CHARM().withIdFromFunc(modifierTypes.GOLDEN_EXP_CHARM).newModifier())) - .concat(getDailyRunStarterModifiers(party)) - .filter((m) => m !== null); - - for (const m of modifiers) { - this.scene.addModifier(m, true, false, false, true); - } - this.scene.updateModifiers(true, true); - - Promise.all(loadPokemonAssets).then(() => { - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - this.scene.gameData.gameStats.dailyRunSessionsPlayed++; - this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; - this.end(); - }); - }; - - // If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date. - if (!Utils.isLocal) { - fetchDailyRunSeed().then(seed => { - if (seed) { - generateDaily(seed); - } else { - throw new Error("Daily run seed is null!"); - } - }).catch(err => { - console.error("Failed to load daily run:\n", err); - }); - } else { - generateDaily(btoa(new Date().toISOString().substring(0, 10))); - } - }); - } - - end(): void { - if (!this.loaded && !this.scene.gameMode.isDaily) { - this.scene.arena.preloadBgm(); - this.scene.gameMode = getGameMode(this.gameMode); - if (this.gameMode === GameModes.CHALLENGE) { - this.scene.pushPhase(new SelectChallengePhase(this.scene)); - } else { - this.scene.pushPhase(new SelectStarterPhase(this.scene)); - } - this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); - } else { - this.scene.playBgm(); - } - - this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); - - if (this.loaded) { - const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; - - this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new SummonPhase(this.scene, 1, true, true)); - } - - if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { - const minPartySize = this.scene.currentBattle.double ? 2 : 1; - if (availablePartyMembers > minPartySize) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); - } - } - } - } - - for (const achv of Object.keys(this.scene.gameData.achvUnlocks)) { - if (vouchers.hasOwnProperty(achv)) { - this.scene.validateVoucher(vouchers[achv]); - } - } - - super.end(); - } -} - -export class UnavailablePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - this.scene.ui.setMode(Mode.UNAVAILABLE, () => { - this.scene.unshiftPhase(new LoginPhase(this.scene, true)); - this.end(); - }); - } -} - -export class ReloadSessionPhase extends Phase { - private systemDataStr: string | null; - - constructor(scene: BattleScene, systemDataStr?: string) { - super(scene); - - this.systemDataStr = systemDataStr ?? null; - } - - start(): void { - this.scene.ui.setMode(Mode.SESSION_RELOAD); - - let delayElapsed = false; - let loaded = false; - - this.scene.time.delayedCall(Utils.fixedInt(1500), () => { - if (loaded) { - this.end(); - } else { - delayElapsed = true; - } - }); - - this.scene.gameData.clearLocalData(); - - (this.systemDataStr ? this.scene.gameData.initSystem(this.systemDataStr) : this.scene.gameData.loadSystem()).then(() => { - if (delayElapsed) { - this.end(); - } else { - loaded = true; - } - }); - } -} - -export class OutdatedPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - this.scene.ui.setMode(Mode.OUTDATED); - } -} - -export class SelectGenderPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - super.start(); - - this.scene.ui.showText(i18next.t("menu:boyOrGirl"), null, () => { - this.scene.ui.setMode(Mode.OPTION_SELECT, { - options: [ - { - label: i18next.t("settings:boy"), - handler: () => { - this.scene.gameData.gender = PlayerGender.MALE; - this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 0); - this.scene.gameData.saveSystem().then(() => this.end()); - return true; - } - }, - { - label: i18next.t("settings:girl"), - handler: () => { - this.scene.gameData.gender = PlayerGender.FEMALE; - this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 1); - this.scene.gameData.saveSystem().then(() => this.end()); - return true; - } - } - ] - }); - }); - } - - end(): void { - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); - } -} - -export class SelectChallengePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.playBgm("menu"); - - this.scene.ui.setMode(Mode.CHALLENGE_SELECT); - } -} - -export class SelectStarterPhase extends Phase { - - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.playBgm("menu"); - - this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { - if (slotId === -1) { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); - return this.end(); - } - this.scene.sessionSlotId = slotId; - this.initBattle(starters); - }); - }); - } - - /** - * Initialize starters before starting the first battle - * @param starters {@linkcode Pokemon} with which to start the first battle - */ - initBattle(starters: Starter[]) { - const party = this.scene.getParty(); - const loadPokemonAssets: Promise[] = []; - starters.forEach((starter: Starter, i: integer) => { - if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { - starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); - } - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); - let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); - if ( - starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && - starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] - ) { - starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; - } - - let starterGender = starter.species.malePercent !== null - ? !starterProps.female ? Gender.MALE : Gender.FEMALE - : Gender.GENDERLESS; - if (Overrides.GENDER_OVERRIDE !== null) { - starterGender = Overrides.GENDER_OVERRIDE; - } - const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); - starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); - if (starter.passive) { - starterPokemon.passive = true; - } - starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); - if (starter.pokerus) { - starterPokemon.pokerus = true; - } - - if (starter.nickname) { - starterPokemon.nickname = starter.nickname; - } - - if (this.scene.gameMode.isSplicedOnly) { - starterPokemon.generateFusionSpecies(true); - } - starterPokemon.setVisible(false); - applyChallenges(this.scene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); - party.push(starterPokemon); - loadPokemonAssets.push(starterPokemon.loadAssets()); - }); - overrideModifiers(this.scene); - overrideHeldItems(this.scene, party[0]); - Promise.all(loadPokemonAssets).then(() => { - SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - if (this.scene.gameMode.isClassic) { - this.scene.gameData.gameStats.classicSessionsPlayed++; - } else { - this.scene.gameData.gameStats.endlessSessionsPlayed++; - } - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; - // Ensures Keldeo (or any future Pokemon that have this type of form change) starts in the correct form - this.scene.getParty().forEach((p: PlayerPokemon) => { - this.scene.triggerPokemonFormChange(p, SpeciesFormChangeMoveLearnedTrigger); - }); - this.end(); - }); - } -} - -export class BattlePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { - const sprites = this.scene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? - const tintSprites = this.scene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? - for (let i = 0; i < sprites.length; i++) { - const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; - [sprites[i], tintSprites[i]].map(sprite => { - if (visible) { - sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16; - } - sprite.setVisible(visible); - sprite.clearTint(); - }); - sprites[i].setVisible(visible); - tintSprites[i].setVisible(visible); - sprites[i].clearTint(); - tintSprites[i].clearTint(); - } - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, - x: "-=16", - y: "+=16", - alpha: 1, - ease: "Sine.easeInOut", - duration: 750 - }); - } - - hideEnemyTrainer(): void { - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, - x: "+=16", - y: "-=16", - alpha: 0, - ease: "Sine.easeInOut", - duration: 750 - }); - } -} - -type PokemonFunc = (pokemon: Pokemon) => void; - -export abstract class FieldPhase extends BattlePhase { - getOrder(): BattlerIndex[] { - const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; - const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; - - // We shuffle the list before sorting so speed ties produce random results - let orderedTargets: Pokemon[] = playerField.concat(enemyField); - // We seed it with the current turn to prevent an inconsistency where it - // was varying based on how long since you last reloaded - this.scene.executeWithSeedOffset(() => { - orderedTargets = Utils.randSeedShuffle(orderedTargets); - }, this.scene.currentBattle.turn, this.scene.waveSeed); - - orderedTargets.sort((a: Pokemon, b: Pokemon) => { - const aSpeed = a?.getBattleStat(Stat.SPD) || 0; - const bSpeed = b?.getBattleStat(Stat.SPD) || 0; - - return bSpeed - aSpeed; - }); - - const speedReversed = new Utils.BooleanHolder(false); - this.scene.arena.applyTags(TrickRoomTag, speedReversed); - - if (speedReversed.value) { - orderedTargets = orderedTargets.reverse(); - } - - return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0)); - } - - executeForAll(func: PokemonFunc): void { - const field = this.scene.getField(true).filter(p => p.summonData); - field.forEach(pokemon => func(pokemon)); - } -} - -export abstract class PokemonPhase extends FieldPhase { - protected battlerIndex: BattlerIndex | integer; - public player: boolean; - public fieldIndex: integer; - - constructor(scene: BattleScene, battlerIndex?: BattlerIndex | integer) { - super(scene); - - if (battlerIndex === undefined) { - battlerIndex = scene.getField().find(p => p?.isActive())!.getBattlerIndex(); // TODO: is the bang correct here? - } - - this.battlerIndex = battlerIndex; - this.player = battlerIndex < 2; - this.fieldIndex = battlerIndex % 2; - } - - getPokemon(): Pokemon { - if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return this.scene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? - } - return this.scene.getField()[this.battlerIndex]!; //TODO: is this bang correct? - } -} - -export abstract class PartyMemberPokemonPhase extends FieldPhase { - protected partyMemberIndex: integer; - protected fieldIndex: integer; - protected player: boolean; - - constructor(scene: BattleScene, partyMemberIndex: integer, player: boolean) { - super(scene); - - this.partyMemberIndex = partyMemberIndex; - this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount() - ? partyMemberIndex - : -1; - this.player = player; - } - - getParty(): Pokemon[] { - return this.player ? this.scene.getParty() : this.scene.getEnemyParty(); - } - - getPokemon(): Pokemon { - return this.getParty()[this.partyMemberIndex]; - } -} - -export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, partyMemberIndex, true); - } - - getPlayerPokemon(): PlayerPokemon { - return super.getPokemon() as PlayerPokemon; - } -} - -export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, partyMemberIndex, false); - } - - getEnemyPokemon(): EnemyPokemon { - return super.getPokemon() as EnemyPokemon; - } -} - -export class EncounterPhase extends BattlePhase { - private loaded: boolean; - - constructor(scene: BattleScene, loaded?: boolean) { - super(scene); - - this.loaded = !!loaded; - } - - start() { - super.start(); - - this.scene.updateGameInfo(); - - this.scene.initSession(); - - this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); - - // Failsafe if players somehow skip floor 200 in classic mode - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } - - const loadEnemyAssets: Promise[] = []; - - const battle = this.scene.currentBattle; - - let totalBst = 0; - - battle.enemyLevels?.forEach((level, e) => { - if (!this.loaded) { - if (battle.battleType === BattleType.TRAINER) { - battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? - } else { - const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); - battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - battle.enemyParty[e].ivs = new Array(6).fill(31); - } - this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { - applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); - }); - } - } - const enemyPokemon = this.scene.getEnemyParty()[e]; - if (e < (battle.double ? 2 : 1)) { - enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]); - enemyPokemon.resetSummonData(); - } - - if (!this.loaded) { - this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER); - } - - if (enemyPokemon.species.speciesId === Species.ETERNATUS) { - if (this.scene.gameMode.isClassic && (battle.battleSpec === BattleSpec.FINAL_BOSS || this.scene.gameMode.isWaveFinal(battle.waveIndex))) { - if (battle.battleSpec !== BattleSpec.FINAL_BOSS) { - enemyPokemon.formIndex = 1; - enemyPokemon.updateScale(); - } - enemyPokemon.setBoss(); - } else if (!(battle.waveIndex % 1000)) { - enemyPokemon.formIndex = 1; - enemyPokemon.updateScale(); - const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; - this.scene.removeModifier(bossMBH!); - bossMBH?.setTransferrableFalse(); - this.scene.addEnemyModifier(bossMBH!); - } - } - - totalBst += enemyPokemon.getSpeciesForm().baseTotal; - - loadEnemyAssets.push(enemyPokemon.loadAssets()); - - console.log(getPokemonNameWithAffix(enemyPokemon), enemyPokemon.species.speciesId, enemyPokemon.stats); - }); - - if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { - this.scene.validateAchv(achvs.SHINY_PARTY); - } - - if (battle.battleType === BattleType.TRAINER) { - loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct? - } else { - // This block only applies for double battles to init the boss segments (idk why it's split up like this) - if (battle.enemyParty.filter(p => p.isBoss()).length > 1) { - for (const enemyPokemon of battle.enemyParty) { - // If the enemy pokemon is a boss and wasn't populated from data source, then set it up - if (enemyPokemon.isBoss() && !enemyPokemon.isPopulatedFromDataSource) { - enemyPokemon.setBoss(true, Math.ceil(enemyPokemon.bossSegments * (enemyPokemon.getSpeciesForm().baseTotal / totalBst))); - enemyPokemon.initBattleInfo(); - } - } - } - } - - Promise.all(loadEnemyAssets).then(() => { - battle.enemyParty.forEach((enemyPokemon, e) => { - if (e < (battle.double ? 2 : 1)) { - if (battle.battleType === BattleType.WILD) { - this.scene.field.add(enemyPokemon); - battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); - const playerPokemon = this.scene.getPlayerPokemon(); - if (playerPokemon?.visible) { - this.scene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); - } - enemyPokemon.tint(0, 0.5); - } else if (battle.battleType === BattleType.TRAINER) { - enemyPokemon.setVisible(false); - this.scene.currentBattle.trainer?.tint(0, 0.5); - } - if (battle.double) { - enemyPokemon.setFieldPosition(e ? FieldPosition.RIGHT : FieldPosition.LEFT); - } - } - }); - - if (!this.loaded) { - regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); - this.scene.generateEnemyModifiers(); - } - - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - if (!this.loaded) { - //@ts-ignore - this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || this.scene.lastSavePlayTime >= 300).then(success => { // TODO: get rid of ts-ignore - this.scene.disableMenu = false; - if (!success) { - return this.scene.reset(true); - } - this.doEncounter(); - }); - } else { - this.doEncounter(); - } - }); - }); - } - - doEncounter() { - this.scene.playBgm(undefined, true); - this.scene.updateModifiers(false); - this.scene.setFieldScale(1); - - /*if (startingWave > 10) { - for (let m = 0; m < Math.min(Math.floor(startingWave / 10), 99); m++) - this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier(), true); - this.scene.updateModifiers(true); - }*/ - - for (const pokemon of this.scene.getParty()) { - if (pokemon) { - pokemon.resetBattleData(); - } - } - - if (!this.loaded) { - this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); - } - - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(), - x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, - duration: 2000, - onComplete: () => { - if (!this.tryOverrideForBattleSpec()) { - this.doEncounterCommon(); - } - } - }); - } - - getEncounterMessage(): string { - const enemyField = this.scene.getEnemyField(); - - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - return i18next.t("battle:bossAppeared", { bossName: getPokemonNameWithAffix(enemyField[0])}); - } - - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - if (this.scene.currentBattle.double) { - return i18next.t("battle:trainerAppearedDouble", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); - - } else { - return i18next.t("battle:trainerAppeared", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); - } - } - - return enemyField.length === 1 - ? i18next.t("battle:singleWildAppeared", { pokemonName: enemyField[0].getNameToRender() }) - : i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].getNameToRender(), pokemonName2: enemyField[1].getNameToRender() }); - } - - doEncounterCommon(showEncounterMessage: boolean = true) { - const enemyField = this.scene.getEnemyField(); - - if (this.scene.currentBattle.battleType === BattleType.WILD) { - enemyField.forEach(enemyPokemon => { - enemyPokemon.untint(100, "Sine.easeOut"); - enemyPokemon.cry(); - enemyPokemon.showInfo(); - if (enemyPokemon.isShiny()) { - this.scene.validateAchv(achvs.SEE_SHINY); - } - }); - this.scene.updateFieldScale(); - if (showEncounterMessage) { - this.scene.ui.showText(this.getEncounterMessage(), null, () => this.end(), 1500); - } else { - this.end(); - } - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - const trainer = this.scene.currentBattle.trainer; - trainer?.untint(100, "Sine.easeOut"); - trainer?.playAnim(); - - const doSummon = () => { - this.scene.currentBattle.started = true; - this.scene.playBgm(undefined); - this.scene.pbTray.showPbTray(this.scene.getParty()); - this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); - const doTrainerSummon = () => { - this.hideEnemyTrainer(); - const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length; - this.scene.unshiftPhase(new SummonPhase(this.scene, 0, false)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.unshiftPhase(new SummonPhase(this.scene, 1, false)); - } - this.end(); - }; - if (showEncounterMessage) { - this.scene.ui.showText(this.getEncounterMessage(), null, doTrainerSummon, 1500, true); - } else { - doTrainerSummon(); - } - }; - - const encounterMessages = this.scene.currentBattle.trainer?.getEncounterMessages(); - - if (!encounterMessages?.length) { - doSummon(); - } else { - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); - message = message!; // tell TS compiler it's defined now - const showDialogueAndSummon = () => { - this.scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { - this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); - }); - }; - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? - } else { - showDialogueAndSummon(); - } - } - } - } - - end() { - const enemyField = this.scene.getEnemyField(); - - enemyField.forEach((enemyPokemon, e) => { - if (enemyPokemon.isShiny()) { - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); - } - }); - - if (this.scene.currentBattle.battleType !== BattleType.TRAINER) { - enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => { - // if there is not a player party, we can't continue - if (!this.scene.getParty()?.length) { - return false; - } - // how many player pokemon are on the field ? - const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length; - // if it's a 2vs1, there will never be a 2nd pokemon on our field even - const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2); - // if it's a double, there should be 2, otherwise 1 - if (this.scene.currentBattle.double) { - return pokemonsOnFieldCount === requiredPokemonsOnField; - } - return pokemonsOnFieldCount === 1; - })); - const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { - enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); - } - } - - if (!this.loaded) { - const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); - - if (!availablePartyMembers[0].isOnField()) { - this.scene.pushPhase(new SummonPhase(this.scene, 0)); - } - - if (this.scene.currentBattle.double) { - if (availablePartyMembers.length > 1) { - this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); - if (!availablePartyMembers[1].isOnField()) { - this.scene.pushPhase(new SummonPhase(this.scene, 1)); - } - } - } else { - if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { - this.scene.pushPhase(new ReturnPhase(this.scene, 1)); - } - this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, false)); - } - - if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { - const minPartySize = this.scene.currentBattle.double ? 2 : 1; - if (availablePartyMembers.length > minPartySize) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); - } - } - } - } - handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end()); - } - - tryOverrideForBattleSpec(): boolean { - switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - const enemy = this.scene.getEnemyPokemon(); - this.scene.ui.showText(this.getEncounterMessage(), null, () => { - const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; - //The two lines below check if English ordinals (1st, 2nd, 3rd, Xth) are used and determine which one to use. - //Otherwise, it defaults to an empty string. - //As of 08-07-24: Spanish and Italian default to the English translations - const ordinalUse = ["en", "es", "it"]; - const currentLanguage = i18next.resolvedLanguage ?? "en"; - const ordinalIndex = (ordinalUse.includes(currentLanguage)) ? ["st", "nd", "rd"][((count + 90) % 100 - 10) % 10 - 1] ?? "th" : ""; - const cycleCount = count.toLocaleString() + ordinalIndex; - const encounterDialogue = i18next.t(`${(this.scene.gameData.gender === PlayerGender.FEMALE) ? "PGF" : "PGM"}battleSpecDialogue:encounter`, {cycleCount: cycleCount}); - this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { - this.doEncounterCommon(false); - }); - }, 1500, true); - return true; - } - - return false; - } -} - -export class NextEncounterPhase extends EncounterPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - } - - doEncounter(): void { - this.scene.playBgm(undefined, true); - - for (const pokemon of this.scene.getParty()) { - if (pokemon) { - pokemon.resetBattleData(); - } - } - - this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType); - this.scene.arenaNextEnemy.setVisible(true); - - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer].flat(), - x: "+=300", - duration: 2000, - onComplete: () => { - this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType); - this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); - this.scene.arenaEnemy.setAlpha(1); - this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); - this.scene.arenaNextEnemy.setVisible(false); - if (this.scene.lastEnemyTrainer) { - this.scene.lastEnemyTrainer.destroy(); - } - - if (!this.tryOverrideForBattleSpec()) { - this.doEncounterCommon(); - } - } - }); - } -} - -export class NewBiomeEncounterPhase extends NextEncounterPhase { - constructor(scene: BattleScene) { - super(scene); - } - - doEncounter(): void { - this.scene.playBgm(undefined, true); - - for (const pokemon of this.scene.getParty()) { - if (pokemon) { - pokemon.resetBattleData(); - } - } - - this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); - - for (const pokemon of this.scene.getParty().filter(p => p.isOnField())) { - applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); - } - - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, enemyField].flat(), - x: "+=300", - duration: 2000, - onComplete: () => { - if (!this.tryOverrideForBattleSpec()) { - this.doEncounterCommon(); - } - } - }); - } -} - -export class PostSummonPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - - if (pokemon.status?.effect === StatusEffect.TOXIC) { - pokemon.status.turnCount = 0; - } - this.scene.arena.applyTags(ArenaTrapTag, pokemon); - applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); - } -} - -export class SelectBiomePhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const currentBiome = this.scene.arena.biomeType; - - const setNextBiome = (nextBiome: Biome) => { - if (this.scene.currentBattle.waveIndex % 10 === 1) { - this.scene.applyModifiers(MoneyInterestModifier, true, this.scene); - this.scene.unshiftPhase(new PartyHealPhase(this.scene, false)); - } - this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); - this.end(); - }; - - if ((this.scene.gameMode.isClassic && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex + 9)) - || (this.scene.gameMode.isDaily && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) - || (this.scene.gameMode.hasShortBiomes && !(this.scene.currentBattle.waveIndex % 50))) { - setNextBiome(Biome.END); - } else if (this.scene.gameMode.hasRandomBiomes) { - setNextBiome(this.generateNextBiome()); - } else if (Array.isArray(biomeLinks[currentBiome])) { - let biomes: Biome[] = []; - this.scene.executeWithSeedOffset(() => { - biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) - .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) - .map(b => !Array.isArray(b) ? b : b[0]); - }, this.scene.currentBattle.waveIndex); - if (biomes.length > 1 && this.scene.findModifier(m => m instanceof MapModifier)) { - let biomeChoices: Biome[] = []; - this.scene.executeWithSeedOffset(() => { - biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) - ? [biomeLinks[currentBiome] as Biome] - : biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) - .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) - .map(b => Array.isArray(b) ? b[0] : b); - }, this.scene.currentBattle.waveIndex); - const biomeSelectItems = biomeChoices.map(b => { - const ret: OptionSelectItem = { - label: getBiomeName(b), - handler: () => { - this.scene.ui.setMode(Mode.MESSAGE); - setNextBiome(b); - return true; - } - }; - return ret; - }); - this.scene.ui.setMode(Mode.OPTION_SELECT, { - options: biomeSelectItems, - delay: 1000 - }); - } else { - setNextBiome(biomes[Utils.randSeedInt(biomes.length)]); - } - } else if (biomeLinks.hasOwnProperty(currentBiome)) { - setNextBiome(biomeLinks[currentBiome] as Biome); - } else { - setNextBiome(this.generateNextBiome()); - } - } - - generateNextBiome(): Biome { - if (!(this.scene.currentBattle.waveIndex % 50)) { - return Biome.END; - } - return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); - } -} - -export class SwitchBiomePhase extends BattlePhase { - private nextBiome: Biome; - - constructor(scene: BattleScene, nextBiome: Biome) { - super(scene); - - this.nextBiome = nextBiome; - } - - start() { - super.start(); - - if (this.nextBiome === undefined) { - return this.end(); - } - - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, this.scene.lastEnemyTrainer], - x: "+=300", - duration: 2000, - onComplete: () => { - this.scene.arenaEnemy.setX(this.scene.arenaEnemy.x - 600); - - this.scene.newArena(this.nextBiome); - - const biomeKey = getBiomeKey(this.nextBiome); - const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBgTransition.setTexture(bgTexture); - this.scene.arenaBgTransition.setAlpha(0); - this.scene.arenaBgTransition.setVisible(true); - this.scene.arenaPlayerTransition.setBiome(this.nextBiome); - this.scene.arenaPlayerTransition.setAlpha(0); - this.scene.arenaPlayerTransition.setVisible(true); - - this.scene.tweens.add({ - targets: [this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition], - duration: 1000, - delay: 1000, - ease: "Sine.easeInOut", - alpha: (target: any) => target === this.scene.arenaPlayer ? 0 : 1, - onComplete: () => { - this.scene.arenaBg.setTexture(bgTexture); - this.scene.arenaPlayer.setBiome(this.nextBiome); - this.scene.arenaPlayer.setAlpha(1); - this.scene.arenaEnemy.setBiome(this.nextBiome); - this.scene.arenaEnemy.setAlpha(1); - this.scene.arenaNextEnemy.setBiome(this.nextBiome); - this.scene.arenaBgTransition.setVisible(false); - this.scene.arenaPlayerTransition.setVisible(false); - if (this.scene.lastEnemyTrainer) { - this.scene.lastEnemyTrainer.destroy(); - } - - this.end(); - } - }); - } - }); - } -} - -export class SummonPhase extends PartyMemberPokemonPhase { - private loaded: boolean; - - constructor(scene: BattleScene, fieldIndex: integer, player: boolean = true, loaded: boolean = false) { - super(scene, fieldIndex, player); - - this.loaded = loaded; - } - - start() { - super.start(); - - this.preSummon(); - } - - /** - * Sends out a Pokemon before the battle begins and shows the appropriate messages - */ - preSummon(): void { - const partyMember = this.getPokemon(); - // If the Pokemon about to be sent out is fainted or illegal under a challenge, switch to the first non-fainted legal Pokemon - if (!partyMember.isAllowedInBattle()) { - console.warn("The Pokemon about to be sent out is fainted or illegal under a challenge. Attempting to resolve..."); - - // First check if they're somehow still in play, if so remove them. - if (partyMember.isOnField()) { - partyMember.leaveField(); - } - - const party = this.getParty(); - - // Find the first non-fainted Pokemon index above the current one - const legalIndex = party.findIndex((p, i) => i > this.partyMemberIndex && p.isAllowedInBattle()); - if (legalIndex === -1) { - console.error("Party Details:\n", party); - console.error("All available Pokemon were fainted or illegal!"); - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - this.end(); - return; - } - - // Swaps the fainted Pokemon and the first non-fainted legal Pokemon in the party - [party[this.partyMemberIndex], party[legalIndex]] = [party[legalIndex], party[this.partyMemberIndex]]; - console.warn("Swapped %s %O with %s %O", getPokemonNameWithAffix(partyMember), partyMember, getPokemonNameWithAffix(party[0]), party[0]); - } - - if (this.player) { - this.scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); - if (this.player) { - this.scene.pbTray.hide(); - } - this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - this.scene.time.delayedCall(562, () => { - this.scene.trainer.setFrame("2"); - this.scene.time.delayedCall(64, () => { - this.scene.trainer.setFrame("3"); - }); - }); - this.scene.tweens.add({ - targets: this.scene.trainer, - x: -36, - duration: 1000, - onComplete: () => this.scene.trainer.setVisible(false) - }); - this.scene.time.delayedCall(750, () => this.summon()); - } else { - const trainerName = this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); - const pokemonName = this.getPokemon().getNameToRender(); - const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); - - this.scene.pbTrayEnemy.hide(); - this.scene.ui.showText(message, null, () => this.summon()); - } - } - - summon(): void { - const pokemon = this.getPokemon(); - - const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball)); - pokeball.setVisible(false); - pokeball.setOrigin(0.5, 0.625); - this.scene.field.add(pokeball); - - if (this.fieldIndex === 1) { - pokemon.setFieldPosition(FieldPosition.RIGHT, 0); - } else { - const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length; - pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); - } - - const fpOffset = pokemon.getFieldPositionOffset(); - - pokeball.setVisible(true); - - this.scene.tweens.add({ - targets: pokeball, - duration: 650, - x: (this.player ? 100 : 236) + fpOffset[0] - }); - - this.scene.tweens.add({ - targets: pokeball, - duration: 150, - ease: "Cubic.easeOut", - y: (this.player ? 70 : 34) + fpOffset[1], - onComplete: () => { - this.scene.tweens.add({ - targets: pokeball, - duration: 500, - ease: "Cubic.easeIn", - angle: 1440, - y: (this.player ? 132 : 86) + fpOffset[1], - onComplete: () => { - this.scene.playSound("pb_rel"); - pokeball.destroy(); - this.scene.add.existing(pokemon); - this.scene.field.add(pokemon); - if (!this.player) { - const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; - if (playerPokemon?.visible) { - this.scene.field.moveBelow(pokemon, playerPokemon); - } - this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); - } - addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.pokeball); - this.scene.updateModifiers(this.player); - this.scene.updateFieldScale(); - pokemon.showInfo(); - pokemon.playAnim(); - pokemon.setVisible(true); - pokemon.getSprite().setVisible(true); - pokemon.setScale(0.5); - pokemon.tint(getPokeballTintColor(pokemon.pokeball)); - pokemon.untint(250, "Sine.easeIn"); - this.scene.updateFieldScale(); - this.scene.tweens.add({ - targets: pokemon, - duration: 250, - ease: "Sine.easeIn", - scale: pokemon.getSpriteScale(), - onComplete: () => { - pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); - pokemon.getSprite().clearTint(); - pokemon.resetSummonData(); - this.scene.time.delayedCall(1000, () => this.end()); - } - }); - } - }); - } - }); - } - - onEnd(): void { - const pokemon = this.getPokemon(); - - if (pokemon.isShiny()) { - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex())); - } - - pokemon.resetTurnData(); - - if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1) { - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - this.queuePostSummon(); - } - } - - queuePostSummon(): void { - this.scene.pushPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); - } - - end() { - this.onEnd(); - - super.end(); - } -} - -export class SwitchSummonPhase extends SummonPhase { - private slotIndex: integer; - private doReturn: boolean; - private batonPass: boolean; - - private lastPokemon: Pokemon; - - /** - * Constructor for creating a new SwitchSummonPhase - * @param scene {@linkcode BattleScene} the scene the phase is associated with - * @param fieldIndex integer representing position on the battle field - * @param slotIndex integer for the index of pokemon (in party of 6) to switch into - * @param doReturn boolean whether to render "comeback" dialogue - * @param batonPass boolean if the switch is from baton pass - * @param player boolean if the switch is from the player - */ - constructor(scene: BattleScene, fieldIndex: integer, slotIndex: integer, doReturn: boolean, batonPass: boolean, player?: boolean) { - super(scene, fieldIndex, player !== undefined ? player : true); - - this.slotIndex = slotIndex; - this.doReturn = doReturn; - this.batonPass = batonPass; - } - - start(): void { - super.start(); - } - - preSummon(): void { - if (!this.player) { - if (this.slotIndex === -1) { - //@ts-ignore - this.slotIndex = this.scene.currentBattle.trainer?.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); // TODO: what would be the default trainer-slot fallback? - } - if (this.slotIndex > -1) { - this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); - this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); - } - } - - if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) { - if (this.player) { - return this.switchAndSummon(); - } else { - this.scene.time.delayedCall(750, () => this.switchAndSummon()); - return; - } - } - - const pokemon = this.getPokemon(); - - if (!this.batonPass) { - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); - } - - this.scene.ui.showText(this.player ? - i18next.t("battle:playerComeBack", { pokemonName: getPokemonNameWithAffix(pokemon) }) : - i18next.t("battle:trainerComeBack", { - trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), - pokemonName: getPokemonNameWithAffix(pokemon) - }) - ); - this.scene.playSound("pb_rel"); - pokemon.hideInfo(); - pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); - this.scene.tweens.add({ - targets: pokemon, - duration: 250, - ease: "Sine.easeIn", - scale: 0.5, - onComplete: () => { - pokemon.leaveField(!this.batonPass, false); - this.scene.time.delayedCall(750, () => this.switchAndSummon()); - } - }); - } - - switchAndSummon() { - const party = this.player ? this.getParty() : this.scene.getEnemyParty(); - const switchedInPokemon = party[this.slotIndex]; - this.lastPokemon = this.getPokemon(); - applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); - if (this.batonPass && switchedInPokemon) { - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id)); - if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier - && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; - if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false); - } - } - } - if (switchedInPokemon) { - party[this.slotIndex] = this.lastPokemon; - party[this.fieldIndex] = switchedInPokemon; - const showTextAndSummon = () => { - this.scene.ui.showText(this.player ? - i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(switchedInPokemon) }) : - i18next.t("battle:trainerGo", { - trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), - pokemonName: this.getPokemon().getNameToRender() - }) - ); - // Ensure improperly persisted summon data (such as tags) is cleared upon switching - if (!this.batonPass) { - switchedInPokemon.resetBattleData(); - switchedInPokemon.resetSummonData(); - } - this.summon(); - }; - if (this.player) { - showTextAndSummon(); - } else { - this.scene.time.delayedCall(1500, () => { - this.hideEnemyTrainer(); - this.scene.pbTrayEnemy.hide(); - showTextAndSummon(); - }); - } - } else { - this.end(); - } - } - - onEnd(): void { - super.onEnd(); - - const pokemon = this.getPokemon(); - - const moveId = this.lastPokemon?.scene.currentBattle.lastMove; - const lastUsedMove = moveId ? allMoves[moveId] : undefined; - - const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; - const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); - - // Compensate for turn spent summoning - // Or compensate for force switch move if switched out pokemon is not fainted - if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) { - pokemon.battleSummonData.turnCount--; - } - - if (this.batonPass && pokemon) { - pokemon.transferSummon(this.lastPokemon); - } - - this.lastPokemon?.resetSummonData(); - - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - } - - queuePostSummon(): void { - this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); - } -} - -export class ReturnPhase extends SwitchSummonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex, -1, true, false); - } - - switchAndSummon(): void { - this.end(); - } - - summon(): void { } - - onEnd(): void { - const pokemon = this.getPokemon(); - - pokemon.resetTurnData(); - pokemon.resetSummonData(); - - this.scene.updateFieldScale(); - - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); - } -} - -export class ShowTrainerPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.trainer.setVisible(true); - - this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); - - this.scene.tweens.add({ - targets: this.scene.trainer, - x: 106, - duration: 1000, - onComplete: () => this.end() - }); - } -} - -export class ToggleDoublePositionPhase extends BattlePhase { - private double: boolean; - - constructor(scene: BattleScene, double: boolean) { - super(scene); - - this.double = double; - } - - start() { - super.start(); - - const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true)); - if (playerPokemon) { - playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { - if (playerPokemon.getFieldIndex() === 1) { - const party = this.scene.getParty(); - party[1] = party[0]; - party[0] = playerPokemon; - } - this.end(); - }); - } else { - this.end(); - } - } -} - -export class CheckSwitchPhase extends BattlePhase { - protected fieldIndex: integer; - protected useName: boolean; - - constructor(scene: BattleScene, fieldIndex: integer, useName: boolean) { - super(scene); - - this.fieldIndex = fieldIndex; - this.useName = useName; - } - - start() { - super.start(); - - const pokemon = this.scene.getPlayerField()[this.fieldIndex]; - - if (this.scene.battleStyle === BattleStyle.SET) { - super.end(); - return; - } - - if (this.scene.field.getAll().indexOf(pokemon) === -1) { - this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); - super.end(); - return; - } - - if (!this.scene.getParty().slice(1).filter(p => p.isActive()).length) { - super.end(); - return; - } - - if (pokemon.getTag(BattlerTagType.FRENZY)) { - super.end(); - return; - } - - this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); - this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); - this.end(); - }, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.end(); - }); - }); - } -} - -export class SummonMissingPhase extends SummonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); - } - - preSummon(): void { - this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); - this.scene.time.delayedCall(250, () => this.summon()); - } -} - -export class LevelCapPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - super.start(); - - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:levelCapUp", { levelCap: this.scene.getMaxExpLevel() }), null, () => this.end(), null, true); - this.executeForAll(pokemon => pokemon.updateInfo(true)); - }); - } -} - -export class TurnInitPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.getPlayerField().forEach(p => { - // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch - if (p.isOnField() && !p.isAllowedInBattle()) { - this.scene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true); - - const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); - - if (!allowedPokemon.length) { - // If there are no longer any legal pokemon in the party, game over. - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) { - // If there is at least one pokemon in the back that is legal to switch in, force a switch. - p.switchOut(false); - } else { - // If there are no pokemon in the back but we're not game overing, just hide the pokemon. - // This should only happen in double battles. - p.leaveField(); - } - if (allowedPokemon.length === 1 && this.scene.currentBattle.double) { - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } - } - }); - - //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); - this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); - - this.scene.getField().forEach((pokemon, i) => { - if (pokemon?.isActive()) { - if (pokemon.isPlayer()) { - this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); - } - - pokemon.resetTurnData(); - - this.scene.pushPhase(pokemon.isPlayer() ? new CommandPhase(this.scene, i) : new EnemyCommandPhase(this.scene, i - BattlerIndex.ENEMY)); - } - }); - - this.scene.pushPhase(new TurnStartPhase(this.scene)); - - this.end(); - } -} - -export class CommandPhase extends FieldPhase { - protected fieldIndex: integer; - - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene); - - this.fieldIndex = fieldIndex; - } - - start() { - super.start(); - - if (this.fieldIndex) { - // If we somehow are attempting to check the right pokemon but there's only one pokemon out - // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching - if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) { - this.fieldIndex = FieldPosition.CENTER; - } else { - const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; - if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand?.command, skip: true }; - } - } - } - - if (this.scene.currentBattle.turnCommands[this.fieldIndex]?.skip) { - return this.end(); - } - - const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; - - const moveQueue = playerPokemon.getMoveQueue(); - - while (moveQueue.length && moveQueue[0] - && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) - || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct? - moveQueue.shift(); - } - - if (moveQueue.length) { - const queuedMove = moveQueue[0]; - if (!queuedMove.move) { - this.handleCommand(Command.FIGHT, -1, false); - } else { - const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move); - if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); - } else { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - } - } else { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - } - - handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { - const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; - const enemyField = this.scene.getEnemyField(); - let success: boolean; - - switch (command) { - case Command.FIGHT: - let useStruggle = false; - if (cursor === -1 || - playerPokemon.trySelectMove(cursor, args[0] as boolean) || - (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? - const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; - if (!moveId) { - turnCommand.targets = [this.fieldIndex]; - } - console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); - if (moveTargets.targets.length > 1 && moveTargets.multiple) { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - if (moveTargets.targets.length <= 1 || moveTargets.multiple) { - turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? - } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { - turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? - } else { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; - success = true; - } else if (cursor < playerPokemon.getMoveset().length) { - const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? - this.scene.ui.setMode(Mode.MESSAGE); - - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = - playerPokemon.summonData.disabledMove === move.moveId ? "battle:moveDisabled" : - move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; - const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - - this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); - }, null, true); - } - break; - case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1); - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); - if (targets.length > 1) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (cursor < 5) { - const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); - if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; - this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; - if (this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - success = true; - } - } - } - break; - case Command.POKEMON: - case Command.RUN: - const isSwitch = command === Command.POKEMON; - if (!isSwitch && this.scene.arena.biomeType === Biome.END) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (!isSwitch && this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; - const trapped = new Utils.BooleanHolder(false); - const batonPass = isSwitch && args[0] as boolean; - const trappedAbMessages: string[] = []; - if (!batonPass) { - enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, true, trappedAbMessages)); - } - if (batonPass || (!trapTag && !trapped.value)) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - success = true; - if (!isSwitch && this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - } else if (trapTag) { - if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) { - success = true; - this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - break; - } - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText( - i18next.t("battle:noEscapePokemon", { - pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", - moveName: trapTag.getMoveName(), - escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") - }), - null, - () => { - this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - }, null, true); - } else if (trapped.value && trappedAbMessages.length > 0) { - if (!isSwitch) { - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText(trappedAbMessages[0], null, () => { - this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } - }, null, true); - } - } - break; - } - - if (success!) { // TODO: is the bang correct? - this.end(); - } - - return success!; // TODO: is the bang correct? - } - - cancel() { - if (this.fieldIndex) { - this.scene.unshiftPhase(new CommandPhase(this.scene, 0)); - this.scene.unshiftPhase(new CommandPhase(this.scene, 1)); - this.end(); - } - } - - checkFightOverride(): boolean { - const pokemon = this.getPokemon(); - - const encoreTag = pokemon.getTag(EncoreTag) as EncoreTag; - - if (!encoreTag) { - return false; - } - - const moveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === encoreTag.moveId); - - if (moveIndex === -1 || !pokemon.getMoveset()[moveIndex]!.isUsable(pokemon)) { // TODO: is this bang correct? - return false; - } - - this.handleCommand(Command.FIGHT, moveIndex, false); - - return true; - } - - getFieldIndex(): integer { - return this.fieldIndex; - } - - getPokemon(): PlayerPokemon { - return this.scene.getPlayerField()[this.fieldIndex]; - } - - end() { - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - } -} - -/** - * Phase for determining an enemy AI's action for the next turn. - * During this phase, the enemy decides whether to switch (if it has a trainer) - * or to use a move from its moveset. - * - * For more information on how the Enemy AI works, see docs/enemy-ai.md - * @see {@linkcode Pokemon.getMatchupScore} - * @see {@linkcode EnemyPokemon.getNextMove} - */ -export class EnemyCommandPhase extends FieldPhase { - protected fieldIndex: integer; - - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene); - - this.fieldIndex = fieldIndex; - } - - start() { - super.start(); - - const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; - - const battle = this.scene.currentBattle; - - const trainer = battle.trainer; - - /** - * If the enemy has a trainer, decide whether or not the enemy should switch - * to another member in its party. - * - * This block compares the active enemy Pokemon's {@linkcode Pokemon.getMatchupScore | matchup score} - * against the active player Pokemon with the enemy party's other non-fainted Pokemon. If a party - * member's matchup score is 3x the active enemy's score (or 2x for "boss" trainers), - * the enemy will switch to that Pokemon. - */ - if (trainer && !enemyPokemon.getMoveQueue().length) { - const opponents = enemyPokemon.getOpponents(); - - const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; - const trapped = new Utils.BooleanHolder(false); - opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, true, [])); - if (!trapTag && !trapped.value) { - const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); - - if (partyMemberScores.length) { - const matchupScores = opponents.map(opp => enemyPokemon.getMatchupScore(opp)); - const matchupScore = matchupScores.reduce((total, score) => total += score, 0) / matchupScores.length; - - const sortedPartyMemberScores = trainer.getSortedPartyMemberMatchupScores(partyMemberScores); - - const switchMultiplier = 1 - (battle.enemySwitchCounter ? Math.pow(0.1, (1 / battle.enemySwitchCounter)) : 0); - - if (sortedPartyMemberScores[0][1] * switchMultiplier >= matchupScore * (trainer.config.isBoss ? 2 : 3)) { - const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores); - - battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = - { command: Command.POKEMON, cursor: index, args: [false] }; - - battle.enemySwitchCounter++; - - return this.end(); - } - } - } - } - - /** Select a move to use (and a target to use it against, if applicable) */ - const nextMove = enemyPokemon.getNextMove(); - - this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = - { command: Command.FIGHT, move: nextMove }; - - this.scene.currentBattle.enemySwitchCounter = Math.max(this.scene.currentBattle.enemySwitchCounter - 1, 0); - - this.end(); - } -} - -export class SelectTargetPhase extends PokemonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); - } - - start() { - super.start(); - - const turnCommand = this.scene.currentBattle.turnCommands[this.fieldIndex]; - const move = turnCommand?.move?.move; - this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => { - this.scene.ui.setMode(Mode.MESSAGE); - if (targets.length < 1) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = null; - this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); - } else { - turnCommand!.targets = targets; //TODO: is the bang correct here? - } - if (turnCommand?.command === Command.BALL && this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; //TODO: is the bang correct here? - } - this.end(); - }); - } -} - -export class TurnStartPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const field = this.scene.getField(); - const order = this.getOrder(); - - const battlerBypassSpeed = {}; - - this.scene.getField(true).filter(p => p.summonData).map(p => { - const bypassSpeed = new Utils.BooleanHolder(false); - const canCheckHeldItems = new Utils.BooleanHolder(true); - applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); - applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems); - if (canCheckHeldItems.value) { - this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); - } - battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; - }); - - const moveOrder = order.slice(0); - - moveOrder.sort((a, b) => { - const aCommand = this.scene.currentBattle.turnCommands[a]; - const bCommand = this.scene.currentBattle.turnCommands[b]; - - if (aCommand?.command !== bCommand?.command) { - if (aCommand?.command === Command.FIGHT) { - return 1; - } else if (bCommand?.command === Command.FIGHT) { - return -1; - } - } else if (aCommand?.command === Command.FIGHT) { - const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here? - const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here? - - const aPriority = new Utils.IntegerHolder(aMove.priority); - const bPriority = new Utils.IntegerHolder(bMove.priority); - - applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? - applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? - - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? - - if (aPriority.value !== bPriority.value) { - const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value); - const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value; - if (bracketDifference === 0 && hasSpeedDifference) { - return battlerBypassSpeed[a].value ? -1 : 1; - } - return aPriority.value < bPriority.value ? 1 : -1; - } - } - - if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { - return battlerBypassSpeed[a].value ? -1 : 1; - } - - const aIndex = order.indexOf(a); - const bIndex = order.indexOf(b); - - return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0; - }); - - let orderIndex = 0; - - for (const o of moveOrder) { - - const pokemon = field[o]; - const turnCommand = this.scene.currentBattle.turnCommands[o]; - - if (turnCommand?.skip) { - continue; - } - - switch (turnCommand?.command) { - case Command.FIGHT: - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { - this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? - } else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? - this.scene.pushPhase(playerPhase); - } - } else { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? - } - break; - case Command.BALL: - this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? - break; - case Command.POKEMON: - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here? - break; - case Command.RUN: - let runningPokemon = pokemon; - if (this.scene.currentBattle.double) { - const playerActivePokemon = field.filter(pokemon => { - if (!!pokemon) { - return pokemon.isPlayer() && pokemon.isActive(); - } else { - return; - } - }); - // if only one pokemon is alive, use that one - if (playerActivePokemon.length > 1) { - // find which active pokemon has faster speed - const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; - // check if either active pokemon has the ability "Run Away" - const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); - runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; - } - } - this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); - break; - } - } - - - this.scene.pushPhase(new WeatherEffectPhase(this.scene)); - - for (const o of order) { - if (field[o].status && field[o].status.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); - } - } - - this.scene.pushPhase(new BerryPhase(this.scene)); - this.scene.pushPhase(new TurnEndPhase(this.scene)); - - /** - * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front - * of the queue and dequeues to start the next phase - * this is important since stuff like SwitchSummon, AttemptRun, AttemptCapture Phases break the "flow" and should take precedence - */ - this.end(); - } -} - -/** The phase after attacks where the pokemon eat berries */ -export class BerryPhase extends FieldPhase { - start() { - super.start(); - - this.executeForAll((pokemon) => { - const hasUsableBerry = !!this.scene.findModifier((m) => { - return m instanceof BerryModifier && m.shouldApply([pokemon]); - }, pokemon.isPlayer()); - - if (hasUsableBerry) { - const cancelled = new Utils.BooleanHolder(false); - pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); - - if (cancelled.value) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - } else { - this.scene.unshiftPhase( - new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) - ); - - for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) { - if (berryModifier.consumed) { - if (!--berryModifier.stackCount) { - this.scene.removeModifier(berryModifier); - } else { - berryModifier.consumed = false; - } - } - this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used - } - - this.scene.updateModifiers(pokemon.isPlayer()); - - applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false)); - } - } - }); - - this.end(); - } -} - -export class TurnEndPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.currentBattle.incrementTurn(this.scene); - this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); - - const handlePokemon = (pokemon: Pokemon) => { - pokemon.lapseTags(BattlerTagLapseType.TURN_END); - - if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) { - this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name }))); - pokemon.summonData.disabledMove = Moves.NONE; - } - - this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); - - if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), - Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); - } - - if (!pokemon.isPlayer()) { - this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); - } - - applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); - - this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); - - this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); - - pokemon.battleSummonData.turnCount++; - }; - - this.executeForAll(handlePokemon); - - this.scene.arena.lapseTags(); - - if (this.scene.arena.weather && !this.scene.arena.weather.lapse()) { - this.scene.arena.trySetWeather(WeatherType.NONE, false); - } - - if (this.scene.arena.terrain && !this.scene.arena.terrain.lapse()) { - this.scene.arena.trySetTerrain(TerrainType.NONE, false); - } - - this.end(); - } -} - -export class BattleEndPhase extends BattlePhase { - start() { - super.start(); - - this.scene.currentBattle.addBattleScore(this.scene); - - this.scene.gameData.gameStats.battles++; - if (this.scene.currentBattle.trainer) { - this.scene.gameData.gameStats.trainersDefeated++; - } - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { - this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; - } - - // Endless graceful end - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex >= 5850) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene, true)); - } - - for (const pokemon of this.scene.getField()) { - if (pokemon) { - pokemon.resetBattleSummonData(); - } - } - - for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) { - applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); - } - - if (this.scene.currentBattle.moneyScattered) { - this.scene.currentBattle.pickUpScatteredMoney(this.scene); - } - - this.scene.clearEnemyHeldItemModifiers(); - - const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; - for (const m of lapsingModifiers) { - const args: any[] = []; - if (m instanceof LapsingPokemonHeldItemModifier) { - args.push(this.scene.getPokemonById(m.pokemonId)); - } - if (!m.lapse(args)) { - this.scene.removeModifier(m); - } - } - - this.scene.updateModifiers().then(() => this.end()); - } -} - -export class NewBattlePhase extends BattlePhase { - start() { - super.start(); - - this.scene.newBattle(); - - this.end(); - } -} - -export class CommonAnimPhase extends PokemonPhase { - private anim: CommonAnim | null; - private targetIndex: integer | undefined; - - constructor(scene: BattleScene, battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex | undefined, anim?: CommonAnim) { - super(scene, battlerIndex); - - this.anim = anim!; // TODO: is this bang correct? - this.targetIndex = targetIndex; - } - - setAnimation(anim: CommonAnim) { - this.anim = anim; - } - - start() { - new CommonBattleAnim(this.anim, this.getPokemon(), this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon()).play(this.scene, () => { - this.end(); - }); - } -} - -export class MoveHeaderPhase extends BattlePhase { - public pokemon: Pokemon; - public move: PokemonMove; - - constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) { - super(scene); - - this.pokemon = pokemon; - this.move = move; - } - - canMove(): boolean { - return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon); - } - - start() { - super.start(); - - if (this.canMove()) { - applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()).then(() => this.end()); - } else { - this.end(); - } - } -} - -export class MovePhase extends BattlePhase { - public pokemon: Pokemon; - public move: PokemonMove; - public targets: BattlerIndex[]; - protected followUp: boolean; - protected ignorePp: boolean; - protected failed: boolean; - protected cancelled: boolean; - - constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { - super(scene); - - this.pokemon = pokemon; - this.targets = targets; - this.move = move; - this.followUp = !!followUp; - this.ignorePp = !!ignorePp; - this.failed = false; - this.cancelled = false; - } - - canMove(): boolean { - return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp) && !!this.targets.length; - } - - /**Signifies the current move should fail but still use PP */ - fail(): void { - this.failed = true; - } - - /**Signifies the current move should cancel and retain PP */ - cancel(): void { - this.cancelled = true; - } - - start() { - super.start(); - - console.log(Moves[this.move.moveId]); - - if (!this.canMove()) { - if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) { - this.scene.queueMessage(`${this.move.getName()} is disabled!`); - } - if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails - this.fail(); - this.showMoveText(); - this.showFailedText(); - } - return this.end(); - } - - if (!this.followUp) { - if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { - this.scene.arena.setIgnoreAbilities(); - } - } else { - this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct? - this.pokemon.turnData.hitCount = 0; // TODO: is `0` correct? - } - - // Move redirection abilities (ie. Storm Drain) only support single target moves - const moveTarget = this.targets.length === 1 - ? new Utils.IntegerHolder(this.targets[0]) - : null; - if (moveTarget) { - const oldTarget = moveTarget.value; - this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); - this.pokemon.getOpponents().forEach(p => { - const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; - if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) { - moveTarget.value = p.getBattlerIndex(); - } - }); - //Check if this move is immune to being redirected, and restore its target to the intended target if it is. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) { - //If an ability prevented this move from being redirected, display its ability pop up. - if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) { - this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); - } - moveTarget.value = oldTarget; - } - this.targets[0] = moveTarget.value; - } - - // Check for counterattack moves to switch target - if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { - if (this.pokemon.turnData.attacksReceived.length) { - const attack = this.pokemon.turnData.attacksReceived[0]; - this.targets[0] = attack.sourceBattlerIndex; - - // account for metal burst and comeuppance hitting remaining targets in double battles - // counterattack will redirect to remaining ally if original attacker faints - if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) { - if (this.scene.getField()[this.targets[0]].hp === 0) { - const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); - //@ts-ignore - this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex(); //TODO: fix ts-ignore - } - } - } - if (this.targets[0] === BattlerIndex.ATTACKER) { - this.fail(); // Marks the move as failed for later in doMove - this.showMoveText(); - this.showFailedText(); - } - } - - const targets = this.scene.getField(true).filter(p => { - if (this.targets.indexOf(p.getBattlerIndex()) > -1) { - return true; - } - return false; - }); - - const doMove = () => { - this.pokemon.turnData.acted = true; // Record that the move was attempted, even if it fails - - this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); - - let ppUsed = 1; - // Filter all opponents to include only those this move is targeting - const targetedOpponents = this.pokemon.getOpponents().filter(o => this.targets.includes(o.getBattlerIndex())); - for (const opponent of targetedOpponents) { - if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) { // If we're already at max PP usage, stop checking - break; - } - if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) { // Accounting for abilities like Pressure - ppUsed++; - } - } - - if (!this.followUp && this.canMove() && !this.cancelled) { - this.pokemon.lapseTags(BattlerTagLapseType.MOVE); - } - - const moveQueue = this.pokemon.getMoveQueue(); - if (this.cancelled || this.failed) { - if (this.failed) { - this.move.usePp(ppUsed); // Only use PP if the move failed - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); - } - - // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock - this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); - - this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. - moveQueue.shift(); // Remove the second turn of charge moves - return this.end(); - } - - this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); - - if (this.move.moveId) { - this.showMoveText(); - } - - // This should only happen when there are no valid targets left on the field - if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) { - this.showFailedText(); - this.cancel(); - - // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock - this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); - - this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. - - moveQueue.shift(); - return this.end(); - } - - if (!moveQueue.length || !moveQueue.shift()?.ignorePP) { // using .shift here clears out two turn moves once they've been used - this.move.usePp(ppUsed); - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); - } - - if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { - this.scene.currentBattle.lastMove = this.move.moveId; - } - - // Assume conditions affecting targets only apply to moves with a single target - let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); - const cancelled = new Utils.BooleanHolder(false); - let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled); - if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) { - success = false; - } else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) { - success = false; - if (failedText === null) { - failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain?.terrainType!); // TODO: is this bang correct? - } - } - - /** - * Trigger pokemon type change before playing the move animation - * Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse, - * regardless of whether the move successfully executes or not. - */ - if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) { - applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); - } - - if (success) { - this.scene.unshiftPhase(this.getEffectPhase()); - } else { - this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); - if (!cancelled.value) { - this.showFailedText(failedText); - } - } - // Checks if Dancer ability is triggered - if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { - // Pokemon with Dancer can be on either side of the battle so we check in both cases - this.scene.getPlayerField().forEach(pokemon => { - applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); - }); - this.scene.getEnemyField().forEach(pokemon => { - applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); - }); - } - this.end(); - }; - - if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { - this.pokemon.status.incrementTurn(); - let activated = false; - let healed = false; - - switch (this.pokemon.status.effect) { - case StatusEffect.PARALYSIS: - if (!this.pokemon.randSeedInt(4)) { - activated = true; - this.cancelled = true; - } - break; - case StatusEffect.SLEEP: - applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); - healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; - activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - this.cancelled = activated; - break; - case StatusEffect.FREEZE: - healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); - activated = !healed; - this.cancelled = activated; - break; - } - - if (activated) { - this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); - doMove(); - } else { - if (healed) { - this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); - this.pokemon.resetStatus(); - this.pokemon.updateInfo(); - } - doMove(); - } - } else { - doMove(); - } - } - - getEffectPhase(): MoveEffectPhase { - return new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move); - } - - showMoveText(): void { - if (this.move.getMove().hasAttr(ChargeAttr)) { - const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; - if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { - this.scene.queueMessage(i18next.t("battle:useMove", { - pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), - moveName: this.move.getName() - }), 500); - return; - } - } - - if (this.pokemon.getTag(BattlerTagType.RECHARGING || BattlerTagType.INTERRUPTED)) { - return; - } - - this.scene.queueMessage(i18next.t("battle:useMove", { - pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), - moveName: this.move.getName() - }), 500); - applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents().find(() => true)!, this.move.getMove()); //TODO: is the bang correct here? - } - - showFailedText(failedText: string | null = null): void { - this.scene.queueMessage(failedText || i18next.t("battle:attackFailed")); - } - - end() { - if (!this.followUp && this.canMove()) { - this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); - } - - super.end(); - } -} - -export class MoveEffectPhase extends PokemonPhase { - public move: PokemonMove; - protected targets: BattlerIndex[]; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { - super(scene, battlerIndex); - this.move = move; - /** - * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies - * with no party members available to switch in, then the right Pokemon takes the index - * of the left Pokemon and gets hit unless this is checked. - */ - if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { - const i = targets.indexOf(battlerIndex); - targets.splice(i, i + 1); - } - this.targets = targets; - } - - start() { - super.start(); - - /** The Pokemon using this phase's invoked move */ - const user = this.getUserPokemon(); - /** All Pokemon targeted by this phase's invoked move */ - const targets = this.getTargets(); - - /** If the user was somehow removed from the field, end this phase */ - if (!user?.isOnField()) { - return super.end(); - } - - /** - * Does an effect from this move override other effects on this turn? - * e.g. Charging moves (Fly, etc.) on their first turn of use. - */ - const overridden = new Utils.BooleanHolder(false); - /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ - const move = this.move.getMove(); - - // Assume single target for override - applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget() ?? null, move, overridden, this.move.virtual).then(() => { - // If other effects were overriden, stop this phase before they can be applied - if (overridden.value) { - return this.end(); - } - - user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); - - /** - * If this phase is for the first hit of the invoked move, - * resolve the move's total hit count. This block combines the - * effects of the move itself, Parental Bond, and Multi-Lens to do so. - */ - if (user.turnData.hitsLeft === undefined) { - const hitCount = new Utils.IntegerHolder(1); - // Assume single target for multi hit - applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount); - // If Parental Bond is applicable, double the hit count - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0)); - // If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user - if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { - this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); - } - // Set the user's relevant turnData fields to reflect the final hit count - user.turnData.hitCount = hitCount.value; - user.turnData.hitsLeft = hitCount.value; - } - - /** - * Log to be entered into the user's move history once the move result is resolved. - * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully - * used in the sense of "Does it have an effect on the user?". - */ - const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; - - /** - * Stores results of hit checks of the invoked move against all targets, organized by battler index. - * @see {@linkcode hitCheck} - */ - const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)])); - const hasActiveTargets = targets.some(t => t.isActive(true)); - /** - * If no targets are left for the move to hit (FAIL), or the invoked move is single-target - * (and not random target) and failed the hit check against its target (MISS), log the move - * as FAILed or MISSed (depending on the conditions above) and end this phase. - */ - if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { - this.stopMultiHit(); - if (hasActiveTargets) { - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getTarget()? getPokemonNameWithAffix(this.getTarget()!) : "" })); - moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, move); - } else { - this.scene.queueMessage(i18next.t("battle:attackFailed")); - moveHistoryEntry.result = MoveResult.FAIL; - } - user.pushMoveHistory(moveHistoryEntry); - return this.end(); - } - - /** All move effect attributes are chained together in this array to be applied asynchronously. */ - const applyAttrs: Promise[] = []; - - // Move animation only needs one target - new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()!).play(this.scene, () => { // TODO: is the bang correct here? - /** Has the move successfully hit a target (for damage) yet? */ - let hasHit: boolean = false; - for (const target of targets) { - /** - * If the move missed a target, stop all future hits against that target - * and move on to the next target (if there is one). - */ - if (!targetHitChecks[target.getBattlerIndex()]) { - this.stopMultiHit(target); - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); - if (moveHistoryEntry.result === MoveResult.PENDING) { - moveHistoryEntry.result = MoveResult.MISS; - } - user.pushMoveHistory(moveHistoryEntry); - applyMoveAttrs(MissEffectAttr, user, null, move); - continue; - } - - /** The {@linkcode ArenaTagSide} to which the target belongs */ - const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ - const hasConditionalProtectApplied = new Utils.BooleanHolder(false); - /** Does the applied conditional protection bypass Protect-ignoring effects? */ - const bypassIgnoreProtect = new Utils.BooleanHolder(false); - // If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects - if (!this.move.getMove().isAllyTarget()) { - this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); - } - - /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ - const isProtected = (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target)) - && (hasConditionalProtectApplied.value || target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))); - - /** Does this phase represent the invoked move's first strike? */ - const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount); - - // Only log the move's result on the first strike - if (firstHit) { - user.pushMoveHistory(moveHistoryEntry); - } - - /** - * Since all fail/miss checks have applied, the move is considered successfully applied. - * It's worth noting that if the move has no effect or is protected against, this assignment - * is overwritten and the move is logged as a FAIL. - */ - moveHistoryEntry.result = MoveResult.SUCCESS; - - /** - * Stores the result of applying the invoked move to the target. - * If the target is protected, the result is always `NO_EFFECT`. - * Otherwise, the hit result is based on type effectiveness, immunities, - * and other factors that may negate the attack or status application. - * - * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated - * (for attack moves) and the target's HP is updated. However, this isn't - * made visible to the user until the resulting {@linkcode DamagePhase} - * is invoked. - */ - const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; - - /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ - const dealsDamage = [ - HitResult.EFFECTIVE, - HitResult.SUPER_EFFECTIVE, - HitResult.NOT_VERY_EFFECTIVE, - HitResult.ONE_HIT_KO - ].includes(hitResult); - - /** Is this target the first one hit by the move on its current strike? */ - const firstTarget = dealsDamage && !hasHit; - if (firstTarget) { - hasHit = true; - } - - /** - * If the move has no effect on the target (i.e. the target is protected or immune), - * change the logged move result to FAIL. - */ - if (hitResult === HitResult.NO_EFFECT) { - moveHistoryEntry.result = MoveResult.FAIL; - } - - /** Does this phase represent the invoked move's last strike? */ - const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); - - /** - * If the user can change forms by using the invoked move, - * it only changes forms after the move's last hit - * (see Relic Song's interaction with Parental Bond when used by Meloetta). - */ - if (lastHit) { - this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); - } - - /** - * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. - * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger - * type requires different conditions to be met with respect to the move's hit result. - */ - applyAttrs.push(new Promise(resolve => { - // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, - user, target, move).then(() => { - // All other effects require the move to not have failed or have been cancelled to trigger - if (hitResult !== HitResult.FAIL) { - /** Are the move's effects tied to the first turn of a charge move? */ - const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move)); - /** - * If the invoked move's effects are meant to trigger during the move's "charge turn," - * ignore all effects after this point. - * Otherwise, apply all self-targeted POST_APPLY effects. - */ - Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY - && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { - // All effects past this point require the move to have hit the target - if (hitResult !== HitResult.NO_EFFECT) { - // Apply all non-self-targeted POST_APPLY effects - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY - && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { - /** - * If the move hit, and the target doesn't have Shield Dust, - * apply the chance to flinch the target gained from King's Rock - */ - if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) { - const flinched = new Utils.BooleanHolder(false); - user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); - if (flinched.value) { - target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); - } - } - // If the move was not protected against, apply all HIT effects - Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT - && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { - // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) - return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { - // If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tags and tokens - target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); - if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { - target.lapseTag(BattlerTagType.SHELL_TRAP); - } - if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { - user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); - } - })).then(() => { - // Apply the user's post-attack ability effects - applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { - /** - * If the invoked move is an attack, apply the user's chance to - * steal an item from the target granted by Grip Claw - */ - if (this.move.getMove() instanceof AttackMove) { - this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); - } - resolve(); - }); - }); - }) - ).then(() => resolve()); - }); - } else { - applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve()); - } - }); - } else { - resolve(); - } - }); - })); - } - // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved - const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ? - applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) : - null; - - if (!!postTarget) { - if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after - applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); - } else { // Otherwise, push a new asynchronous move effect - applyAttrs.push(postTarget); - } - } - - // Wait for all move effects to finish applying, then end this phase - Promise.allSettled(applyAttrs).then(() => this.end()); - }); - }); - } - - end() { - const move = this.move.getMove(); - move.type = move.defaultType; - const user = this.getUserPokemon(); - /** - * If this phase isn't for the invoked move's last strike, - * unshift another MoveEffectPhase for the next strike. - * Otherwise, queue a message indicating the number of times the move has struck - * (if the move has struck more than once), then apply the heal from Shell Bell - * to the user. - */ - if (user) { - if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { - this.scene.unshiftPhase(this.getNewHitPhase()); - } else { - // Queue message for number of hits made by multi-move - // If multi-hit attack only hits once, still want to render a message - const hitsTotal = user.turnData.hitCount! - Math.max(user.turnData.hitsLeft!, 0); // TODO: are those bangs correct? - if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { - // If there are multiple hits, or if there are hits of the multi-hit move left - this.scene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); - } - this.scene.applyModifiers(HitHealModifier, this.player, user); - } - } - - super.end(); - } - - /** - * Resolves whether this phase's invoked move hits or misses the given target - * @param target {@linkcode Pokemon} the Pokemon targeted by the invoked move - * @returns `true` if the move does not miss the target; `false` otherwise - */ - hitCheck(target: Pokemon): boolean { - // Moves targeting the user and entry hazards can't miss - if ([MoveTarget.USER, MoveTarget.ENEMY_SIDE].includes(this.move.getMove().moveTarget)) { - return true; - } - - const user = this.getUserPokemon()!; // TODO: is this bang correct? - - // Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits. - // However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal - // multi-hit move and proceed with all hits - if (user.turnData.hitsLeft < user.turnData.hitCount) { - if (!this.move.getMove().hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { - return true; - } - } - - if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { - return true; - } - - // If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match - if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) { - return true; - } - - if (target.getTag(BattlerTagType.ALWAYS_GET_HIT)) { - return true; - } - - const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); - if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { - return false; - } - - const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here? - - if (moveAccuracy === -1) { - return true; - } - - const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove()); - const rand = user.randSeedInt(100, 1); - - return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct? - } - - /** Returns the {@linkcode Pokemon} using this phase's invoked move */ - getUserPokemon(): Pokemon | undefined { - if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return this.scene.getPokemonById(this.battlerIndex) ?? undefined; - } - return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; - } - - /** Returns an array of all {@linkcode Pokemon} targeted by this phase's invoked move */ - getTargets(): Pokemon[] { - return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); - } - - /** Returns the first target of this phase's invoked move */ - getTarget(): Pokemon | undefined { - return this.getTargets()[0]; - } - - /** - * Removes the given {@linkcode Pokemon} from this phase's target list - * @param target {@linkcode Pokemon} the Pokemon to be removed - */ - removeTarget(target: Pokemon): void { - const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); - if (targetIndex !== -1) { - this.targets.splice(this.targets.findIndex(ind => ind === target.getBattlerIndex()), 1); - } - } - - /** - * Prevents subsequent strikes of this phase's invoked move from occurring - * @param target {@linkcode Pokemon} if defined, only stop subsequent - * strikes against this Pokemon - */ - stopMultiHit(target?: Pokemon): void { - /** If given a specific target, remove the target from subsequent strikes */ - if (target) { - this.removeTarget(target); - } - /** - * If no target specified, or the specified target was the last of this move's - * targets, completely cancel all subsequent strikes. - */ - if (!target || this.targets.length === 0 ) { - this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? - this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? - } - } - - /** Returns a new MoveEffectPhase with the same properties as this phase */ - getNewHitPhase() { - return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); - } -} - -export class MoveEndPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - if (pokemon.isActive(true)) { - pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); - } - - this.scene.arena.setIgnoreAbilities(false); - - this.end(); - } -} - -export class MoveAnimTestPhase extends BattlePhase { - private moveQueue: Moves[]; - - constructor(scene: BattleScene, moveQueue?: Moves[]) { - super(scene); - - this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1); - } - - start() { - const moveQueue = this.moveQueue.slice(0); - this.playMoveAnim(moveQueue, true); - } - - playMoveAnim(moveQueue: Moves[], player: boolean) { - const moveId = player ? moveQueue[0] : moveQueue.shift(); - if (moveId === undefined) { - this.playMoveAnim(this.moveQueue.slice(0), true); - return; - } else if (player) { - console.log(Moves[moveId]); - } - - initMoveAnim(this.scene, moveId).then(() => { - loadMoveAnimAssets(this.scene, [moveId], true) - .then(() => { - new MoveAnim(moveId, player ? this.scene.getPlayerPokemon()! : this.scene.getEnemyPokemon()!, (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon()! : this.scene.getPlayerPokemon()!).getBattlerIndex()).play(this.scene, () => { // TODO: are the bangs correct here? - if (player) { - this.playMoveAnim(moveQueue, false); - } else { - this.playMoveAnim(moveQueue, true); - } - }); - }); - }); - } -} - -export class ShowAbilityPhase extends PokemonPhase { - private passive: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, passive: boolean = false) { - super(scene, battlerIndex); - - this.passive = passive; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - - if (pokemon) { - this.scene.abilityBar.showAbility(pokemon, this.passive); - - if (pokemon?.battleData) { - pokemon.battleData.abilityRevealed = true; - } - } - - this.end(); - } -} - -export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; - -export class StatChangePhase extends PokemonPhase { - private stats: BattleStat[]; - private selfTarget: boolean; - private levels: integer; - private showMessage: boolean; - private ignoreAbilities: boolean; - private canBeCopied: boolean; - private onChange: StatChangeCallback | null; - - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatChangeCallback | null = null) { - super(scene, battlerIndex); - - this.selfTarget = selfTarget; - this.stats = stats; - this.levels = levels; - this.showMessage = showMessage; - this.ignoreAbilities = ignoreAbilities; - this.canBeCopied = canBeCopied; - this.onChange = onChange; - } - - start() { - const pokemon = this.getPokemon(); - - let random = false; - - if (this.stats.length === 1 && this.stats[0] === BattleStat.RAND) { - this.stats[0] = this.getRandomStat(); - random = true; - } - - this.aggregateStatChanges(random); - - if (!pokemon.isActive(true)) { - return this.end(); - } - - const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => { - const cancelled = new Utils.BooleanHolder(false); - - if (!this.selfTarget && this.levels < 0) { - this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); - } - - if (!cancelled.value && !this.selfTarget && this.levels < 0) { - applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); - } - - return !cancelled.value; - }); - - const levels = new Utils.IntegerHolder(this.levels); - - if (!this.ignoreAbilities) { - applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); - } - - const battleStats = this.getPokemon().summonData.battleStats; - const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]); - - this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels); - - const end = () => { - if (this.showMessage) { - const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels); - for (const message of messages) { - this.scene.queueMessage(message); - } - } - - for (const stat of filteredStats) { - pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); - } - - if (levels.value > 0 && this.canBeCopied) { - for (const opponent of pokemon.getOpponents()) { - applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); - } - } - - applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); - - // Look for any other stat change phases; if this is the last one, do White Herb check - const existingPhase = this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex); - if (!(existingPhase instanceof StatChangePhase)) { - // Apply White Herb if needed - const whiteHerb = this.scene.applyModifier(PokemonResetNegativeStatStageModifier, this.player, pokemon) as PokemonResetNegativeStatStageModifier; - // If the White Herb was applied, consume it - if (whiteHerb) { - --whiteHerb.stackCount; - if (whiteHerb.stackCount <= 0) { - this.scene.removeModifier(whiteHerb); - } - this.scene.updateModifiers(this.player); - } - } - - pokemon.updateInfo(); - - handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end()); - }; - - if (relLevels.filter(l => l).length && this.scene.moveAnimations) { - pokemon.enableMask(); - const pokemonMaskSprite = pokemon.maskSprite; - - const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale(); - const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale(); - - // On increase, show the red sprite located at ATK - // On decrease, show the blue sprite located at SPD - const spriteColor = levels.value >= 1 ? BattleStat[BattleStat.ATK].toLowerCase() : BattleStat[BattleStat.SPD].toLowerCase(); - const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); - statSprite.setPipeline(this.scene.fieldSpritePipeline); - statSprite.setAlpha(0); - statSprite.setScale(6); - statSprite.setOrigin(0.5, 1); - - this.scene.playSound(`stat_${levels.value >= 1 ? "up" : "down"}`); - - statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined)); - - this.scene.tweens.add({ - targets: statSprite, - duration: 250, - alpha: 0.8375, - onComplete: () => { - this.scene.tweens.add({ - targets: statSprite, - delay: 1000, - duration: 250, - alpha: 0 - }); - } - }); - - this.scene.tweens.add({ - targets: statSprite, - duration: 1500, - y: `${levels.value >= 1 ? "-" : "+"}=${160 * 6}` - }); - - this.scene.time.delayedCall(1750, () => { - pokemon.disableMask(); - end(); - }); - } else { - end(); - } - } - - getRandomStat(): BattleStat { - const allStats = Utils.getEnumValues(BattleStat); - return this.getPokemon() ? allStats[this.getPokemon()!.randSeedInt(BattleStat.SPD + 1)] : BattleStat.ATK; // TODO: return default ATK on random? idk... - } - - aggregateStatChanges(random: boolean = false): void { - const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s)); - let existingPhase: StatChangePhase; - if (this.stats.length === 1) { - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 - && (p.stats[0] === this.stats[0] || (random && p.stats[0] === BattleStat.RAND)) - && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { - if (existingPhase.stats[0] === BattleStat.RAND) { - existingPhase.stats[0] = this.getRandomStat(); - if (existingPhase.stats[0] !== this.stats[0]) { - continue; - } - } - this.levels += existingPhase.levels; - - if (!this.scene.tryRemovePhase(p => p === existingPhase)) { - break; - } - } - } - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget - && ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva) - && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { - this.stats.push(...existingPhase.stats); - if (!this.scene.tryRemovePhase(p => p === existingPhase)) { - break; - } - } - } - - getStatChangeMessages(stats: BattleStat[], levels: integer, relLevels: integer[]): string[] { - const messages: string[] = []; - - const relLevelStatIndexes = {}; - for (let rl = 0; rl < relLevels.length; rl++) { - const relLevel = relLevels[rl]; - if (!relLevelStatIndexes[relLevel]) { - relLevelStatIndexes[relLevel] = []; - } - relLevelStatIndexes[relLevel].push(rl); - } - - Object.keys(relLevelStatIndexes).forEach(rl => { - const relLevelStats = stats.filter((_, i) => relLevelStatIndexes[rl].includes(i)); - let statsFragment = ""; - - if (relLevelStats.length > 1) { - statsFragment = relLevelStats.length >= 5 - ? i18next.t("battle:stats") - : `${relLevelStats.slice(0, -1).map(s => getBattleStatName(s)).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${getBattleStatName(relLevelStats[relLevelStats.length - 1])}`; - messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); - } else { - statsFragment = getBattleStatName(relLevelStats[0]); - messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); - } - }); - - return messages; - } -} - -export class WeatherEffectPhase extends CommonAnimPhase { - public weather: Weather | null; - - constructor(scene: BattleScene) { - super(scene, undefined, undefined, CommonAnim.SUNNY + ((scene?.arena?.weather?.weatherType || WeatherType.NONE) - 1)); - this.weather = scene?.arena?.weather; - } - - start() { - // Update weather state with any changes that occurred during the turn - this.weather = this.scene?.arena?.weather; - - if (!this.weather) { - this.end(); - return; - } - - this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1)); - - if (this.weather.isDamaging()) { - - const cancelled = new Utils.BooleanHolder(false); - - this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled)); - - if (!cancelled.value) { - const inflictDamage = (pokemon: Pokemon) => { - const cancelled = new Utils.BooleanHolder(false); - - applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather , cancelled); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - - if (cancelled.value) { - return; - } - - const damage = Math.ceil(pokemon.getMaxHp() / 16); - - this.scene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? - pokemon.damageAndUpdate(damage, HitResult.EFFECTIVE, false, false, true); - }; - - this.executeForAll((pokemon: Pokemon) => { - const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; - if (!immune) { - inflictDamage(pokemon); - } - }); - } - } - - this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct? - this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); - - super.start(); - }); - } -} - -export class ObtainStatusEffectPhase extends PokemonPhase { - private statusEffect: StatusEffect | undefined; - private cureTurn: integer | null; - private sourceText: string | null; - private sourcePokemon: Pokemon | null; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string, sourcePokemon?: Pokemon) { - super(scene, battlerIndex); - - this.statusEffect = statusEffect; - this.cureTurn = cureTurn!; // TODO: is this bang correct? - this.sourceText = sourceText!; // TODO: is this bang correct? - this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct? - } - - start() { - const pokemon = this.getPokemon(); - if (!pokemon?.status) { - if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { - if (this.cureTurn) { - pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? - } - pokemon.updateInfo(true); - new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, () => { - this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); - if (pokemon.status?.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); - } - this.end(); - }); - return; - } - } else if (pokemon.status.effect === this.statusEffect) { - this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon))); - } - this.end(); - } -} - -export class PostTurnStatusEffectPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - const pokemon = this.getPokemon(); - if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { - pokemon.status.incrementTurn(); - const cancelled = new Utils.BooleanHolder(false); - applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); - applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); - - if (!cancelled.value) { - this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); - const damage = new Utils.NumberHolder(0); - switch (pokemon.status.effect) { - case StatusEffect.POISON: - damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); - break; - case StatusEffect.TOXIC: - damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); - break; - case StatusEffect.BURN: - damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage); - break; - } - if (damage.value) { - // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... - this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); - pokemon.updateInfo(); - } - new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); - } else { - this.end(); - } - } else { - this.end(); - } - } - - override end() { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - this.scene.initFinalBossPhaseTwo(this.getPokemon()); - } else { - super.end(); - } - } -} - -export class MessagePhase extends Phase { - private text: string; - private callbackDelay: integer | null; - private prompt: boolean | null; - private promptDelay: integer | null; - - constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { - super(scene); - - this.text = text; - this.callbackDelay = callbackDelay!; // TODO: is this bang correct? - this.prompt = prompt!; // TODO: is this bang correct? - this.promptDelay = promptDelay!; // TODO: is this bang correct? - } - - start() { - super.start(); - - if (this.text.indexOf("$") > -1) { - const pageIndex = this.text.indexOf("$"); - this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay)); - this.text = this.text.slice(0, pageIndex).trim(); - } - - this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); - } - - end() { - if (this.scene.abilityBar.shown) { - this.scene.abilityBar.hide(); - } - - super.end(); - } -} - -export class DamagePhase extends PokemonPhase { - private amount: integer; - private damageResult: DamageResult; - private critical: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { - super(scene, battlerIndex); - - this.amount = amount; - this.damageResult = damageResult || HitResult.EFFECTIVE; - this.critical = critical; - } - - start() { - super.start(); - - if (this.damageResult === HitResult.ONE_HIT_KO) { - if (this.scene.moveAnimations) { - this.scene.toggleInvert(true); - } - this.scene.time.delayedCall(Utils.fixedInt(1000), () => { - this.scene.toggleInvert(false); - this.applyDamage(); - }); - return; - } - - this.applyDamage(); - } - - updateAmount(amount: integer): void { - this.amount = amount; - } - - applyDamage() { - switch (this.damageResult) { - case HitResult.EFFECTIVE: - this.scene.playSound("hit"); - break; - case HitResult.SUPER_EFFECTIVE: - case HitResult.ONE_HIT_KO: - this.scene.playSound("hit_strong"); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.playSound("hit_weak"); - break; - } - - if (this.amount) { - this.scene.damageNumberHandler.add(this.getPokemon(), this.amount, this.damageResult, this.critical); - } - - if (this.damageResult !== HitResult.OTHER) { - const flashTimer = this.scene.time.addEvent({ - delay: 100, - repeat: 5, - startAt: 200, - callback: () => { - this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0); - if (!flashTimer.repeatCount) { - this.getPokemon().updateInfo().then(() => this.end()); - } - } - }); - } else { - this.getPokemon().updateInfo().then(() => this.end()); - } - } - - override end() { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - this.scene.initFinalBossPhaseTwo(this.getPokemon()); - } else { - super.end(); - } - } -} - -export class FaintPhase extends PokemonPhase { - private preventEndure: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) { - super(scene, battlerIndex); - - this.preventEndure = preventEndure!; // TODO: is this bang correct? - } - - start() { - super.start(); - - if (!this.preventEndure) { - const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; - - if (instantReviveModifier) { - if (!--instantReviveModifier.stackCount) { - this.scene.removeModifier(instantReviveModifier); - } - this.scene.updateModifiers(this.player); - return this.end(); - } - } - - if (!this.tryOverrideForBattleSpec()) { - this.doFaint(); - } - } - - doFaint(): void { - const pokemon = this.getPokemon(); - - - // Track total times pokemon have been KO'd for supreme overlord/last respects - if (pokemon.isPlayer()) { - this.scene.currentBattle.playerFaints += 1; - } else { - this.scene.currentBattle.enemyFaints += 1; - } - - this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); - - if (pokemon.turnData?.attacksReceived?.length) { - const lastAttack = pokemon.turnData.attacksReceived[0]; - applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? - } - - const alivePlayField = this.scene.getField(true); - alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); - if (pokemon.turnData?.attacksReceived?.length) { - const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); - if (defeatSource?.isOnField()) { - applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); - const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; - const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); - if (pvattrs.length) { - for (const pvattr of pvattrs) { - pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); - } - } - } - } - - if (this.player) { - /** The total number of Pokemon in the player's party that can legally fight */ - const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); - /** The total number of legal player Pokemon that aren't currently on the field */ - const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); - if (!legalPlayerPokemon.length) { - /** If the player doesn't have any legal Pokemon, end the game */ - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { - /** - * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon - * is already on the field, unshift a phase that moves that Pokemon to center position. - */ - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); - } else if (legalPlayerPartyPokemon.length > 0) { - /** - * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, - * push a phase that prompts the player to summon a Pokemon from their party. - */ - this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); - } - } else { - this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; - if (hasReservePartyMember) { - this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false)); - } - } - } - - // in double battles redirect potential moves off fainted pokemon - if (this.scene.currentBattle.double) { - const allyPokemon = pokemon.getAlly(); - this.scene.redirectPokemonMoves(pokemon, allyPokemon); - } - - pokemon.lapseTags(BattlerTagLapseType.FAINT); - this.scene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); - - pokemon.faintCry(() => { - if (pokemon instanceof PlayerPokemon) { - pokemon.addFriendship(-10); - } - pokemon.hideInfo(); - this.scene.playSound("faint"); - this.scene.tweens.add({ - targets: pokemon, - duration: 500, - y: pokemon.y + 150, - ease: "Sine.easeIn", - onComplete: () => { - pokemon.setVisible(false); - pokemon.y -= 150; - pokemon.trySetStatus(StatusEffect.FAINT); - if (pokemon.isPlayer()) { - this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); - } else { - this.scene.addFaintedEnemyScore(pokemon as EnemyPokemon); - this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); - } - this.scene.field.remove(pokemon); - this.end(); - } - }); - }); - } - - tryOverrideForBattleSpec(): boolean { - switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.player) { - const enemy = this.getPokemon(); - if (enemy.formIndex) { - this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); - } else { - // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase - enemy.hp++; - this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); - this.end(); - } - return true; - } - } - - return false; - } -} - -export class VictoryPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - this.scene.gameData.gameStats.pokemonDefeated++; - - const participantIds = this.scene.currentBattle.playerParticipantIds; - const party = this.scene.getParty(); - const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; - const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; - const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; - const nonFaintedPartyMembers = party.filter(p => p.hp); - const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel()); - const partyMemberExp: number[] = []; - - if (participantIds.size) { - let expValue = this.getPokemon().getExpValue(); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - expValue = Math.floor(expValue * 1.5); - } - for (const partyMember of nonFaintedPartyMembers) { - const pId = partyMember.id; - const participated = participantIds.has(pId); - if (participated) { - partyMember.addFriendship(2); - } - if (!expPartyMembers.includes(partyMember)) { - continue; - } - if (!participated && !expShareModifier) { - partyMemberExp.push(0); - continue; - } - let expMultiplier = 0; - if (participated) { - expMultiplier += (1 / participantIds.size); - if (participantIds.size > 1 && multipleParticipantExpBonusModifier) { - expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2; - } - } else if (expShareModifier) { - expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.size; - } - if (partyMember.pokerus) { - expMultiplier *= 1.5; - } - if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) { - expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; - } - const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); - this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); - partyMemberExp.push(Math.floor(pokemonExp.value)); - } - - if (expBalanceModifier) { - let totalLevel = 0; - let totalExp = 0; - expPartyMembers.forEach((expPartyMember, epm) => { - totalExp += partyMemberExp[epm]; - totalLevel += expPartyMember.level; - }); - - const medianLevel = Math.floor(totalLevel / expPartyMembers.length); - - const recipientExpPartyMemberIndexes: number[] = []; - expPartyMembers.forEach((expPartyMember, epm) => { - if (expPartyMember.level <= medianLevel) { - recipientExpPartyMemberIndexes.push(epm); - } - }); - - const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length); - - expPartyMembers.forEach((_partyMember, pm) => { - partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount()); - }); - } - - for (let pm = 0; pm < expPartyMembers.length; pm++) { - const exp = partyMemberExp[pm]; - - if (exp) { - const partyMemberIndex = party.indexOf(expPartyMembers[pm]); - this.scene.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this.scene, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this.scene, partyMemberIndex, exp)); - } - } - } - - if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType ? !p?.isFainted(true) : p.isOnField())) { - this.scene.pushPhase(new BattleEndPhase(this.scene)); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.pushPhase(new TrainerVictoryPhase(this.scene)); - } - if (this.scene.gameMode.isEndless || !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { - this.scene.pushPhase(new EggLapsePhase(this.scene)); - if (this.scene.currentBattle.waveIndex % 10) { - this.scene.pushPhase(new SelectModifierPhase(this.scene)); - } else if (this.scene.gameMode.isDaily) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); - if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); - } - } else { - const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); - } - if (this.scene.currentBattle.waveIndex <= 750 && (this.scene.currentBattle.waveIndex <= 500 || (this.scene.currentBattle.waveIndex % 30) === superExpWave)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, (this.scene.currentBattle.waveIndex % 30) !== superExpWave || this.scene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM)); - } - if (this.scene.currentBattle.waveIndex <= 150 && !(this.scene.currentBattle.waveIndex % 50)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); - } - if (this.scene.gameMode.isEndless && !(this.scene.currentBattle.waveIndex % 50)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, !(this.scene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS)); - this.scene.pushPhase(new AddEnemyBuffModifierPhase(this.scene)); - } - } - this.scene.pushPhase(new NewBattlePhase(this.scene)); - } else { - this.scene.currentBattle.battleType = BattleType.CLEAR; - this.scene.score += this.scene.gameMode.getClearScoreBonus(); - this.scene.updateScoreText(); - this.scene.pushPhase(new GameOverPhase(this.scene, true)); - } - } - - this.end(); - } -} - -export class TrainerVictoryPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - this.scene.disableMenu = true; - - this.scene.playBgm(this.scene.currentBattle.trainer?.config.victoryBgm); - - this.scene.unshiftPhase(new MoneyRewardPhase(this.scene, this.scene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? - - const modifierRewardFuncs = this.scene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? - for (const modifierRewardFunc of modifierRewardFuncs) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc)); - } - - const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? - if (vouchers.hasOwnProperty(TrainerType[trainerType])) { - if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType])); - } - } - - this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { - const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); - message = message!; // tell TS compiler it's defined now - - const showMessage = () => { - const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.ui.showDialogue(message, this.scene.currentBattle.trainer?.getName(), null, originalFunc); - - showMessageOrEnd(); - }; - let showMessageOrEnd = () => this.end(); - if (victoryMessages?.length) { - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { - const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => originalFunc())); - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(this.scene.currentBattle.trainer?.getKey()!, getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); // TODO: is this bang correct? - } else { - showMessage(); - } - } else { - showMessageOrEnd(); - } - }, null, true); - - this.showEnemyTrainer(); - } -} - -export class MoneyRewardPhase extends BattlePhase { - private moneyMultiplier: number; - - constructor(scene: BattleScene, moneyMultiplier: number) { - super(scene); - - this.moneyMultiplier = moneyMultiplier; - } - - start() { - const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); - - this.scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - - if (this.scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { - moneyAmount.value *= 2; - } - - this.scene.addMoney(moneyAmount.value); - - const userLocale = navigator.language || "en-US"; - const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); - const message = i18next.t("battle:moneyWon", { moneyAmount: formattedMoneyAmount }); - - this.scene.ui.showText(message, null, () => this.end(), null, true); - } -} - -export class ModifierRewardPhase extends BattlePhase { - protected modifierType: ModifierType; - - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { - super(scene); - - this.modifierType = getModifierType(modifierTypeFunc); - } - - start() { - super.start(); - - this.doReward().then(() => this.end()); - } - - doReward(): Promise { - return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("item_fanfare"); - this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); - }); - }); - } -} - -export class GameOverModifierRewardPhase extends ModifierRewardPhase { - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { - super(scene, modifierTypeFunc); - } - - doReward(): Promise { - return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.fadeIn(250).then(() => { - this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { - this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); - resolve(); - }, null, true, 1500); - }); - }); - }); - } -} - -export class RibbonModifierRewardPhase extends ModifierRewardPhase { - private species: PokemonSpecies; - - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { - super(scene, modifierTypeFunc); - - this.species = species; - } - - doReward(): Promise { - return new Promise(resolve => { - const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:beatModeFirstTime", { - speciesName: this.species.name, - gameMode: this.scene.gameMode.getName(), - newModifier: newModifier?.type.name - }), null, () => { - resolve(); - }, null, true, 1500); - }); - }); - } -} - -export class GameOverPhase extends BattlePhase { - private victory: boolean; - private firstRibbons: PokemonSpecies[] = []; - - constructor(scene: BattleScene, victory?: boolean) { - super(scene); - - this.victory = !!victory; - } - - start() { - super.start(); - - // Failsafe if players somehow skip floor 200 in classic mode - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { - this.victory = true; - } - - if (this.victory && this.scene.gameMode.isEndless) { - this.scene.ui.showDialogue(i18next.t("PGMmiscDialogue:ending_endless"), i18next.t("PGMmiscDialogue:ending_name"), 0, () => this.handleGameOver()); - } else if (this.victory || !this.scene.enableRetries) { - this.handleGameOver(); - } else { - this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.fadeOut(1250).then(() => { - this.scene.reset(); - this.scene.clearPhaseQueue(); - this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { - this.scene.pushPhase(new EncounterPhase(this.scene, true)); - - const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; - - this.scene.pushPhase(new SummonPhase(this.scene, 0)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new SummonPhase(this.scene, 1)); - } - if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); - } - } - - this.scene.ui.fadeIn(1250); - this.end(); - }); - }); - }, () => this.handleGameOver(), false, 0, 0, 1000); - }); - } - } - - handleGameOver(): void { - const doGameOver = (newClear: boolean) => { - this.scene.disableMenu = true; - this.scene.time.delayedCall(1000, () => { - let firstClear = false; - if (this.victory && newClear) { - if (this.scene.gameMode.isClassic) { - firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); - this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); - this.scene.gameData.gameStats.sessionsWon++; - for (const pokemon of this.scene.getParty()) { - this.awardRibbon(pokemon); - - if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) { - this.awardRibbon(pokemon, true); - } - } - } else if (this.scene.gameMode.isDaily && newClear) { - this.scene.gameData.gameStats.dailyRunSessionsWon++; - } - } - const fadeDuration = this.victory ? 10000 : 5000; - this.scene.fadeOutBgm(fadeDuration, true); - const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); - activeBattlers.map(p => p.hideInfo()); - this.scene.ui.fadeOut(fadeDuration).then(() => { - activeBattlers.map(a => a.setVisible(false)); - this.scene.setFieldScale(1, true); - this.scene.clearPhaseQueue(); - this.scene.ui.clearText(); - - if (this.victory && this.scene.gameMode.isChallenge) { - this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); - } - - const clear = (endCardPhase?: EndCardPhase) => { - if (newClear) { - this.handleUnlocks(); - } - if (this.victory && newClear) { - for (const species of this.firstRibbons) { - this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species)); - } - if (!firstClear) { - this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM)); - } - } - this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); - this.end(); - }; - - if (this.victory && this.scene.gameMode.isClassic) { - const message = miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1]; - - if (!this.scene.ui.shouldSkipDialogue(message)) { - this.scene.ui.fadeIn(500).then(() => { - this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1])).then(() => { - this.scene.ui.showDialogue(message, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { - this.scene.ui.fadeOut(500).then(() => { - this.scene.charSprite.hide().then(() => { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); - clear(endCardPhase); - }); - }); - }); - }); - }); - } else { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); - clear(endCardPhase); - } - } else { - clear(); - } - }); - }); - }; - - /* Added a local check to see if the game is running offline on victory - If Online, execute apiFetch as intended - If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ - if (this.victory) { - if (!Utils.isLocal) { - Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) - .then(response => response.json()) - .then(newClear => doGameOver(newClear)); - } else { - this.scene.gameData.offlineNewClear(this.scene).then(result => { - doGameOver(result); - }); - } - } else { - doGameOver(false); - } - } - - handleUnlocks(): void { - if (this.victory && this.scene.gameMode.isClassic) { - if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); - } - if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.SPLICED_ENDLESS_MODE)); - } - if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE)); - } - if (!this.scene.gameData.unlocks[Unlockables.EVIOLITE] && this.scene.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.EVIOLITE)); - } - } - } - - awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void { - const speciesId = getPokemonSpecies(pokemon.species.speciesId); - const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter); - // first time classic win, award voucher - if (speciesRibbonCount === 1) { - this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); - } - } -} - -export class EndCardPhase extends Phase { - public endCard: Phaser.GameObjects.Image; - public text: Phaser.GameObjects.Text; - - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - super.start(); - - this.scene.ui.getMessageHandler().bg.setVisible(false); - this.scene.ui.getMessageHandler().nameBoxContainer.setVisible(false); - - this.endCard = this.scene.add.image(0, 0, `end_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}`); - this.endCard.setOrigin(0); - this.endCard.setScale(0.5); - this.scene.field.add(this.endCard); - - this.text = addTextObject(this.scene, this.scene.game.canvas.width / 12, (this.scene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); - this.text.setOrigin(0.5); - this.scene.field.add(this.text); - - this.scene.ui.clearText(); - - this.scene.ui.fadeIn(1000).then(() => { - - this.scene.ui.showText("", null, () => { - this.scene.ui.getMessageHandler().bg.setVisible(true); - this.end(); - }, null, true); - }); - } -} - -export class UnlockPhase extends Phase { - private unlockable: Unlockables; - - constructor(scene: BattleScene, unlockable: Unlockables) { - super(scene); - - this.unlockable = unlockable; - } - - start(): void { - this.scene.time.delayedCall(2000, () => { - this.scene.gameData.unlocks[this.unlockable] = true; - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:unlockedSomething", { unlockedThing: getUnlockableName(this.unlockable) }), null, () => { - this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); - this.end(); - }, null, true, 1500); - }); - } -} - -export class PostGameOverPhase extends Phase { - private endCardPhase: EndCardPhase | null; - - constructor(scene: BattleScene, endCardPhase?: EndCardPhase) { - super(scene); - - this.endCardPhase = endCardPhase!; // TODO: is this bang correct? - } - - start() { - super.start(); - - const saveAndReset = () => { - this.scene.gameData.saveAll(this.scene, true, true, true).then(success => { - if (!success) { - return this.scene.reset(true); - } - this.scene.gameData.tryClearSession(this.scene, this.scene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { - if (!success[0]) { - return this.scene.reset(true); - } - this.scene.reset(); - this.scene.unshiftPhase(new TitlePhase(this.scene)); - this.end(); - }); - }); - }; - - if (this.endCardPhase) { - this.scene.ui.fadeOut(500).then(() => { - this.scene.ui.getMessageHandler().bg.setVisible(true); - - this.endCardPhase?.endCard.destroy(); - this.endCardPhase?.text.destroy(); - saveAndReset(); - }); - } else { - saveAndReset(); - } - } -} - -/** - * Opens the party selector UI and transitions into a {@linkcode SwitchSummonPhase} - * for the player (if a switch would be valid for the current battle state). - */ -export class SwitchPhase extends BattlePhase { - protected fieldIndex: integer; - private isModal: boolean; - private doReturn: boolean; - - /** - * Creates a new SwitchPhase - * @param scene {@linkcode BattleScene} Current battle scene - * @param fieldIndex Field index to switch out - * @param isModal Indicates if the switch should be forced (true) or is - * optional (false). - * @param doReturn Indicates if the party member on the field should be - * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. - */ - constructor(scene: BattleScene, fieldIndex: integer, isModal: boolean, doReturn: boolean) { - super(scene); - - this.fieldIndex = fieldIndex; - this.isModal = isModal; - this.doReturn = doReturn; - } - - start() { - super.start(); - - // Skip modal switch if impossible (no remaining party members that aren't in battle) - if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { - return super.end(); - } - - // Skip if the fainted party member has been revived already. doReturn is - // only passed as `false` from FaintPhase (as opposed to other usages such - // as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this - // if the mon should have already been returned but is still alive and well - // on the field. see also; battle.test.ts - if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) { - return super.end(); - } - - // Check if there is any space still in field - if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { - return super.end(); - } - - // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once - const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0; - - this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { - if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); - } - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - }, PartyUiHandler.FilterNonFainted); - } -} - -export class ExpPhase extends PlayerPartyMemberPokemonPhase { - private expValue: number; - - constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { - super(scene, partyMemberIndex); - - this.expValue = expValue; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - const exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); - exp.value = Math.floor(exp.value); - this.scene.ui.showText(i18next.t("battle:expGain", { pokemonName: getPokemonNameWithAffix(pokemon), exp: exp.value }), null, () => { - const lastLevel = pokemon.level; - pokemon.addExp(exp.value); - const newLevel = pokemon.level; - if (newLevel > lastLevel) { - this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); - } - pokemon.updateInfo().then(() => this.end()); - }, null, true); - } -} - -export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { - private expValue: number; - - constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { - super(scene, partyMemberIndex); - - this.expValue = expValue; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - const exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); - exp.value = Math.floor(exp.value); - - const lastLevel = pokemon.level; - pokemon.addExp(exp.value); - const newLevel = pokemon.level; - if (newLevel > lastLevel) { - this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); - } - this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); - pokemon.updateInfo(); - - if (this.scene.expParty === ExpNotification.SKIP) { - this.end(); - } else if (this.scene.expParty === ExpNotification.ONLY_LEVEL_UP) { - if (newLevel > lastLevel) { // this means if we level up - // instead of displaying the exp gain in the small frame, we display the new level - // we use the same method for mode 0 & 1, by giving a parameter saying to display the exp or the level - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { - setTimeout(() => this.end(), 800 / Math.pow(2, this.scene.expGainsSpeed)); - }); - } else { - this.end(); - } - } else if (this.scene.expGainsSpeed < 3) { - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { - setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); - }); - } else { - this.end(); - } - - } -} - -export class HidePartyExpBarPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.partyExpBar.hide().then(() => this.end()); - } -} - -export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { - private lastLevel: integer; - private level: integer; - - constructor(scene: BattleScene, partyMemberIndex: integer, lastLevel: integer, level: integer) { - super(scene, partyMemberIndex); - - this.lastLevel = lastLevel; - this.level = level; - this.scene = scene; - } - - start() { - super.start(); - - if (this.level > this.scene.gameData.gameStats.highestLevel) { - this.scene.gameData.gameStats.highestLevel = this.level; - } - - this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); - - const pokemon = this.getPokemon(); - const prevStats = pokemon.stats.slice(0); - pokemon.calculateStats(); - pokemon.updateInfo(); - if (this.scene.expParty === ExpNotification.DEFAULT) { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.getPokemon()), level: this.level }), null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()), null, true); - } else if (this.scene.expParty === ExpNotification.SKIP) { - this.end(); - } else { - // we still want to display the stats if activated - this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); - } - if (this.lastLevel < 100) { // this feels like an unnecessary optimization - const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); - for (const lm of levelMoves) { - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm[1])); - } - } - if (!pokemon.pauseEvolutions) { - const evolution = pokemon.getEvolution(); - if (evolution) { - this.scene.unshiftPhase(new EvolutionPhase(this.scene, pokemon as PlayerPokemon, evolution, this.lastLevel)); - } - } - } -} - -export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { - private moveId: Moves; - - constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) { - super(scene, partyMemberIndex); - - this.moveId = moveId; - } - - start() { - super.start(); - - const pokemon = this.getPokemon(); - const move = allMoves[this.moveId]; - - const existingMoveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === move.id); - - if (existingMoveIndex > -1) { - return this.end(); - } - - const emptyMoveIndex = pokemon.getMoveset().length < 4 - ? pokemon.getMoveset().length - : pokemon.getMoveset().findIndex(m => m === null); - - const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler - ? Mode.EVOLUTION_SCENE - : Mode.MESSAGE; - - if (emptyMoveIndex > -1) { - pokemon.setMove(emptyMoveIndex, this.moveId); - initMoveAnim(this.scene, this.moveId).then(() => { - loadMoveAnimAssets(this.scene, [this.moveId], true) - .then(() => { - this.scene.ui.setMode(messageMode).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); - this.end(); - }, messageMode === Mode.EVOLUTION_SCENE ? 1000 : null, true); - }); - }); - }); - } else { - this.scene.ui.setMode(messageMode).then(() => { - this.scene.ui.showText(i18next.t("battle:learnMovePrompt", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { - this.scene.ui.showText(i18next.t("battle:learnMoveLimitReached", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - this.scene.ui.showText(i18next.t("battle:learnMoveReplaceQuestion", { moveName: move.name }), null, () => { - const noHandler = () => { - this.scene.ui.setMode(messageMode).then(() => { - this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { - this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { - this.scene.ui.setMode(messageMode); - this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => this.end(), null, true); - }, () => { - this.scene.ui.setMode(messageMode); - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); - this.end(); - }); - }); - }); - }; - this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { - this.scene.ui.setMode(messageMode); - this.scene.ui.showText(i18next.t("battle:learnMoveForgetQuestion"), null, () => { - this.scene.ui.setModeWithoutClear(Mode.SUMMARY, this.getPokemon(), SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { - if (moveIndex === 4) { - noHandler(); - return; - } - this.scene.ui.setMode(messageMode).then(() => { - this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { - this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: pokemon.moveset[moveIndex]!.getName() }), null, () => { // TODO: is the bang correct? - this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { - pokemon.setMove(moveIndex, Moves.NONE); - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); - this.end(); - }, null, true); - }, null, true); - }, null, true); - }); - }); - }, null, true); - }, noHandler); - }); - }, null, true); - }, null, true); - }); - } - } -} - -export class PokemonHealPhase extends CommonAnimPhase { - private hpHealed: integer; - private message: string | null; - private showFullHpMessage: boolean; - private skipAnim: boolean; - private revive: boolean; - private healStatus: boolean; - private preventFullHeal: boolean; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false) { - super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); - - this.hpHealed = hpHealed; - this.message = message; - this.showFullHpMessage = showFullHpMessage; - this.skipAnim = skipAnim; - this.revive = revive; - this.healStatus = healStatus; - this.preventFullHeal = preventFullHeal; - } - - start() { - if (!this.skipAnim && (this.revive || this.getPokemon().hp) && !this.getPokemon().isFullHp()) { - super.start(); - } else { - this.end(); - } - } - - end() { - const pokemon = this.getPokemon(); - - if (!pokemon.isOnField() || (!this.revive && !pokemon.isActive())) { - super.end(); - return; - } - - const hasMessage = !!this.message; - const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0); - let lastStatusEffect = StatusEffect.NONE; - - if (healOrDamage) { - const hpRestoreMultiplier = new Utils.IntegerHolder(1); - if (!this.revive) { - this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); - } - const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); - if (healAmount.value < 0) { - pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL as DamageResult); - healAmount.value = 0; - } - // Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock) - if (this.preventFullHeal && pokemon.hp + healAmount.value >= pokemon.getMaxHp()) { - healAmount.value = (pokemon.getMaxHp() - pokemon.hp) - 1; - } - healAmount.value = pokemon.heal(healAmount.value); - if (healAmount.value) { - this.scene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL); - } - if (pokemon.isPlayer()) { - this.scene.validateAchvs(HealAchv, healAmount); - if (healAmount.value > this.scene.gameData.gameStats.highestHeal) { - this.scene.gameData.gameStats.highestHeal = healAmount.value; - } - } - if (this.healStatus && !this.revive && pokemon.status) { - lastStatusEffect = pokemon.status.effect; - pokemon.resetStatus(); - } - pokemon.updateInfo().then(() => super.end()); - } else if (this.healStatus && !this.revive && pokemon.status) { - lastStatusEffect = pokemon.status.effect; - pokemon.resetStatus(); - pokemon.updateInfo().then(() => super.end()); - } else if (this.showFullHpMessage) { - this.message = i18next.t("battle:hpIsFull", { pokemonName: getPokemonNameWithAffix(pokemon) }); - } - - if (this.message) { - this.scene.queueMessage(this.message); - } - - if (this.healStatus && lastStatusEffect && !hasMessage) { - this.scene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); - } - - if (!healOrDamage && !lastStatusEffect) { - super.end(); - } - } -} - -export class AttemptCapturePhase extends PokemonPhase { - private pokeballType: PokeballType; - private pokeball: Phaser.GameObjects.Sprite; - private originalY: number; - - constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { - super(scene, BattlerIndex.ENEMY + targetIndex); - - this.pokeballType = pokeballType; - } - - start() { - super.start(); - - const pokemon = this.getPokemon() as EnemyPokemon; - - if (!pokemon?.hp) { - return this.end(); - } - - this.scene.pokeballCounts[this.pokeballType]--; - - this.originalY = pokemon.y; - - const _3m = 3 * pokemon.getMaxHp(); - const _2h = 2 * pokemon.hp; - const catchRate = pokemon.species.catchRate; - const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); - const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; - const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); - const y = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x))); - const fpOffset = pokemon.getFieldPositionOffset(); - - const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); - this.pokeball = this.scene.addFieldSprite(16, 80, "pb", pokeballAtlasKey); - this.pokeball.setOrigin(0.5, 0.625); - this.scene.field.add(this.pokeball); - - this.scene.playSound("pb_throw"); - this.scene.time.delayedCall(300, () => { - this.scene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); - }); - - this.scene.tweens.add({ - targets: this.pokeball, - x: { value: 236 + fpOffset[0], ease: "Linear" }, - y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" }, - duration: 500, - onComplete: () => { - this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - this.scene.playSound("pb_rel"); - pokemon.tint(getPokeballTintColor(this.pokeballType)); - - addPokeballOpenParticles(this.scene, this.pokeball.x, this.pokeball.y, this.pokeballType); - - this.scene.tweens.add({ - targets: pokemon, - duration: 500, - ease: "Sine.easeIn", - scale: 0.25, - y: 20, - onComplete: () => { - this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - pokemon.setVisible(false); - this.scene.playSound("pb_catch"); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); - - const doShake = () => { - let shakeCount = 0; - const pbX = this.pokeball.x; - const shakeCounter = this.scene.tweens.addCounter({ - from: 0, - to: 1, - repeat: 4, - yoyo: true, - ease: "Cubic.easeOut", - duration: 250, - repeatDelay: 500, - onUpdate: t => { - if (shakeCount && shakeCount < 4) { - const value = t.getValue(); - const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1; - this.pokeball.setX(pbX + value * 4 * directionMultiplier); - this.pokeball.setAngle(value * 27.5 * directionMultiplier); - } - }, - onRepeat: () => { - if (!pokemon.species.isObtainable()) { - shakeCounter.stop(); - this.failCatch(shakeCount); - } else if (shakeCount++ < 3) { - if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { - this.scene.playSound("pb_move"); - } else { - shakeCounter.stop(); - this.failCatch(shakeCount); - } - } else { - this.scene.playSound("pb_lock"); - addPokeballCaptureStars(this.scene, this.pokeball); - - const pbTint = this.scene.add.sprite(this.pokeball.x, this.pokeball.y, "pb", "pb"); - pbTint.setOrigin(this.pokeball.originX, this.pokeball.originY); - pbTint.setTintFill(0); - pbTint.setAlpha(0); - this.scene.field.add(pbTint); - this.scene.tweens.add({ - targets: pbTint, - alpha: 0.375, - duration: 200, - easing: "Sine.easeOut", - onComplete: () => { - this.scene.tweens.add({ - targets: pbTint, - alpha: 0, - duration: 200, - easing: "Sine.easeIn", - onComplete: () => pbTint.destroy() - }); - } - }); - } - }, - onComplete: () => { - this.catch(); - } - }); - }; - - this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake)); - } - }); - } - }); - } - - failCatch(shakeCount: integer) { - const pokemon = this.getPokemon(); - - this.scene.playSound("pb_rel"); - pokemon.setY(this.originalY); - if (pokemon.status?.effect !== StatusEffect.SLEEP) { - pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); - } - pokemon.tint(getPokeballTintColor(this.pokeballType)); - pokemon.setVisible(true); - pokemon.untint(250, "Sine.easeOut"); - - const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); - this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - - this.scene.tweens.add({ - targets: pokemon, - duration: 250, - ease: "Sine.easeOut", - scale: 1 - }); - - this.scene.currentBattle.lastUsedPokeball = this.pokeballType; - this.removePb(); - this.end(); - } - - catch() { - const pokemon = this.getPokemon() as EnemyPokemon; - - const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); - - if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { - this.scene.validateAchv(achvs.HIDDEN_ABILITY); - } - - if (pokemon.species.subLegendary) { - this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); - } - - if (pokemon.species.legendary) { - this.scene.validateAchv(achvs.CATCH_LEGENDARY); - } - - if (pokemon.species.mythical) { - this.scene.validateAchv(achvs.CATCH_MYTHICAL); - } - - this.scene.pokemonInfoContainer.show(pokemon, true); - - this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); - - this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - const end = () => { - this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); - this.scene.pokemonInfoContainer.hide(); - this.removePb(); - this.end(); - }; - const removePokemon = () => { - this.scene.addFaintedEnemyScore(pokemon); - this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); - pokemon.hp = 0; - pokemon.trySetStatus(StatusEffect.FAINT); - this.scene.clearEnemyHeldItemModifiers(); - this.scene.field.remove(pokemon, true); - }; - const addToParty = () => { - const newPokemon = pokemon.addToParty(this.pokeballType); - const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); - if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { - this.scene.validateAchv(achvs.SHINY_PARTY); - } - Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { - this.scene.updateModifiers(true); - removePokemon(); - if (newPokemon) { - newPokemon.loadAssets().then(end); - } else { - end(); - } - }); - }; - Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { - if (this.scene.getParty().length === 6) { - const promptRelease = () => { - this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { - this.scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); - this.scene.ui.setMode(Mode.CONFIRM, () => { - const newPokemon = this.scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); - this.scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - promptRelease(); - }); - }, false); - }, () => { - this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - if (slotIndex < 6) { - addToParty(); - } else { - promptRelease(); - } - }); - }); - }, () => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { - removePokemon(); - end(); - }); - }, "fullParty"); - }); - }; - promptRelease(); - } else { - addToParty(); - } - }); - }, 0, true); - } - - removePb() { - this.scene.tweens.add({ - targets: this.pokeball, - duration: 250, - delay: 250, - ease: "Sine.easeIn", - alpha: 0, - onComplete: () => this.pokeball.destroy() - }); - } -} - -export class AttemptRunPhase extends PokemonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); - } - - start() { - super.start(); - - const playerPokemon = this.getPokemon(); - const enemyField = this.scene.getEnemyField(); - - const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length; - - const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); - applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); - - if (playerPokemon.randSeedInt(256) < escapeChance.value) { - this.scene.playSound("flee"); - this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); - - this.scene.tweens.add({ - targets: [this.scene.arenaEnemy, enemyField].flat(), - alpha: 0, - duration: 250, - ease: "Sine.easeIn", - onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()) - }); - - this.scene.clearEnemyHeldItemModifiers(); - - enemyField.forEach(enemyPokemon => { - enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); - enemyPokemon.hp = 0; - enemyPokemon.trySetStatus(StatusEffect.FAINT); - }); - - this.scene.pushPhase(new BattleEndPhase(this.scene)); - this.scene.pushPhase(new NewBattlePhase(this.scene)); - } else { - this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); - } - - this.end(); - } -} - -export class SelectModifierPhase extends BattlePhase { - private rerollCount: integer; - private modifierTiers: ModifierTier[]; - - constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[]) { - super(scene); - - this.rerollCount = rerollCount; - this.modifierTiers = modifierTiers!; // TODO: is this bang correct? - } - - start() { - super.start(); - - if (!this.rerollCount) { - this.updateSeed(); - } else { - this.scene.reroll = false; - } - - const party = this.scene.getParty(); - regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); - const modifierCount = new Utils.IntegerHolder(3); - if (this.isPlayer()) { - this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); - } - const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); - - const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { - if (rowCursor < 0 || cursor < 0) { - this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { - this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.ui.revertMode(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); - }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers))); - }); - return false; - } - let modifierType: ModifierType; - let cost: integer; - switch (rowCursor) { - case 0: - switch (cursor) { - case 0: - const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); - if (this.scene.money < rerollCost) { - this.scene.ui.playError(); - return false; - } else { - this.scene.reroll = true; - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= rerollCost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("buy"); - } - break; - case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { - if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.isTransferrable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, PartyUiHandler.FilterItemMaxStacks); - break; - case 2: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - }); - break; - case 3: - this.scene.lockModifierTiers = !this.scene.lockModifierTiers; - const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; - } - return true; - case 1: - if (typeOptions[cursor].type) { - modifierType = typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); - const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; - if (shopOption.type) { - modifierType = shopOption.type; - } - cost = shopOption.cost; - break; - } - - if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? - this.scene.ui.playError(); - return false; - } - - const applyModifier = (modifier: Modifier, playSound: boolean = false) => { - const result = this.scene.addModifier(modifier, false, playSound); - if (cost) { - result.then(success => { - if (success) { - if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= cost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("buy"); - (this.scene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); - } else { - this.scene.ui.playError(); - } - }); - } else { - const doEnd = () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); - }; - if (result instanceof Promise) { - result.then(() => doEnd()); - } else { - doEnd(); - } - } - }; - - if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? - if (modifierType instanceof FusePokemonModifierType) { - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { - if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? - applyModifier(modifier, true); - }); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, modifierType.selectFilter); - } else { - const pokemonModifierType = modifierType as PokemonModifierType; - const isMoveModifier = modifierType instanceof PokemonMoveModifierType; - const isTmModifier = modifierType instanceof TmModifierType; - const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; - const isPpRestoreModifier = (modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType); - const partyUiMode = isMoveModifier ? PartyUiMode.MOVE_MODIFIER - : isTmModifier ? PartyUiMode.TM_MODIFIER - : isRememberMoveModifier ? PartyUiMode.REMEMBER_MOVE_MODIFIER - : PartyUiMode.MODIFIER; - const tmMoveId = isTmModifier - ? (modifierType as TmModifierType).moveId - : undefined; - this.scene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { - if (slotIndex < 6) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { - const modifier = !isMoveModifier - ? !isRememberMoveModifier - ? modifierType.newModifier(party[slotIndex]) - : modifierType.newModifier(party[slotIndex], option as integer) - : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); - applyModifier(modifier!, true); // TODO: is the bang correct? - }); - } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); - } - } else { - applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? - } - - return !cost!;// TODO: is the bang correct? - }; - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); - } - - updateSeed(): void { - this.scene.resetSeed(); - } - - isPlayer(): boolean { - return true; - } - - getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer { - let baseValue = 0; - if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - return baseValue; - } else if (lockRarities) { - const tierValues = [50, 125, 300, 750, 2000]; - for (const opt of typeOptions) { - baseValue += tierValues[opt.type.tier ?? 0]; - } - } else { - baseValue = 250; - } - return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount), Number.MAX_SAFE_INTEGER); - } - - getPoolType(): ModifierPoolType { - return ModifierPoolType.PLAYER; - } - - getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { - return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined); - } - - addModifier(modifier: Modifier): Promise { - return this.scene.addModifier(modifier, false, true); - } -} - -export class EggLapsePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => { - return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1; - }); - - let eggCount: integer = eggsToHatch.length; - - if (eggCount) { - this.scene.queueMessage(i18next.t("battle:eggHatching")); - - for (const egg of eggsToHatch) { - this.scene.unshiftPhase(new EggHatchPhase(this.scene, egg, eggCount)); - if (eggCount > 0) { - eggCount--; - } - } - - } - this.end(); - } -} - -export class AddEnemyBuffModifierPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - const waveIndex = this.scene.currentBattle.waveIndex; - const tier = !(waveIndex % 1000) ? ModifierTier.ULTRA : !(waveIndex % 250) ? ModifierTier.GREAT : ModifierTier.COMMON; - - regenerateModifierPoolThresholds(this.scene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); - - const count = Math.ceil(waveIndex / 250); - for (let i = 0; i < count; i++) { - this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof EnemyPersistentModifier, false), this.scene), true, true); - } - this.scene.updateModifiers(false, true).then(() => this.end()); - } -} - -/** - * Cures the party of all non-volatile status conditions, shows a message - * @param {BattleScene} scene The current scene - * @param {Pokemon} user The user of the move that cures the party - * @param {string} message The message that should be displayed - * @param {Abilities} abilityCondition Pokemon with this ability will not be affected ie. Soundproof - */ -export class PartyStatusCurePhase extends BattlePhase { - private user: Pokemon; - private message: string; - private abilityCondition: Abilities; - - constructor(scene: BattleScene, user: Pokemon, message: string, abilityCondition: Abilities) { - super(scene); - - this.user = user; - this.message = message; - this.abilityCondition = abilityCondition; - } - - start() { - super.start(); - for (const pokemon of this.scene.getParty()) { - if (!pokemon.isOnField() || pokemon === this.user) { - pokemon.resetStatus(false); - pokemon.updateInfo(true); - } else { - if (!pokemon.hasAbility(this.abilityCondition)) { - pokemon.resetStatus(); - pokemon.updateInfo(true); - } else { - // Manually show ability bar, since we're not hooked into the targeting system - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); - } - } - } - if (this.message) { - this.scene.queueMessage(this.message); - } - this.end(); - } -} - -export class PartyHealPhase extends BattlePhase { - private resumeBgm: boolean; - - constructor(scene: BattleScene, resumeBgm: boolean) { - super(scene); - - this.resumeBgm = resumeBgm; - } - - start() { - super.start(); - - const bgmPlaying = this.scene.isBgmPlaying(); - if (bgmPlaying) { - this.scene.fadeOutBgm(1000, false); - } - this.scene.ui.fadeOut(1000).then(() => { - for (const pokemon of this.scene.getParty()) { - pokemon.hp = pokemon.getMaxHp(); - pokemon.resetStatus(); - for (const move of pokemon.moveset) { - move!.ppUsed = 0; // TODO: is this bang correct? - } - pokemon.updateInfo(true); - } - const healSong = this.scene.playSoundWithoutBgm("heal"); - this.scene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { - healSong.destroy(); - if (this.resumeBgm && bgmPlaying) { - this.scene.playBgm(); - } - this.scene.ui.fadeIn(500).then(() => this.end()); - }); - }); - } -} - -export class ShinySparklePhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); - } - - start() { - super.start(); - - this.getPokemon().sparkle(); - this.scene.time.delayedCall(1000, () => this.end()); - } -} - -export class ScanIvsPhase extends PokemonPhase { - private shownIvs: integer; - - constructor(scene: BattleScene, battlerIndex: BattlerIndex, shownIvs: integer) { - super(scene, battlerIndex); - - this.shownIvs = shownIvs; - } - - start() { - super.start(); - - if (!this.shownIvs) { - return this.end(); - } - - const pokemon = this.getPokemon(); - - let enemyIvs: number[] = []; - let statsContainer: Phaser.GameObjects.Sprite[] = []; - let statsContainerLabels: Phaser.GameObjects.Sprite[] = []; - const enemyField = this.scene.getEnemyField(); - const uiTheme = (this.scene as BattleScene).uiTheme; // Assuming uiTheme is accessible - for (let e = 0; e < enemyField.length; e++) { - enemyIvs = enemyField[e].ivs; - const currentIvs = this.scene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists - const ivsToShow = this.scene.ui.getMessageHandler().getTopIvs(enemyIvs, this.shownIvs); - statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; - statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); - for (let s = 0; s < statsContainerLabels.length; s++) { - const ivStat = Stat[statsContainerLabels[s].frame.name]; - if (enemyIvs[ivStat] > currentIvs[ivStat] && ivsToShow.indexOf(Number(ivStat)) >= 0) { - const hexColour = enemyIvs[ivStat] === 31 ? getTextColor(TextStyle.PERFECT_IV, false, uiTheme) : getTextColor(TextStyle.SUMMARY_GREEN, false, uiTheme); - const hexTextColour = Phaser.Display.Color.HexStringToColor(hexColour).color; - statsContainerLabels[s].setTint(hexTextColour); - } - statsContainerLabels[s].setVisible(true); - } - } - - if (!this.scene.hideIvs) { - this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { - this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); - }); - }, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - this.end(); - }); - }); - } else { - this.end(); - } - } -} - -export class TrainerMessageTestPhase extends BattlePhase { - private trainerTypes: TrainerType[]; - - constructor(scene: BattleScene, ...trainerTypes: TrainerType[]) { - super(scene); - - this.trainerTypes = trainerTypes; - } - - start() { - super.start(); - - const testMessages: string[] = []; - - for (const t of Object.keys(trainerConfigs)) { - const type = parseInt(t); - if (this.trainerTypes.length && !this.trainerTypes.find(tt => tt === type as TrainerType)) { - continue; - } - const config = trainerConfigs[type]; - [config.encounterMessages, config.femaleEncounterMessages, config.victoryMessages, config.femaleVictoryMessages, config.defeatMessages, config.femaleDefeatMessages] - .map(messages => { - if (messages?.length) { - testMessages.push(...messages); - } - }); - } - - for (const message of testMessages) { - this.scene.pushPhase(new TestMessagePhase(this.scene, message)); - } - - this.end(); - } -} - -export class TestMessagePhase extends MessagePhase { - constructor(scene: BattleScene, message: string) { - super(scene, message, null, true); - } -} diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts new file mode 100644 index 00000000000..a9936eb765d --- /dev/null +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -0,0 +1,26 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierTier } from "#app/modifier/modifier-tier.js"; +import { regenerateModifierPoolThresholds, ModifierPoolType, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type.js"; +import { EnemyPersistentModifier } from "#app/modifier/modifier.js"; +import { Phase } from "#app/phase.js"; + +export class AddEnemyBuffModifierPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const waveIndex = this.scene.currentBattle.waveIndex; + const tier = !(waveIndex % 1000) ? ModifierTier.ULTRA : !(waveIndex % 250) ? ModifierTier.GREAT : ModifierTier.COMMON; + + regenerateModifierPoolThresholds(this.scene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); + + const count = Math.ceil(waveIndex / 250); + for (let i = 0; i < count; i++) { + this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof EnemyPersistentModifier, false), this.scene), true, true); + } + this.scene.updateModifiers(false, true).then(() => this.end()); + } +} diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts new file mode 100644 index 00000000000..3c165a25157 --- /dev/null +++ b/src/phases/attempt-capture-phase.ts @@ -0,0 +1,288 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { getPokeballCatchMultiplier, getPokeballAtlasKey, getPokeballTintColor, doPokeballBounceAnim } from "#app/data/pokeball.js"; +import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect.js"; +import { PokeballType } from "#app/enums/pokeball.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { addPokeballOpenParticles, addPokeballCaptureStars } from "#app/field/anims.js"; +import { EnemyPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier.js"; +import { achvs } from "#app/system/achv.js"; +import { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler.js"; +import { SummaryUiMode } from "#app/ui/summary-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { PokemonPhase } from "./pokemon-phase"; +import { VictoryPhase } from "./victory-phase"; + +export class AttemptCapturePhase extends PokemonPhase { + private pokeballType: PokeballType; + private pokeball: Phaser.GameObjects.Sprite; + private originalY: number; + + constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { + super(scene, BattlerIndex.ENEMY + targetIndex); + + this.pokeballType = pokeballType; + } + + start() { + super.start(); + + const pokemon = this.getPokemon() as EnemyPokemon; + + if (!pokemon?.hp) { + return this.end(); + } + + this.scene.pokeballCounts[this.pokeballType]--; + + this.originalY = pokemon.y; + + const _3m = 3 * pokemon.getMaxHp(); + const _2h = 2 * pokemon.hp; + const catchRate = pokemon.species.catchRate; + const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); + const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; + const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); + const y = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x))); + const fpOffset = pokemon.getFieldPositionOffset(); + + const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); + this.pokeball = this.scene.addFieldSprite(16, 80, "pb", pokeballAtlasKey); + this.pokeball.setOrigin(0.5, 0.625); + this.scene.field.add(this.pokeball); + + this.scene.playSound("pb_throw"); + this.scene.time.delayedCall(300, () => { + this.scene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); + }); + + this.scene.tweens.add({ + targets: this.pokeball, + x: { value: 236 + fpOffset[0], ease: "Linear" }, + y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" }, + duration: 500, + onComplete: () => { + this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); + this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + this.scene.playSound("pb_rel"); + pokemon.tint(getPokeballTintColor(this.pokeballType)); + + addPokeballOpenParticles(this.scene, this.pokeball.x, this.pokeball.y, this.pokeballType); + + this.scene.tweens.add({ + targets: pokemon, + duration: 500, + ease: "Sine.easeIn", + scale: 0.25, + y: 20, + onComplete: () => { + this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); + pokemon.setVisible(false); + this.scene.playSound("pb_catch"); + this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); + + const doShake = () => { + let shakeCount = 0; + const pbX = this.pokeball.x; + const shakeCounter = this.scene.tweens.addCounter({ + from: 0, + to: 1, + repeat: 4, + yoyo: true, + ease: "Cubic.easeOut", + duration: 250, + repeatDelay: 500, + onUpdate: t => { + if (shakeCount && shakeCount < 4) { + const value = t.getValue(); + const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1; + this.pokeball.setX(pbX + value * 4 * directionMultiplier); + this.pokeball.setAngle(value * 27.5 * directionMultiplier); + } + }, + onRepeat: () => { + if (!pokemon.species.isObtainable()) { + shakeCounter.stop(); + this.failCatch(shakeCount); + } else if (shakeCount++ < 3) { + if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { + this.scene.playSound("pb_move"); + } else { + shakeCounter.stop(); + this.failCatch(shakeCount); + } + } else { + this.scene.playSound("pb_lock"); + addPokeballCaptureStars(this.scene, this.pokeball); + + const pbTint = this.scene.add.sprite(this.pokeball.x, this.pokeball.y, "pb", "pb"); + pbTint.setOrigin(this.pokeball.originX, this.pokeball.originY); + pbTint.setTintFill(0); + pbTint.setAlpha(0); + this.scene.field.add(pbTint); + this.scene.tweens.add({ + targets: pbTint, + alpha: 0.375, + duration: 200, + easing: "Sine.easeOut", + onComplete: () => { + this.scene.tweens.add({ + targets: pbTint, + alpha: 0, + duration: 200, + easing: "Sine.easeIn", + onComplete: () => pbTint.destroy() + }); + } + }); + } + }, + onComplete: () => { + this.catch(); + } + }); + }; + + this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake)); + } + }); + } + }); + } + + failCatch(shakeCount: integer) { + const pokemon = this.getPokemon(); + + this.scene.playSound("pb_rel"); + pokemon.setY(this.originalY); + if (pokemon.status?.effect !== StatusEffect.SLEEP) { + pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); + } + pokemon.tint(getPokeballTintColor(this.pokeballType)); + pokemon.setVisible(true); + pokemon.untint(250, "Sine.easeOut"); + + const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); + this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); + this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + + this.scene.tweens.add({ + targets: pokemon, + duration: 250, + ease: "Sine.easeOut", + scale: 1 + }); + + this.scene.currentBattle.lastUsedPokeball = this.pokeballType; + this.removePb(); + this.end(); + } + + catch() { + const pokemon = this.getPokemon() as EnemyPokemon; + + const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); + + if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { + this.scene.validateAchv(achvs.HIDDEN_ABILITY); + } + + if (pokemon.species.subLegendary) { + this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + } + + if (pokemon.species.legendary) { + this.scene.validateAchv(achvs.CATCH_LEGENDARY); + } + + if (pokemon.species.mythical) { + this.scene.validateAchv(achvs.CATCH_MYTHICAL); + } + + this.scene.pokemonInfoContainer.show(pokemon, true); + + this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + + this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + const end = () => { + this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); + this.scene.pokemonInfoContainer.hide(); + this.removePb(); + this.end(); + }; + const removePokemon = () => { + this.scene.addFaintedEnemyScore(pokemon); + this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); + pokemon.hp = 0; + pokemon.trySetStatus(StatusEffect.FAINT); + this.scene.clearEnemyHeldItemModifiers(); + this.scene.field.remove(pokemon, true); + }; + const addToParty = () => { + const newPokemon = pokemon.addToParty(this.pokeballType); + const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { + this.scene.validateAchv(achvs.SHINY_PARTY); + } + Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { + this.scene.updateModifiers(true); + removePokemon(); + if (newPokemon) { + newPokemon.loadAssets().then(end); + } else { + end(); + } + }); + }; + Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { + if (this.scene.getParty().length === 6) { + const promptRelease = () => { + this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { + this.scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); + this.scene.ui.setMode(Mode.CONFIRM, () => { + const newPokemon = this.scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); + this.scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + promptRelease(); + }); + }, false); + }, () => { + this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + if (slotIndex < 6) { + addToParty(); + } else { + promptRelease(); + } + }); + }); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + removePokemon(); + end(); + }); + }, "fullParty"); + }); + }; + promptRelease(); + } else { + addToParty(); + } + }); + }, 0, true); + } + + removePb() { + this.scene.tweens.add({ + targets: this.pokeball, + duration: 250, + delay: 250, + ease: "Sine.easeIn", + alpha: 0, + onComplete: () => this.pokeball.destroy() + }); + } +} diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts new file mode 100644 index 00000000000..9781ca6d360 --- /dev/null +++ b/src/phases/attempt-run-phase.ts @@ -0,0 +1,56 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability.js"; +import { Stat } from "#app/enums/stat.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import Pokemon from "#app/field/pokemon.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattleEndPhase } from "./battle-end-phase"; +import { NewBattlePhase } from "./new-battle-phase"; +import { PokemonPhase } from "./pokemon-phase"; + +export class AttemptRunPhase extends PokemonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex); + } + + start() { + super.start(); + + const playerPokemon = this.getPokemon(); + const enemyField = this.scene.getEnemyField(); + + const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length; + + const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256); + applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, escapeChance); + + if (playerPokemon.randSeedInt(256) < escapeChance.value) { + this.scene.playSound("flee"); + this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); + + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, enemyField].flat(), + alpha: 0, + duration: 250, + ease: "Sine.easeIn", + onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()) + }); + + this.scene.clearEnemyHeldItemModifiers(); + + enemyField.forEach(enemyPokemon => { + enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); + enemyPokemon.hp = 0; + enemyPokemon.trySetStatus(StatusEffect.FAINT); + }); + + this.scene.pushPhase(new BattleEndPhase(this.scene)); + this.scene.pushPhase(new NewBattlePhase(this.scene)); + } else { + this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); + } + + this.end(); + } +} diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts new file mode 100644 index 00000000000..a9999370cdd --- /dev/null +++ b/src/phases/battle-end-phase.ts @@ -0,0 +1,55 @@ +import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/ability.js"; +import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier.js"; +import { BattlePhase } from "./battle-phase"; +import { GameOverPhase } from "./game-over-phase"; + +export class BattleEndPhase extends BattlePhase { + start() { + super.start(); + + this.scene.currentBattle.addBattleScore(this.scene); + + this.scene.gameData.gameStats.battles++; + if (this.scene.currentBattle.trainer) { + this.scene.gameData.gameStats.trainersDefeated++; + } + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { + this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; + } + + // Endless graceful end + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex >= 5850) { + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene, true)); + } + + for (const pokemon of this.scene.getField()) { + if (pokemon) { + pokemon.resetBattleSummonData(); + } + } + + for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) { + applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); + } + + if (this.scene.currentBattle.moneyScattered) { + this.scene.currentBattle.pickUpScatteredMoney(this.scene); + } + + this.scene.clearEnemyHeldItemModifiers(); + + const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; + for (const m of lapsingModifiers) { + const args: any[] = []; + if (m instanceof LapsingPokemonHeldItemModifier) { + args.push(this.scene.getPokemonById(m.pokemonId)); + } + if (!m.lapse(args)) { + this.scene.removeModifier(m); + } + } + + this.scene.updateModifiers().then(() => this.end()); + } +} diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts new file mode 100644 index 00000000000..3e7e0e28596 --- /dev/null +++ b/src/phases/battle-phase.ts @@ -0,0 +1,47 @@ +import BattleScene from "#app/battle-scene.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { Phase } from "#app/phase.js"; + +export class BattlePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { + const sprites = this.scene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? + const tintSprites = this.scene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? + for (let i = 0; i < sprites.length; i++) { + const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; + [sprites[i], tintSprites[i]].map(sprite => { + if (visible) { + sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16; + } + sprite.setVisible(visible); + sprite.clearTint(); + }); + sprites[i].setVisible(visible); + tintSprites[i].setVisible(visible); + sprites[i].clearTint(); + tintSprites[i].clearTint(); + } + this.scene.tweens.add({ + targets: this.scene.currentBattle.trainer, + x: "-=16", + y: "+=16", + alpha: 1, + ease: "Sine.easeInOut", + duration: 750 + }); + } + + hideEnemyTrainer(): void { + this.scene.tweens.add({ + targets: this.scene.currentBattle.trainer, + x: "+=16", + y: "-=16", + alpha: 0, + ease: "Sine.easeInOut", + duration: 750 + }); + } +} diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts new file mode 100644 index 00000000000..504fb6ec163 --- /dev/null +++ b/src/phases/berry-phase.ts @@ -0,0 +1,52 @@ +import { applyAbAttrs, PreventBerryUseAbAttr, HealFromBerryUseAbAttr } from "#app/data/ability.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { BerryUsedEvent } from "#app/events/battle-scene.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { BerryModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { FieldPhase } from "./field-phase"; +import { CommonAnimPhase } from "./common-anim-phase"; + +/** The phase after attacks where the pokemon eat berries */ +export class BerryPhase extends FieldPhase { + start() { + super.start(); + + this.executeForAll((pokemon) => { + const hasUsableBerry = !!this.scene.findModifier((m) => { + return m instanceof BerryModifier && m.shouldApply([pokemon]); + }, pokemon.isPlayer()); + + if (hasUsableBerry) { + const cancelled = new Utils.BooleanHolder(false); + pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); + + if (cancelled.value) { + pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } else { + this.scene.unshiftPhase( + new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) + ); + + for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon) as BerryModifier[]) { + if (berryModifier.consumed) { + if (!--berryModifier.stackCount) { + this.scene.removeModifier(berryModifier); + } else { + berryModifier.consumed = false; + } + } + this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used + } + + this.scene.updateModifiers(pokemon.isPlayer()); + + applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false)); + } + } + }); + + this.end(); + } +} diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts new file mode 100644 index 00000000000..cd8f2b00c46 --- /dev/null +++ b/src/phases/check-switch-phase.ts @@ -0,0 +1,61 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattleStyle } from "#app/enums/battle-style.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; +import { PostSummonPhase } from "./post-summon-phase"; +import { SummonMissingPhase } from "./summon-missing-phase"; +import { SwitchPhase } from "./switch-phase"; + +export class CheckSwitchPhase extends BattlePhase { + protected fieldIndex: integer; + protected useName: boolean; + + constructor(scene: BattleScene, fieldIndex: integer, useName: boolean) { + super(scene); + + this.fieldIndex = fieldIndex; + this.useName = useName; + } + + start() { + super.start(); + + const pokemon = this.scene.getPlayerField()[this.fieldIndex]; + + if (this.scene.battleStyle === BattleStyle.SET) { + super.end(); + return; + } + + if (this.scene.field.getAll().indexOf(pokemon) === -1) { + this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); + super.end(); + return; + } + + if (!this.scene.getParty().slice(1).filter(p => p.isActive()).length) { + super.end(); + return; + } + + if (pokemon.getTag(BattlerTagType.FRENZY)) { + super.end(); + return; + } + + this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); + this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); + this.end(); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.end(); + }); + }); + } +} diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts new file mode 100644 index 00000000000..5d466e5d3b6 --- /dev/null +++ b/src/phases/command-phase.ts @@ -0,0 +1,288 @@ +import BattleScene from "#app/battle-scene.js"; +import { TurnCommand, BattleType } from "#app/battle.js"; +import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js"; +import { TrappedTag, EncoreTag } from "#app/data/battler-tags.js"; +import { MoveTargetSet, getMoveTargets } from "#app/data/move.js"; +import { speciesStarters } from "#app/data/pokemon-species.js"; +import { Type } from "#app/data/type.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Biome } from "#app/enums/biome.js"; +import { Moves } from "#app/enums/moves.js"; +import { PokeballType } from "#app/enums/pokeball.js"; +import { FieldPosition, PlayerPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { FieldPhase } from "./field-phase"; +import { SelectTargetPhase } from "./select-target-phase"; + +export class CommandPhase extends FieldPhase { + protected fieldIndex: integer; + + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene); + + this.fieldIndex = fieldIndex; + } + + start() { + super.start(); + + if (this.fieldIndex) { + // If we somehow are attempting to check the right pokemon but there's only one pokemon out + // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching + if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) { + this.fieldIndex = FieldPosition.CENTER; + } else { + const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; + if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand?.command, skip: true }; + } + } + } + + if (this.scene.currentBattle.turnCommands[this.fieldIndex]?.skip) { + return this.end(); + } + + const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; + + const moveQueue = playerPokemon.getMoveQueue(); + + while (moveQueue.length && moveQueue[0] + && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) + || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct? + moveQueue.shift(); + } + + if (moveQueue.length) { + const queuedMove = moveQueue[0]; + if (!queuedMove.move) { + this.handleCommand(Command.FIGHT, -1, false); + } else { + const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move); + if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); + } else { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + } + } else { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + } + + handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { + const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; + const enemyField = this.scene.getEnemyField(); + let success: boolean; + + switch (command) { + case Command.FIGHT: + let useStruggle = false; + if (cursor === -1 || + playerPokemon.trySelectMove(cursor, args[0] as boolean) || + (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { + const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; + const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + if (!moveId) { + turnCommand.targets = [this.fieldIndex]; + } + console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); + if (moveTargets.targets.length > 1 && moveTargets.multiple) { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + if (moveTargets.targets.length <= 1 || moveTargets.multiple) { + turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? + } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { + turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? + } else { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + success = true; + } else if (cursor < playerPokemon.getMoveset().length) { + const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? + this.scene.ui.setMode(Mode.MESSAGE); + + // Decides between a Disabled, Not Implemented, or No PP translation message + const errorMessage = + playerPokemon.summonData.disabledMove === move.moveId ? "battle:moveDisabled" : + move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator + + this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + }, null, true); + } + break; + case Command.BALL: + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarters).length - 1); + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); + if (targets.length > 1) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (cursor < 5) { + const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); + if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; + this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; + if (this.fieldIndex) { + this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + success = true; + } + } + } + break; + case Command.POKEMON: + case Command.RUN: + const isSwitch = command === Command.POKEMON; + if (!isSwitch && this.scene.arena.biomeType === Biome.END) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (!isSwitch && this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; + const trapped = new Utils.BooleanHolder(false); + const batonPass = isSwitch && args[0] as boolean; + const trappedAbMessages: string[] = []; + if (!batonPass) { + enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, true, trappedAbMessages)); + } + if (batonPass || (!trapTag && !trapped.value)) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + success = true; + if (!isSwitch && this.fieldIndex) { + this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + } else if (trapTag) { + if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) { + success = true; + this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + break; + } + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText( + i18next.t("battle:noEscapePokemon", { + pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", + moveName: trapTag.getMoveName(), + escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") + }), + null, + () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } else if (trapped.value && trappedAbMessages.length > 0) { + if (!isSwitch) { + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText(trappedAbMessages[0], null, () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } + } + break; + } + + if (success!) { // TODO: is the bang correct? + this.end(); + } + + return success!; // TODO: is the bang correct? + } + + cancel() { + if (this.fieldIndex) { + this.scene.unshiftPhase(new CommandPhase(this.scene, 0)); + this.scene.unshiftPhase(new CommandPhase(this.scene, 1)); + this.end(); + } + } + + checkFightOverride(): boolean { + const pokemon = this.getPokemon(); + + const encoreTag = pokemon.getTag(EncoreTag) as EncoreTag; + + if (!encoreTag) { + return false; + } + + const moveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === encoreTag.moveId); + + if (moveIndex === -1 || !pokemon.getMoveset()[moveIndex]!.isUsable(pokemon)) { // TODO: is this bang correct? + return false; + } + + this.handleCommand(Command.FIGHT, moveIndex, false); + + return true; + } + + getFieldIndex(): integer { + return this.fieldIndex; + } + + getPokemon(): PlayerPokemon { + return this.scene.getPlayerField()[this.fieldIndex]; + } + + end() { + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + } +} diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts new file mode 100644 index 00000000000..d3663abe3b6 --- /dev/null +++ b/src/phases/common-anim-phase.ts @@ -0,0 +1,26 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class CommonAnimPhase extends PokemonPhase { + private anim: CommonAnim | null; + private targetIndex: integer | undefined; + + constructor(scene: BattleScene, battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex | undefined, anim?: CommonAnim) { + super(scene, battlerIndex); + + this.anim = anim!; // TODO: is this bang correct? + this.targetIndex = targetIndex; + } + + setAnimation(anim: CommonAnim) { + this.anim = anim; + } + + start() { + new CommonBattleAnim(this.anim, this.getPokemon(), this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon()).play(this.scene, () => { + this.end(); + }); + } +} diff --git a/src/phases/damage-phase.ts b/src/phases/damage-phase.ts new file mode 100644 index 00000000000..9f63ce35cf2 --- /dev/null +++ b/src/phases/damage-phase.ts @@ -0,0 +1,84 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { DamageResult, HitResult } from "#app/field/pokemon.js"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class DamagePhase extends PokemonPhase { + private amount: integer; + private damageResult: DamageResult; + private critical: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { + super(scene, battlerIndex); + + this.amount = amount; + this.damageResult = damageResult || HitResult.EFFECTIVE; + this.critical = critical; + } + + start() { + super.start(); + + if (this.damageResult === HitResult.ONE_HIT_KO) { + if (this.scene.moveAnimations) { + this.scene.toggleInvert(true); + } + this.scene.time.delayedCall(Utils.fixedInt(1000), () => { + this.scene.toggleInvert(false); + this.applyDamage(); + }); + return; + } + + this.applyDamage(); + } + + updateAmount(amount: integer): void { + this.amount = amount; + } + + applyDamage() { + switch (this.damageResult) { + case HitResult.EFFECTIVE: + this.scene.playSound("hit"); + break; + case HitResult.SUPER_EFFECTIVE: + case HitResult.ONE_HIT_KO: + this.scene.playSound("hit_strong"); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.playSound("hit_weak"); + break; + } + + if (this.amount) { + this.scene.damageNumberHandler.add(this.getPokemon(), this.amount, this.damageResult, this.critical); + } + + if (this.damageResult !== HitResult.OTHER) { + const flashTimer = this.scene.time.addEvent({ + delay: 100, + repeat: 5, + startAt: 200, + callback: () => { + this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0); + if (!flashTimer.repeatCount) { + this.getPokemon().updateInfo().then(() => this.end()); + } + } + }); + } else { + this.getPokemon().updateInfo().then(() => this.end()); + } + } + + override end() { + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + this.scene.initFinalBossPhaseTwo(this.getPokemon()); + } else { + super.end(); + } + } +} diff --git a/src/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts similarity index 95% rename from src/egg-hatch-phase.ts rename to src/phases/egg-hatch-phase.ts index 73c88cbde37..6f3f0b37905 100644 --- a/src/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -1,18 +1,17 @@ -import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; +import BattleScene, { AnySound } from "#app/battle-scene.js"; +import { Egg, EGG_SEED } from "#app/data/egg.js"; +import { EggCountChangedEvent } from "#app/events/egg.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { Phase } from "#app/phase.js"; +import { achvs } from "#app/system/achv.js"; +import EggCounterContainer from "#app/ui/egg-counter-container.js"; +import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler.js"; +import PokemonInfoContainer from "#app/ui/pokemon-info-container.js"; +import { Mode } from "#app/ui/ui.js"; import i18next from "i18next"; -import { Phase } from "./phase"; -import BattleScene, { AnySound } from "./battle-scene"; -import * as Utils from "./utils"; -import { Mode } from "./ui/ui"; -import { EGG_SEED, Egg } from "./data/egg"; -import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler"; -import { PlayerPokemon } from "./field/pokemon"; -import { achvs } from "./system/achv"; -import PokemonInfoContainer from "./ui/pokemon-info-container"; -import EggCounterContainer from "./ui/egg-counter-container"; -import { EggCountChangedEvent } from "./events/egg"; -import { getPokemonNameWithAffix } from "./messages"; - +import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; +import * as Utils from "#app/utils.js"; /** * Class that represents egg hatching */ diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts new file mode 100644 index 00000000000..50d7106f229 --- /dev/null +++ b/src/phases/egg-lapse-phase.ts @@ -0,0 +1,35 @@ +import BattleScene from "#app/battle-scene.js"; +import { Egg } from "#app/data/egg.js"; +import { Phase } from "#app/phase.js"; +import i18next from "i18next"; +import Overrides from "#app/overrides"; +import { EggHatchPhase } from "./egg-hatch-phase"; + +export class EggLapsePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => { + return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1; + }); + + let eggCount: integer = eggsToHatch.length; + + if (eggCount) { + this.scene.queueMessage(i18next.t("battle:eggHatching")); + + for (const egg of eggsToHatch) { + this.scene.unshiftPhase(new EggHatchPhase(this.scene, egg, eggCount)); + if (eggCount > 0) { + eggCount--; + } + } + + } + this.end(); + } +} diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts new file mode 100644 index 00000000000..739bb1d93f1 --- /dev/null +++ b/src/phases/encounter-phase.ts @@ -0,0 +1,379 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattleType, BattlerIndex } from "#app/battle.js"; +import { applyAbAttrs, SyncEncounterNatureAbAttr } from "#app/data/ability.js"; +import { getCharVariantFromDialogue } from "#app/data/dialogue.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { getRandomWeatherType } from "#app/data/weather.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { Species } from "#app/enums/species.js"; +import { EncounterPhaseEvent } from "#app/events/battle-scene.js"; +import Pokemon, { FieldPosition } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { regenerateModifierPoolThresholds, ModifierPoolType } from "#app/modifier/modifier-type.js"; +import { IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier.js"; +import { achvs } from "#app/system/achv.js"; +import { handleTutorial, Tutorial } from "#app/tutorial.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; +import * as Utils from "#app/utils.js"; +import { CheckSwitchPhase } from "./check-switch-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { PostSummonPhase } from "./post-summon-phase"; +import { ReturnPhase } from "./return-phase"; +import { ScanIvsPhase } from "./scan-ivs-phase"; +import { ShinySparklePhase } from "./shiny-sparkle-phase"; +import { SummonPhase } from "./summon-phase"; +import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; + +export class EncounterPhase extends BattlePhase { + private loaded: boolean; + + constructor(scene: BattleScene, loaded?: boolean) { + super(scene); + + this.loaded = !!loaded; + } + + start() { + super.start(); + + this.scene.updateGameInfo(); + + this.scene.initSession(); + + this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); + + // Failsafe if players somehow skip floor 200 in classic mode + if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } + + const loadEnemyAssets: Promise[] = []; + + const battle = this.scene.currentBattle; + + let totalBst = 0; + + battle.enemyLevels?.forEach((level, e) => { + if (!this.loaded) { + if (battle.battleType === BattleType.TRAINER) { + battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? + } else { + const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); + battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + battle.enemyParty[e].ivs = new Array(6).fill(31); + } + this.scene.getParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { + applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, battle.enemyParty[e]); + }); + } + } + const enemyPokemon = this.scene.getEnemyParty()[e]; + if (e < (battle.double ? 2 : 1)) { + enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]); + enemyPokemon.resetSummonData(); + } + + if (!this.loaded) { + this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER); + } + + if (enemyPokemon.species.speciesId === Species.ETERNATUS) { + if (this.scene.gameMode.isClassic && (battle.battleSpec === BattleSpec.FINAL_BOSS || this.scene.gameMode.isWaveFinal(battle.waveIndex))) { + if (battle.battleSpec !== BattleSpec.FINAL_BOSS) { + enemyPokemon.formIndex = 1; + enemyPokemon.updateScale(); + } + enemyPokemon.setBoss(); + } else if (!(battle.waveIndex % 1000)) { + enemyPokemon.formIndex = 1; + enemyPokemon.updateScale(); + const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; + this.scene.removeModifier(bossMBH!); + bossMBH?.setTransferrableFalse(); + this.scene.addEnemyModifier(bossMBH!); + } + } + + totalBst += enemyPokemon.getSpeciesForm().baseTotal; + + loadEnemyAssets.push(enemyPokemon.loadAssets()); + + console.log(getPokemonNameWithAffix(enemyPokemon), enemyPokemon.species.speciesId, enemyPokemon.stats); + }); + + if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { + this.scene.validateAchv(achvs.SHINY_PARTY); + } + + if (battle.battleType === BattleType.TRAINER) { + loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct? + } else { + // This block only applies for double battles to init the boss segments (idk why it's split up like this) + if (battle.enemyParty.filter(p => p.isBoss()).length > 1) { + for (const enemyPokemon of battle.enemyParty) { + // If the enemy pokemon is a boss and wasn't populated from data source, then set it up + if (enemyPokemon.isBoss() && !enemyPokemon.isPopulatedFromDataSource) { + enemyPokemon.setBoss(true, Math.ceil(enemyPokemon.bossSegments * (enemyPokemon.getSpeciesForm().baseTotal / totalBst))); + enemyPokemon.initBattleInfo(); + } + } + } + } + + Promise.all(loadEnemyAssets).then(() => { + battle.enemyParty.forEach((enemyPokemon, e) => { + if (e < (battle.double ? 2 : 1)) { + if (battle.battleType === BattleType.WILD) { + this.scene.field.add(enemyPokemon); + battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); + const playerPokemon = this.scene.getPlayerPokemon(); + if (playerPokemon?.visible) { + this.scene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); + } + enemyPokemon.tint(0, 0.5); + } else if (battle.battleType === BattleType.TRAINER) { + enemyPokemon.setVisible(false); + this.scene.currentBattle.trainer?.tint(0, 0.5); + } + if (battle.double) { + enemyPokemon.setFieldPosition(e ? FieldPosition.RIGHT : FieldPosition.LEFT); + } + } + }); + + if (!this.loaded) { + regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); + this.scene.generateEnemyModifiers(); + } + + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + if (!this.loaded) { + //@ts-ignore + this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || this.scene.lastSavePlayTime >= 300).then(success => { // TODO: get rid of ts-ignore + this.scene.disableMenu = false; + if (!success) { + return this.scene.reset(true); + } + this.doEncounter(); + }); + } else { + this.doEncounter(); + } + }); + }); + } + + doEncounter() { + this.scene.playBgm(undefined, true); + this.scene.updateModifiers(false); + this.scene.setFieldScale(1); + + /*if (startingWave > 10) { + for (let m = 0; m < Math.min(Math.floor(startingWave / 10), 99); m++) + this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier(), true); + this.scene.updateModifiers(true); + }*/ + + for (const pokemon of this.scene.getParty()) { + if (pokemon) { + pokemon.resetBattleData(); + } + } + + if (!this.loaded) { + this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); + } + + const enemyField = this.scene.getEnemyField(); + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(), + x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, + duration: 2000, + onComplete: () => { + if (!this.tryOverrideForBattleSpec()) { + this.doEncounterCommon(); + } + } + }); + } + + getEncounterMessage(): string { + const enemyField = this.scene.getEnemyField(); + + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + return i18next.t("battle:bossAppeared", { bossName: getPokemonNameWithAffix(enemyField[0])}); + } + + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + if (this.scene.currentBattle.double) { + return i18next.t("battle:trainerAppearedDouble", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + + } else { + return i18next.t("battle:trainerAppeared", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + } + } + + return enemyField.length === 1 + ? i18next.t("battle:singleWildAppeared", { pokemonName: enemyField[0].getNameToRender() }) + : i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].getNameToRender(), pokemonName2: enemyField[1].getNameToRender() }); + } + + doEncounterCommon(showEncounterMessage: boolean = true) { + const enemyField = this.scene.getEnemyField(); + + if (this.scene.currentBattle.battleType === BattleType.WILD) { + enemyField.forEach(enemyPokemon => { + enemyPokemon.untint(100, "Sine.easeOut"); + enemyPokemon.cry(); + enemyPokemon.showInfo(); + if (enemyPokemon.isShiny()) { + this.scene.validateAchv(achvs.SEE_SHINY); + } + }); + this.scene.updateFieldScale(); + if (showEncounterMessage) { + this.scene.ui.showText(this.getEncounterMessage(), null, () => this.end(), 1500); + } else { + this.end(); + } + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + const trainer = this.scene.currentBattle.trainer; + trainer?.untint(100, "Sine.easeOut"); + trainer?.playAnim(); + + const doSummon = () => { + this.scene.currentBattle.started = true; + this.scene.playBgm(undefined); + this.scene.pbTray.showPbTray(this.scene.getParty()); + this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); + const doTrainerSummon = () => { + this.hideEnemyTrainer(); + const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length; + this.scene.unshiftPhase(new SummonPhase(this.scene, 0, false)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.unshiftPhase(new SummonPhase(this.scene, 1, false)); + } + this.end(); + }; + if (showEncounterMessage) { + this.scene.ui.showText(this.getEncounterMessage(), null, doTrainerSummon, 1500, true); + } else { + doTrainerSummon(); + } + }; + + const encounterMessages = this.scene.currentBattle.trainer?.getEncounterMessages(); + + if (!encounterMessages?.length) { + doSummon(); + } else { + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); + message = message!; // tell TS compiler it's defined now + const showDialogueAndSummon = () => { + this.scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { + this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); + }); + }; + if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { + this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? + } else { + showDialogueAndSummon(); + } + } + } + } + + end() { + const enemyField = this.scene.getEnemyField(); + + enemyField.forEach((enemyPokemon, e) => { + if (enemyPokemon.isShiny()) { + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); + } + }); + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER) { + enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => { + // if there is not a player party, we can't continue + if (!this.scene.getParty()?.length) { + return false; + } + // how many player pokemon are on the field ? + const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length; + // if it's a 2vs1, there will never be a 2nd pokemon on our field even + const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2); + // if it's a double, there should be 2, otherwise 1 + if (this.scene.currentBattle.double) { + return pokemonsOnFieldCount === requiredPokemonsOnField; + } + return pokemonsOnFieldCount === 1; + })); + const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); + if (ivScannerModifier) { + enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); + } + } + + if (!this.loaded) { + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()); + + if (!availablePartyMembers[0].isOnField()) { + this.scene.pushPhase(new SummonPhase(this.scene, 0)); + } + + if (this.scene.currentBattle.double) { + if (availablePartyMembers.length > 1) { + this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); + if (!availablePartyMembers[1].isOnField()) { + this.scene.pushPhase(new SummonPhase(this.scene, 1)); + } + } + } else { + if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { + this.scene.pushPhase(new ReturnPhase(this.scene, 1)); + } + this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, false)); + } + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { + const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (availablePartyMembers.length > minPartySize) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } + } + } + } + handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end()); + } + + tryOverrideForBattleSpec(): boolean { + switch (this.scene.currentBattle.battleSpec) { + case BattleSpec.FINAL_BOSS: + const enemy = this.scene.getEnemyPokemon(); + this.scene.ui.showText(this.getEncounterMessage(), null, () => { + const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; + //The two lines below check if English ordinals (1st, 2nd, 3rd, Xth) are used and determine which one to use. + //Otherwise, it defaults to an empty string. + //As of 08-07-24: Spanish and Italian default to the English translations + const ordinalUse = ["en", "es", "it"]; + const currentLanguage = i18next.resolvedLanguage ?? "en"; + const ordinalIndex = (ordinalUse.includes(currentLanguage)) ? ["st", "nd", "rd"][((count + 90) % 100 - 10) % 10 - 1] ?? "th" : ""; + const cycleCount = count.toLocaleString() + ordinalIndex; + const encounterDialogue = i18next.t(`${(this.scene.gameData.gender === PlayerGender.FEMALE) ? "PGF" : "PGM"}battleSpecDialogue:encounter`, {cycleCount: cycleCount}); + this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + this.doEncounterCommon(false); + }); + }, 1500, true); + return true; + } + + return false; + } +} diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts new file mode 100644 index 00000000000..0b70664b993 --- /dev/null +++ b/src/phases/end-card-phase.ts @@ -0,0 +1,40 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { Phase } from "#app/phase.js"; +import { addTextObject, TextStyle } from "#app/ui/text.js"; +import i18next from "i18next"; + +export class EndCardPhase extends Phase { + public endCard: Phaser.GameObjects.Image; + public text: Phaser.GameObjects.Text; + + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + super.start(); + + this.scene.ui.getMessageHandler().bg.setVisible(false); + this.scene.ui.getMessageHandler().nameBoxContainer.setVisible(false); + + this.endCard = this.scene.add.image(0, 0, `end_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}`); + this.endCard.setOrigin(0); + this.endCard.setScale(0.5); + this.scene.field.add(this.endCard); + + this.text = addTextObject(this.scene, this.scene.game.canvas.width / 12, (this.scene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); + this.text.setOrigin(0.5); + this.scene.field.add(this.text); + + this.scene.ui.clearText(); + + this.scene.ui.fadeIn(1000).then(() => { + + this.scene.ui.showText("", null, () => { + this.scene.ui.getMessageHandler().bg.setVisible(true); + this.end(); + }, null, true); + }); + } +} diff --git a/src/phases/end-evolution-phase.ts b/src/phases/end-evolution-phase.ts new file mode 100644 index 00000000000..2a6d492a425 --- /dev/null +++ b/src/phases/end-evolution-phase.ts @@ -0,0 +1,16 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; + +export class EndEvolutionPhase extends Phase { + + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end()); + } +} diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts new file mode 100644 index 00000000000..d7f553681c2 --- /dev/null +++ b/src/phases/enemy-command-phase.ts @@ -0,0 +1,86 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js"; +import { TrappedTag } from "#app/data/battler-tags.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import * as Utils from "#app/utils.js"; +import { FieldPhase } from "./field-phase"; + +/** + * Phase for determining an enemy AI's action for the next turn. + * During this phase, the enemy decides whether to switch (if it has a trainer) + * or to use a move from its moveset. + * + * For more information on how the Enemy AI works, see docs/enemy-ai.md + * @see {@linkcode Pokemon.getMatchupScore} + * @see {@linkcode EnemyPokemon.getNextMove} + */ +export class EnemyCommandPhase extends FieldPhase { + protected fieldIndex: integer; + + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene); + + this.fieldIndex = fieldIndex; + } + + start() { + super.start(); + + const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; + + const battle = this.scene.currentBattle; + + const trainer = battle.trainer; + + /** + * If the enemy has a trainer, decide whether or not the enemy should switch + * to another member in its party. + * + * This block compares the active enemy Pokemon's {@linkcode Pokemon.getMatchupScore | matchup score} + * against the active player Pokemon with the enemy party's other non-fainted Pokemon. If a party + * member's matchup score is 3x the active enemy's score (or 2x for "boss" trainers), + * the enemy will switch to that Pokemon. + */ + if (trainer && !enemyPokemon.getMoveQueue().length) { + const opponents = enemyPokemon.getOpponents(); + + const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; + const trapped = new Utils.BooleanHolder(false); + opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, true, [])); + if (!trapTag && !trapped.value) { + const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true); + + if (partyMemberScores.length) { + const matchupScores = opponents.map(opp => enemyPokemon.getMatchupScore(opp)); + const matchupScore = matchupScores.reduce((total, score) => total += score, 0) / matchupScores.length; + + const sortedPartyMemberScores = trainer.getSortedPartyMemberMatchupScores(partyMemberScores); + + const switchMultiplier = 1 - (battle.enemySwitchCounter ? Math.pow(0.1, (1 / battle.enemySwitchCounter)) : 0); + + if (sortedPartyMemberScores[0][1] * switchMultiplier >= matchupScore * (trainer.config.isBoss ? 2 : 3)) { + const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores); + + battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.POKEMON, cursor: index, args: [false] }; + + battle.enemySwitchCounter++; + + return this.end(); + } + } + } + } + + /** Select a move to use (and a target to use it against, if applicable) */ + const nextMove = enemyPokemon.getNextMove(); + + this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.FIGHT, move: nextMove }; + + this.scene.currentBattle.enemySwitchCounter = Math.max(this.scene.currentBattle.enemySwitchCounter - 1, 0); + + this.end(); + } +} diff --git a/src/phases/enemy-party-member-pokemon-phase.ts b/src/phases/enemy-party-member-pokemon-phase.ts new file mode 100644 index 00000000000..10af0913f93 --- /dev/null +++ b/src/phases/enemy-party-member-pokemon-phase.ts @@ -0,0 +1,13 @@ +import BattleScene from "#app/battle-scene.js"; +import { EnemyPokemon } from "#app/field/pokemon.js"; +import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; + +export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { + constructor(scene: BattleScene, partyMemberIndex: integer) { + super(scene, partyMemberIndex, false); + } + + getEnemyPokemon(): EnemyPokemon { + return super.getPokemon() as EnemyPokemon; + } +} diff --git a/src/evolution-phase.ts b/src/phases/evolution-phase.ts similarity index 96% rename from src/evolution-phase.ts rename to src/phases/evolution-phase.ts index 7b50a6368f6..398450ec693 100644 --- a/src/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -1,16 +1,17 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { Phase } from "./phase"; -import BattleScene from "./battle-scene"; -import { SpeciesFormEvolution } from "./data/pokemon-evolutions"; -import EvolutionSceneHandler from "./ui/evolution-scene-handler"; -import * as Utils from "./utils"; -import { Mode } from "./ui/ui"; -import { LearnMovePhase } from "./phases"; -import { cos, sin } from "./field/anims"; -import { PlayerPokemon } from "./field/pokemon"; -import { getTypeRgb } from "./data/type"; +import { Phase } from "../phase"; +import BattleScene from "../battle-scene"; +import { SpeciesFormEvolution } from "../data/pokemon-evolutions"; +import EvolutionSceneHandler from "../ui/evolution-scene-handler"; +import * as Utils from "../utils"; +import { Mode } from "../ui/ui"; +import { cos, sin } from "../field/anims"; +import { PlayerPokemon } from "../field/pokemon"; +import { getTypeRgb } from "../data/type"; import i18next from "i18next"; -import { getPokemonNameWithAffix } from "./messages"; +import { getPokemonNameWithAffix } from "../messages"; +import { LearnMovePhase } from "./learn-move-phase"; +import { EndEvolutionPhase } from "./end-evolution-phase"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; @@ -530,16 +531,3 @@ export class EvolutionPhase extends Phase { updateParticle(); } } - -export class EndEvolutionPhase extends Phase { - - constructor(scene: BattleScene) { - super(scene); - } - - start() { - super.start(); - - this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end()); - } -} diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts new file mode 100644 index 00000000000..9c2ba95d550 --- /dev/null +++ b/src/phases/exp-phase.ts @@ -0,0 +1,35 @@ +import BattleScene from "#app/battle-scene.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { ExpBoosterModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { LevelUpPhase } from "./level-up-phase"; + +export class ExpPhase extends PlayerPartyMemberPokemonPhase { + private expValue: number; + + constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { + super(scene, partyMemberIndex); + + this.expValue = expValue; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + const exp = new Utils.NumberHolder(this.expValue); + this.scene.applyModifiers(ExpBoosterModifier, true, exp); + exp.value = Math.floor(exp.value); + this.scene.ui.showText(i18next.t("battle:expGain", { pokemonName: getPokemonNameWithAffix(pokemon), exp: exp.value }), null, () => { + const lastLevel = pokemon.level; + pokemon.addExp(exp.value); + const newLevel = pokemon.level; + if (newLevel > lastLevel) { + this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + } + pokemon.updateInfo().then(() => this.end()); + }, null, true); + } +} diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts new file mode 100644 index 00000000000..14727f992d2 --- /dev/null +++ b/src/phases/faint-phase.ts @@ -0,0 +1,171 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex, BattleType } from "#app/battle.js"; +import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability.js"; +import { BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { battleSpecDialogue } from "#app/data/dialogue.js"; +import { allMoves, PostVictoryStatChangeAttr } from "#app/data/move.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonInstantReviveModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import { DamagePhase } from "./damage-phase"; +import { PokemonPhase } from "./pokemon-phase"; +import { SwitchSummonPhase } from "./switch-summon-phase"; +import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { SwitchPhase } from "./switch-phase"; +import { VictoryPhase } from "./victory-phase"; + +export class FaintPhase extends PokemonPhase { + private preventEndure: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) { + super(scene, battlerIndex); + + this.preventEndure = preventEndure!; // TODO: is this bang correct? + } + + start() { + super.start(); + + if (!this.preventEndure) { + const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; + + if (instantReviveModifier) { + if (!--instantReviveModifier.stackCount) { + this.scene.removeModifier(instantReviveModifier); + } + this.scene.updateModifiers(this.player); + return this.end(); + } + } + + if (!this.tryOverrideForBattleSpec()) { + this.doFaint(); + } + } + + doFaint(): void { + const pokemon = this.getPokemon(); + + + // Track total times pokemon have been KO'd for supreme overlord/last respects + if (pokemon.isPlayer()) { + this.scene.currentBattle.playerFaints += 1; + } else { + this.scene.currentBattle.enemyFaints += 1; + } + + this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); + + if (pokemon.turnData?.attacksReceived?.length) { + const lastAttack = pokemon.turnData.attacksReceived[0]; + applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? + } + + const alivePlayField = this.scene.getField(true); + alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); + if (pokemon.turnData?.attacksReceived?.length) { + const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); + if (defeatSource?.isOnField()) { + applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); + const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; + const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); + if (pvattrs.length) { + for (const pvattr of pvattrs) { + pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); + } + } + } + } + + if (this.player) { + /** The total number of Pokemon in the player's party that can legally fight */ + const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + /** The total number of legal player Pokemon that aren't currently on the field */ + const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); + if (!legalPlayerPokemon.length) { + /** If the player doesn't have any legal Pokemon, end the game */ + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + /** + * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon + * is already on the field, unshift a phase that moves that Pokemon to center position. + */ + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } else if (legalPlayerPartyPokemon.length > 0) { + /** + * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, + * push a phase that prompts the player to summon a Pokemon from their party. + */ + this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); + } + } else { + this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; + if (hasReservePartyMember) { + this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false)); + } + } + } + + // in double battles redirect potential moves off fainted pokemon + if (this.scene.currentBattle.double) { + const allyPokemon = pokemon.getAlly(); + this.scene.redirectPokemonMoves(pokemon, allyPokemon); + } + + pokemon.lapseTags(BattlerTagLapseType.FAINT); + this.scene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); + + pokemon.faintCry(() => { + if (pokemon instanceof PlayerPokemon) { + pokemon.addFriendship(-10); + } + pokemon.hideInfo(); + this.scene.playSound("faint"); + this.scene.tweens.add({ + targets: pokemon, + duration: 500, + y: pokemon.y + 150, + ease: "Sine.easeIn", + onComplete: () => { + pokemon.setVisible(false); + pokemon.y -= 150; + pokemon.trySetStatus(StatusEffect.FAINT); + if (pokemon.isPlayer()) { + this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); + } else { + this.scene.addFaintedEnemyScore(pokemon as EnemyPokemon); + this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); + } + this.scene.field.remove(pokemon); + this.end(); + } + }); + }); + } + + tryOverrideForBattleSpec(): boolean { + switch (this.scene.currentBattle.battleSpec) { + case BattleSpec.FINAL_BOSS: + if (!this.player) { + const enemy = this.getPokemon(); + if (enemy.formIndex) { + this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); + } else { + // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase + enemy.hp++; + this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + this.end(); + } + return true; + } + } + + return false; + } +} diff --git a/src/phases/field-phase.ts b/src/phases/field-phase.ts new file mode 100644 index 00000000000..a9622271f14 --- /dev/null +++ b/src/phases/field-phase.ts @@ -0,0 +1,44 @@ +import { BattlerIndex } from "#app/battle.js"; +import { TrickRoomTag } from "#app/data/arena-tag.js"; +import { Stat } from "#app/enums/stat.js"; +import Pokemon from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; +import * as Utils from "#app/utils.js"; + +type PokemonFunc = (pokemon: Pokemon) => void; + +export abstract class FieldPhase extends BattlePhase { + getOrder(): BattlerIndex[] { + const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; + const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; + + // We shuffle the list before sorting so speed ties produce random results + let orderedTargets: Pokemon[] = playerField.concat(enemyField); + // We seed it with the current turn to prevent an inconsistency where it + // was varying based on how long since you last reloaded + this.scene.executeWithSeedOffset(() => { + orderedTargets = Utils.randSeedShuffle(orderedTargets); + }, this.scene.currentBattle.turn, this.scene.waveSeed); + + orderedTargets.sort((a: Pokemon, b: Pokemon) => { + const aSpeed = a?.getBattleStat(Stat.SPD) || 0; + const bSpeed = b?.getBattleStat(Stat.SPD) || 0; + + return bSpeed - aSpeed; + }); + + const speedReversed = new Utils.BooleanHolder(false); + this.scene.arena.applyTags(TrickRoomTag, speedReversed); + + if (speedReversed.value) { + orderedTargets = orderedTargets.reverse(); + } + + return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0)); + } + + executeForAll(func: PokemonFunc): void { + const field = this.scene.getField(true).filter(p => p.summonData); + field.forEach(pokemon => func(pokemon)); + } +} diff --git a/src/form-change-phase.ts b/src/phases/form-change-phase.ts similarity index 57% rename from src/form-change-phase.ts rename to src/phases/form-change-phase.ts index 5acbc4fb77c..88e0dd00ce1 100644 --- a/src/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -1,17 +1,14 @@ -import BattleScene from "./battle-scene"; -import * as Utils from "./utils"; -import { SpeciesFormKey } from "./data/pokemon-species"; -import { achvs } from "./system/achv"; -import { SpeciesFormChange, getSpeciesFormChangeMessage } from "./data/pokemon-forms"; -import { EndEvolutionPhase, EvolutionPhase } from "./evolution-phase"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon"; -import { Mode } from "./ui/ui"; -import PartyUiHandler from "./ui/party-ui-handler"; -import { BattleSpec } from "#enums/battle-spec"; -import { BattlePhase, MovePhase, PokemonHealPhase } from "./phases"; -import { getTypeRgb } from "./data/type"; -import { getPokemonNameWithAffix } from "./messages"; -import { SemiInvulnerableTag } from "./data/battler-tags"; +import BattleScene from "../battle-scene"; +import * as Utils from "../utils"; +import { SpeciesFormKey } from "../data/pokemon-species"; +import { achvs } from "../system/achv"; +import { SpeciesFormChange, getSpeciesFormChangeMessage } from "../data/pokemon-forms"; +import { PlayerPokemon } from "../field/pokemon"; +import { Mode } from "../ui/ui"; +import PartyUiHandler from "../ui/party-ui-handler"; +import { getPokemonNameWithAffix } from "../messages"; +import { EndEvolutionPhase } from "./end-evolution-phase"; +import { EvolutionPhase } from "./evolution-phase"; export class FormChangePhase extends EvolutionPhase { private formChange: SpeciesFormChange; @@ -175,126 +172,3 @@ export class FormChangePhase extends EvolutionPhase { } } } - -export class QuietFormChangePhase extends BattlePhase { - protected pokemon: Pokemon; - protected formChange: SpeciesFormChange; - - constructor(scene: BattleScene, pokemon: Pokemon, formChange: SpeciesFormChange) { - super(scene); - this.pokemon = pokemon; - this.formChange = formChange; - } - - start(): void { - super.start(); - - if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) { - return this.end(); - } - - const preName = getPokemonNameWithAffix(this.pokemon); - - if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) { - this.pokemon.changeForm(this.formChange).then(() => { - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); - }); - return; - } - - const getPokemonSprite = () => { - const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); - sprite.setOrigin(0.5, 1); - sprite.play(this.pokemon.getBattleSpriteKey()).stop(); - sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); - [ "spriteColors", "fusionSpriteColors" ].map(k => { - if (this.pokemon.summonData?.speciesForm) { - k += "Base"; - } - sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; - }); - this.scene.field.add(sprite); - return sprite; - }; - - const [ pokemonTintSprite, pokemonFormTintSprite ] = [ getPokemonSprite(), getPokemonSprite() ]; - - this.pokemon.getSprite().on("animationupdate", (_anim, frame) => { - if (frame.textureKey === pokemonTintSprite.texture.key) { - pokemonTintSprite.setFrame(frame.textureFrame); - } else { - pokemonFormTintSprite.setFrame(frame.textureFrame); - } - }); - - pokemonTintSprite.setAlpha(0); - pokemonTintSprite.setTintFill(0xFFFFFF); - pokemonFormTintSprite.setVisible(false); - pokemonFormTintSprite.setTintFill(0xFFFFFF); - - this.scene.playSound("PRSFX- Transform"); - - this.scene.tweens.add({ - targets: pokemonTintSprite, - alpha: 1, - duration: 1000, - ease: "Cubic.easeIn", - onComplete: () => { - this.pokemon.setVisible(false); - this.pokemon.changeForm(this.formChange).then(() => { - pokemonFormTintSprite.setScale(0.01); - pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop(); - pokemonFormTintSprite.setVisible(true); - this.scene.tweens.add({ - targets: pokemonTintSprite, - delay: 250, - scale: 0.01, - ease: "Cubic.easeInOut", - duration: 500, - onComplete: () => pokemonTintSprite.destroy() - }); - this.scene.tweens.add({ - targets: pokemonFormTintSprite, - delay: 250, - scale: this.pokemon.getSpriteScale(), - ease: "Cubic.easeInOut", - duration: 500, - onComplete: () => { - this.pokemon.setVisible(true); - this.scene.tweens.add({ - targets: pokemonFormTintSprite, - delay: 250, - alpha: 0, - ease: "Cubic.easeOut", - duration: 1000, - onComplete: () => { - pokemonTintSprite.setVisible(false); - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); - } - }); - } - }); - }); - } - }); - } - - end(): void { - if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { - this.scene.playBgm(); - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); - this.pokemon.findAndRemoveTags(() => true); - this.pokemon.bossSegments = 5; - this.pokemon.bossSegmentIndex = 4; - this.pokemon.initBattleInfo(); - this.pokemon.cry(); - - const movePhase = this.scene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; - if (movePhase) { - movePhase.cancel(); - } - } - - super.end(); - } -} diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-modifier-reward-phase.ts new file mode 100644 index 00000000000..e2f4d134cba --- /dev/null +++ b/src/phases/game-over-modifier-reward-phase.ts @@ -0,0 +1,27 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierTypeFunc } from "#app/modifier/modifier-type.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; + +export class GameOverModifierRewardPhase extends ModifierRewardPhase { + constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { + super(scene, modifierTypeFunc); + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + this.scene.addModifier(newModifier).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.fadeIn(250).then(() => { + this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { + this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); + resolve(); + }, null, true, 1500); + }); + }); + }); + } +} diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts new file mode 100644 index 00000000000..4beed489f29 --- /dev/null +++ b/src/phases/game-over-phase.ts @@ -0,0 +1,203 @@ +import { clientSessionId } from "#app/account.js"; +import BattleScene from "#app/battle-scene.js"; +import { BattleType } from "#app/battle.js"; +import { miscDialogue, getCharVariantFromDialogue } from "#app/data/dialogue.js"; +import { pokemonEvolutions } from "#app/data/pokemon-evolutions.js"; +import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species.js"; +import { trainerConfigs } from "#app/data/trainer-config.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { TrainerType } from "#app/enums/trainer-type.js"; +import Pokemon from "#app/field/pokemon.js"; +import { modifierTypes } from "#app/modifier/modifier-type.js"; +import { achvs, ChallengeAchv } from "#app/system/achv.js"; +import { Unlockables } from "#app/system/unlockables.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import { CheckSwitchPhase } from "./check-switch-phase"; +import { EncounterPhase } from "./encounter-phase"; +import { GameOverModifierRewardPhase } from "./game-over-modifier-reward-phase"; +import { RibbonModifierRewardPhase } from "./ribbon-modifier-reward-phase"; +import { SummonPhase } from "./summon-phase"; +import { EndCardPhase } from "./end-card-phase"; +import { PostGameOverPhase } from "./post-game-over-phase"; +import { UnlockPhase } from "./unlock-phase"; + +export class GameOverPhase extends BattlePhase { + private victory: boolean; + private firstRibbons: PokemonSpecies[] = []; + + constructor(scene: BattleScene, victory?: boolean) { + super(scene); + + this.victory = !!victory; + } + + start() { + super.start(); + + // Failsafe if players somehow skip floor 200 in classic mode + if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { + this.victory = true; + } + + if (this.victory && this.scene.gameMode.isEndless) { + this.scene.ui.showDialogue(i18next.t("PGMmiscDialogue:ending_endless"), i18next.t("PGMmiscDialogue:ending_name"), 0, () => this.handleGameOver()); + } else if (this.victory || !this.scene.enableRetries) { + this.handleGameOver(); + } else { + this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.fadeOut(1250).then(() => { + this.scene.reset(); + this.scene.clearPhaseQueue(); + this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { + this.scene.pushPhase(new EncounterPhase(this.scene, true)); + + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; + + this.scene.pushPhase(new SummonPhase(this.scene, 0)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.pushPhase(new SummonPhase(this.scene, 1)); + } + if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } + } + + this.scene.ui.fadeIn(1250); + this.end(); + }); + }); + }, () => this.handleGameOver(), false, 0, 0, 1000); + }); + } + } + + handleGameOver(): void { + const doGameOver = (newClear: boolean) => { + this.scene.disableMenu = true; + this.scene.time.delayedCall(1000, () => { + let firstClear = false; + if (this.victory && newClear) { + if (this.scene.gameMode.isClassic) { + firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); + this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); + this.scene.gameData.gameStats.sessionsWon++; + for (const pokemon of this.scene.getParty()) { + this.awardRibbon(pokemon); + + if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) { + this.awardRibbon(pokemon, true); + } + } + } else if (this.scene.gameMode.isDaily && newClear) { + this.scene.gameData.gameStats.dailyRunSessionsWon++; + } + } + const fadeDuration = this.victory ? 10000 : 5000; + this.scene.fadeOutBgm(fadeDuration, true); + const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); + activeBattlers.map(p => p.hideInfo()); + this.scene.ui.fadeOut(fadeDuration).then(() => { + activeBattlers.map(a => a.setVisible(false)); + this.scene.setFieldScale(1, true); + this.scene.clearPhaseQueue(); + this.scene.ui.clearText(); + + if (this.victory && this.scene.gameMode.isChallenge) { + this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); + } + + const clear = (endCardPhase?: EndCardPhase) => { + if (newClear) { + this.handleUnlocks(); + } + if (this.victory && newClear) { + for (const species of this.firstRibbons) { + this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species)); + } + if (!firstClear) { + this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM)); + } + } + this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); + this.end(); + }; + + if (this.victory && this.scene.gameMode.isClassic) { + const message = miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1]; + + if (!this.scene.ui.shouldSkipDialogue(message)) { + this.scene.ui.fadeIn(500).then(() => { + this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(miscDialogue.ending[this.scene.gameData.gender === PlayerGender.FEMALE ? 0 : 1])).then(() => { + this.scene.ui.showDialogue(message, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { + this.scene.ui.fadeOut(500).then(() => { + this.scene.charSprite.hide().then(() => { + const endCardPhase = new EndCardPhase(this.scene); + this.scene.unshiftPhase(endCardPhase); + clear(endCardPhase); + }); + }); + }); + }); + }); + } else { + const endCardPhase = new EndCardPhase(this.scene); + this.scene.unshiftPhase(endCardPhase); + clear(endCardPhase); + } + } else { + clear(); + } + }); + }); + }; + + /* Added a local check to see if the game is running offline on victory + If Online, execute apiFetch as intended + If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ + if (this.victory) { + if (!Utils.isLocal) { + Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) + .then(response => response.json()) + .then(newClear => doGameOver(newClear)); + } else { + this.scene.gameData.offlineNewClear(this.scene).then(result => { + doGameOver(result); + }); + } + } else { + doGameOver(false); + } + } + + handleUnlocks(): void { + if (this.victory && this.scene.gameMode.isClassic) { + if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); + } + if (this.scene.getParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.SPLICED_ENDLESS_MODE)); + } + if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE)); + } + if (!this.scene.gameData.unlocks[Unlockables.EVIOLITE] && this.scene.getParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) { + this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.EVIOLITE)); + } + } + } + + awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void { + const speciesId = getPokemonSpecies(pokemon.species.speciesId); + const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter); + // first time classic win, award voucher + if (speciesRibbonCount === 1) { + this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); + } + } +} diff --git a/src/phases/hide-party-exp-bar-phase.ts b/src/phases/hide-party-exp-bar-phase.ts new file mode 100644 index 00000000000..c2c9d96462e --- /dev/null +++ b/src/phases/hide-party-exp-bar-phase.ts @@ -0,0 +1,14 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlePhase } from "./battle-phase"; + +export class HidePartyExpBarPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.partyExpBar.hide().then(() => this.end()); + } +} diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts new file mode 100644 index 00000000000..e30fc0c3d10 --- /dev/null +++ b/src/phases/learn-move-phase.ts @@ -0,0 +1,103 @@ +import BattleScene from "#app/battle-scene.js"; +import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims.js"; +import { allMoves } from "#app/data/move.js"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms.js"; +import { Moves } from "#app/enums/moves.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import EvolutionSceneHandler from "#app/ui/evolution-scene-handler.js"; +import { SummaryUiMode } from "#app/ui/summary-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; + +export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { + private moveId: Moves; + + constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) { + super(scene, partyMemberIndex); + + this.moveId = moveId; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + const move = allMoves[this.moveId]; + + const existingMoveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === move.id); + + if (existingMoveIndex > -1) { + return this.end(); + } + + const emptyMoveIndex = pokemon.getMoveset().length < 4 + ? pokemon.getMoveset().length + : pokemon.getMoveset().findIndex(m => m === null); + + const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler + ? Mode.EVOLUTION_SCENE + : Mode.MESSAGE; + + if (emptyMoveIndex > -1) { + pokemon.setMove(emptyMoveIndex, this.moveId); + initMoveAnim(this.scene, this.moveId).then(() => { + loadMoveAnimAssets(this.scene, [this.moveId], true) + .then(() => { + this.scene.ui.setMode(messageMode).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.showText(i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); + this.end(); + }, messageMode === Mode.EVOLUTION_SCENE ? 1000 : null, true); + }); + }); + }); + } else { + this.scene.ui.setMode(messageMode).then(() => { + this.scene.ui.showText(i18next.t("battle:learnMovePrompt", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => { + this.scene.ui.showText(i18next.t("battle:learnMoveLimitReached", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + this.scene.ui.showText(i18next.t("battle:learnMoveReplaceQuestion", { moveName: move.name }), null, () => { + const noHandler = () => { + this.scene.ui.setMode(messageMode).then(() => { + this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { + this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { + this.scene.ui.setMode(messageMode); + this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), null, () => this.end(), null, true); + }, () => { + this.scene.ui.setMode(messageMode); + this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); + this.end(); + }); + }); + }); + }; + this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { + this.scene.ui.setMode(messageMode); + this.scene.ui.showText(i18next.t("battle:learnMoveForgetQuestion"), null, () => { + this.scene.ui.setModeWithoutClear(Mode.SUMMARY, this.getPokemon(), SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { + if (moveIndex === 4) { + noHandler(); + return; + } + this.scene.ui.setMode(messageMode).then(() => { + this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { + this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: pokemon.moveset[moveIndex]!.getName() }), null, () => { // TODO: is the bang correct? + this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { + pokemon.setMove(moveIndex, Moves.NONE); + this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); + this.end(); + }, null, true); + }, null, true); + }, null, true); + }); + }); + }, null, true); + }, noHandler); + }); + }, null, true); + }, null, true); + }); + } + } +} diff --git a/src/phases/level-cap-phase.ts b/src/phases/level-cap-phase.ts new file mode 100644 index 00000000000..4a07e7d131e --- /dev/null +++ b/src/phases/level-cap-phase.ts @@ -0,0 +1,20 @@ +import BattleScene from "#app/battle-scene.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { FieldPhase } from "./field-phase"; + +export class LevelCapPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + super.start(); + + this.scene.ui.setMode(Mode.MESSAGE).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.showText(i18next.t("battle:levelCapUp", { levelCap: this.scene.getMaxExpLevel() }), null, () => this.end(), null, true); + this.executeForAll(pokemon => pokemon.updateInfo(true)); + }); + } +} diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts new file mode 100644 index 00000000000..a8a6b8f3d80 --- /dev/null +++ b/src/phases/level-up-phase.ts @@ -0,0 +1,59 @@ +import BattleScene from "#app/battle-scene.js"; +import { ExpNotification } from "#app/enums/exp-notification.js"; +import { EvolutionPhase } from "#app/phases/evolution-phase.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { LevelAchv } from "#app/system/achv.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { LearnMovePhase } from "./learn-move-phase"; + +export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { + private lastLevel: integer; + private level: integer; + + constructor(scene: BattleScene, partyMemberIndex: integer, lastLevel: integer, level: integer) { + super(scene, partyMemberIndex); + + this.lastLevel = lastLevel; + this.level = level; + this.scene = scene; + } + + start() { + super.start(); + + if (this.level > this.scene.gameData.gameStats.highestLevel) { + this.scene.gameData.gameStats.highestLevel = this.level; + } + + this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); + + const pokemon = this.getPokemon(); + const prevStats = pokemon.stats.slice(0); + pokemon.calculateStats(); + pokemon.updateInfo(); + if (this.scene.expParty === ExpNotification.DEFAULT) { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.showText(i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.getPokemon()), level: this.level }), null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()), null, true); + } else if (this.scene.expParty === ExpNotification.SKIP) { + this.end(); + } else { + // we still want to display the stats if activated + this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); + } + if (this.lastLevel < 100) { // this feels like an unnecessary optimization + const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); + for (const lm of levelMoves) { + this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm[1])); + } + } + if (!pokemon.pauseEvolutions) { + const evolution = pokemon.getEvolution(); + if (evolution) { + this.scene.unshiftPhase(new EvolutionPhase(this.scene, pokemon as PlayerPokemon, evolution, this.lastLevel)); + } + } + } +} diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts new file mode 100644 index 00000000000..b58cc9bca1f --- /dev/null +++ b/src/phases/login-phase.ts @@ -0,0 +1,116 @@ +import { updateUserInfo } from "#app/account.js"; +import BattleScene, { bypassLogin } from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { handleTutorial, Tutorial } from "#app/tutorial.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next, { t } from "i18next"; +import * as Utils from "#app/utils.js"; +import { SelectGenderPhase } from "./select-gender-phase"; +import { UnavailablePhase } from "./unavailable-phase"; + +export class LoginPhase extends Phase { + private showText: boolean; + + constructor(scene: BattleScene, showText?: boolean) { + super(scene); + + this.showText = showText === undefined || !!showText; + } + + start(): void { + super.start(); + + const hasSession = !!Utils.getCookie(Utils.sessionIdKey); + + this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); + Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { + const success = response ? response[0] : false; + const statusCode = response ? response[1] : null; + if (!success) { + if (!statusCode || statusCode === 400) { + if (this.showText) { + this.scene.ui.showText(i18next.t("menu:logInOrCreateAccount")); + } + + this.scene.playSound("menu_open"); + + const loadData = () => { + updateUserInfo().then(success => { + if (!success[0]) { + Utils.removeCookie(Utils.sessionIdKey); + this.scene.reset(true, true); + return; + } + this.scene.gameData.loadSystem().then(() => this.end()); + }); + }; + + this.scene.ui.setMode(Mode.LOGIN_FORM, { + buttonActions: [ + () => { + this.scene.ui.playSelect(); + loadData(); + }, () => { + this.scene.playSound("menu_open"); + this.scene.ui.setMode(Mode.REGISTRATION_FORM, { + buttonActions: [ + () => { + this.scene.ui.playSelect(); + updateUserInfo().then(success => { + if (!success[0]) { + Utils.removeCookie(Utils.sessionIdKey); + this.scene.reset(true, true); + return; + } + this.end(); + } ); + }, () => { + this.scene.unshiftPhase(new LoginPhase(this.scene, false)); + this.end(); + } + ] + }); + }, () => { + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); + const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; + const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; + window.open(discordUrl, "_self"); + }, () => { + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); + const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; + const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; + window.open(googleUrl, "_self"); + } + ] + }); + } else if (statusCode === 401) { + Utils.removeCookie(Utils.sessionIdKey); + this.scene.reset(true, true); + } else { + this.scene.unshiftPhase(new UnavailablePhase(this.scene)); + super.end(); + } + return null; + } else { + this.scene.gameData.loadSystem().then(success => { + if (success || bypassLogin) { + this.end(); + } else { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(t("menu:failedToLoadSaveData")); + } + }); + } + }); + } + + end(): void { + this.scene.ui.setMode(Mode.MESSAGE); + + if (!this.scene.gameData.gender) { + this.scene.unshiftPhase(new SelectGenderPhase(this.scene)); + } + + handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); + } +} diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts new file mode 100644 index 00000000000..46e907ae2ba --- /dev/null +++ b/src/phases/message-phase.ts @@ -0,0 +1,38 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; + +export class MessagePhase extends Phase { + private text: string; + private callbackDelay: integer | null; + private prompt: boolean | null; + private promptDelay: integer | null; + + constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) { + super(scene); + + this.text = text; + this.callbackDelay = callbackDelay!; // TODO: is this bang correct? + this.prompt = prompt!; // TODO: is this bang correct? + this.promptDelay = promptDelay!; // TODO: is this bang correct? + } + + start() { + super.start(); + + if (this.text.indexOf("$") > -1) { + const pageIndex = this.text.indexOf("$"); + this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay)); + this.text = this.text.slice(0, pageIndex).trim(); + } + + this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); + } + + end() { + if (this.scene.abilityBar.shown) { + this.scene.abilityBar.hide(); + } + + super.end(); + } +} diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts new file mode 100644 index 00000000000..4d083a367a6 --- /dev/null +++ b/src/phases/modifier-reward-phase.ts @@ -0,0 +1,30 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierType, ModifierTypeFunc, getModifierType } from "#app/modifier/modifier-type.js"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; + +export class ModifierRewardPhase extends BattlePhase { + protected modifierType: ModifierType; + + constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { + super(scene); + + this.modifierType = getModifierType(modifierTypeFunc); + } + + start() { + super.start(); + + this.doReward().then(() => this.end()); + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + this.scene.addModifier(newModifier).then(() => { + this.scene.playSound("item_fanfare"); + this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); + }); + }); + } +} diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts new file mode 100644 index 00000000000..e0bff608972 --- /dev/null +++ b/src/phases/money-reward-phase.ts @@ -0,0 +1,34 @@ +import BattleScene from "#app/battle-scene.js"; +import { ArenaTagType } from "#app/enums/arena-tag-type.js"; +import { MoneyMultiplierModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; + +export class MoneyRewardPhase extends BattlePhase { + private moneyMultiplier: number; + + constructor(scene: BattleScene, moneyMultiplier: number) { + super(scene); + + this.moneyMultiplier = moneyMultiplier; + } + + start() { + const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); + + this.scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + + if (this.scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { + moneyAmount.value *= 2; + } + + this.scene.addMoney(moneyAmount.value); + + const userLocale = navigator.language || "en-US"; + const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); + const message = i18next.t("battle:moneyWon", { moneyAmount: formattedMoneyAmount }); + + this.scene.ui.showText(message, null, () => this.end(), null, true); + } +} diff --git a/src/phases/move-anim-test-phase.ts b/src/phases/move-anim-test-phase.ts new file mode 100644 index 00000000000..7fb3c1d61e7 --- /dev/null +++ b/src/phases/move-anim-test-phase.ts @@ -0,0 +1,44 @@ +import BattleScene from "#app/battle-scene.js"; +import { initMoveAnim, loadMoveAnimAssets, MoveAnim } from "#app/data/battle-anims.js"; +import { allMoves, SelfStatusMove } from "#app/data/move.js"; +import { Moves } from "#app/enums/moves.js"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; + +export class MoveAnimTestPhase extends BattlePhase { + private moveQueue: Moves[]; + + constructor(scene: BattleScene, moveQueue?: Moves[]) { + super(scene); + + this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1); + } + + start() { + const moveQueue = this.moveQueue.slice(0); + this.playMoveAnim(moveQueue, true); + } + + playMoveAnim(moveQueue: Moves[], player: boolean) { + const moveId = player ? moveQueue[0] : moveQueue.shift(); + if (moveId === undefined) { + this.playMoveAnim(this.moveQueue.slice(0), true); + return; + } else if (player) { + console.log(Moves[moveId]); + } + + initMoveAnim(this.scene, moveId).then(() => { + loadMoveAnimAssets(this.scene, [moveId], true) + .then(() => { + new MoveAnim(moveId, player ? this.scene.getPlayerPokemon()! : this.scene.getEnemyPokemon()!, (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon()! : this.scene.getPlayerPokemon()!).getBattlerIndex()).play(this.scene, () => { // TODO: are the bangs correct here? + if (player) { + this.playMoveAnim(moveQueue, false); + } else { + this.playMoveAnim(moveQueue, true); + } + }); + }); + }); + } +} diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts new file mode 100644 index 00000000000..a5ac913cc5d --- /dev/null +++ b/src/phases/move-effect-phase.ts @@ -0,0 +1,447 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, applyPostDefendAbAttrs, PostDefendAbAttr, applyPostAttackAbAttrs, PostAttackAbAttr, MaxMultiHitAbAttr, AlwaysHitAbAttr } from "#app/data/ability.js"; +import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag.js"; +import { MoveAnim } from "#app/data/battle-anims.js"; +import { BattlerTagLapseType, ProtectedTag, SemiInvulnerableTag } from "#app/data/battler-tags.js"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move.js"; +import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Moves } from "#app/enums/moves.js"; +import Pokemon, { PokemonMove, MoveResult, HitResult } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonMultiHitModifier, FlinchChanceModifier, EnemyAttackStatusEffectChanceModifier, ContactHeldItemTransferChanceModifier, HitHealModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class MoveEffectPhase extends PokemonPhase { + public move: PokemonMove; + protected targets: BattlerIndex[]; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { + super(scene, battlerIndex); + this.move = move; + /** + * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies + * with no party members available to switch in, then the right Pokemon takes the index + * of the left Pokemon and gets hit unless this is checked. + */ + if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { + const i = targets.indexOf(battlerIndex); + targets.splice(i, i + 1); + } + this.targets = targets; + } + + start() { + super.start(); + + /** The Pokemon using this phase's invoked move */ + const user = this.getUserPokemon(); + /** All Pokemon targeted by this phase's invoked move */ + const targets = this.getTargets(); + + /** If the user was somehow removed from the field, end this phase */ + if (!user?.isOnField()) { + return super.end(); + } + + /** + * Does an effect from this move override other effects on this turn? + * e.g. Charging moves (Fly, etc.) on their first turn of use. + */ + const overridden = new Utils.BooleanHolder(false); + /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ + const move = this.move.getMove(); + + // Assume single target for override + applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget() ?? null, move, overridden, this.move.virtual).then(() => { + // If other effects were overriden, stop this phase before they can be applied + if (overridden.value) { + return this.end(); + } + + user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + + /** + * If this phase is for the first hit of the invoked move, + * resolve the move's total hit count. This block combines the + * effects of the move itself, Parental Bond, and Multi-Lens to do so. + */ + if (user.turnData.hitsLeft === undefined) { + const hitCount = new Utils.IntegerHolder(1); + // Assume single target for multi hit + applyMoveAttrs(MultiHitAttr, user, this.getTarget() ?? null, move, hitCount); + // If Parental Bond is applicable, double the hit count + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0)); + // If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user + if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { + this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0)); + } + // Set the user's relevant turnData fields to reflect the final hit count + user.turnData.hitCount = hitCount.value; + user.turnData.hitsLeft = hitCount.value; + } + + /** + * Log to be entered into the user's move history once the move result is resolved. + * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully + * used in the sense of "Does it have an effect on the user?". + */ + const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; + + /** + * Stores results of hit checks of the invoked move against all targets, organized by battler index. + * @see {@linkcode hitCheck} + */ + const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)])); + const hasActiveTargets = targets.some(t => t.isActive(true)); + /** + * If no targets are left for the move to hit (FAIL), or the invoked move is single-target + * (and not random target) and failed the hit check against its target (MISS), log the move + * as FAILed or MISSed (depending on the conditions above) and end this phase. + */ + if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { + this.stopMultiHit(); + if (hasActiveTargets) { + this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getTarget()? getPokemonNameWithAffix(this.getTarget()!) : "" })); + moveHistoryEntry.result = MoveResult.MISS; + applyMoveAttrs(MissEffectAttr, user, null, move); + } else { + this.scene.queueMessage(i18next.t("battle:attackFailed")); + moveHistoryEntry.result = MoveResult.FAIL; + } + user.pushMoveHistory(moveHistoryEntry); + return this.end(); + } + + /** All move effect attributes are chained together in this array to be applied asynchronously. */ + const applyAttrs: Promise[] = []; + + // Move animation only needs one target + new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()!).play(this.scene, () => { // TODO: is the bang correct here? + /** Has the move successfully hit a target (for damage) yet? */ + let hasHit: boolean = false; + for (const target of targets) { + /** + * If the move missed a target, stop all future hits against that target + * and move on to the next target (if there is one). + */ + if (!targetHitChecks[target.getBattlerIndex()]) { + this.stopMultiHit(target); + this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + if (moveHistoryEntry.result === MoveResult.PENDING) { + moveHistoryEntry.result = MoveResult.MISS; + } + user.pushMoveHistory(moveHistoryEntry); + applyMoveAttrs(MissEffectAttr, user, null, move); + continue; + } + + /** The {@linkcode ArenaTagSide} to which the target belongs */ + const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + /** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */ + const hasConditionalProtectApplied = new Utils.BooleanHolder(false); + /** Does the applied conditional protection bypass Protect-ignoring effects? */ + const bypassIgnoreProtect = new Utils.BooleanHolder(false); + // If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects + if (!this.move.getMove().isAllyTarget()) { + this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); + } + + /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ + const isProtected = (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target)) + && (hasConditionalProtectApplied.value || target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType))); + + /** Does this phase represent the invoked move's first strike? */ + const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount); + + // Only log the move's result on the first strike + if (firstHit) { + user.pushMoveHistory(moveHistoryEntry); + } + + /** + * Since all fail/miss checks have applied, the move is considered successfully applied. + * It's worth noting that if the move has no effect or is protected against, this assignment + * is overwritten and the move is logged as a FAIL. + */ + moveHistoryEntry.result = MoveResult.SUCCESS; + + /** + * Stores the result of applying the invoked move to the target. + * If the target is protected, the result is always `NO_EFFECT`. + * Otherwise, the hit result is based on type effectiveness, immunities, + * and other factors that may negate the attack or status application. + * + * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated + * (for attack moves) and the target's HP is updated. However, this isn't + * made visible to the user until the resulting {@linkcode DamagePhase} + * is invoked. + */ + const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; + + /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ + const dealsDamage = [ + HitResult.EFFECTIVE, + HitResult.SUPER_EFFECTIVE, + HitResult.NOT_VERY_EFFECTIVE, + HitResult.ONE_HIT_KO + ].includes(hitResult); + + /** Is this target the first one hit by the move on its current strike? */ + const firstTarget = dealsDamage && !hasHit; + if (firstTarget) { + hasHit = true; + } + + /** + * If the move has no effect on the target (i.e. the target is protected or immune), + * change the logged move result to FAIL. + */ + if (hitResult === HitResult.NO_EFFECT) { + moveHistoryEntry.result = MoveResult.FAIL; + } + + /** Does this phase represent the invoked move's last strike? */ + const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); + + /** + * If the user can change forms by using the invoked move, + * it only changes forms after the move's last hit + * (see Relic Song's interaction with Parental Bond when used by Meloetta). + */ + if (lastHit) { + this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); + } + + /** + * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. + * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger + * type requires different conditions to be met with respect to the move's hit result. + */ + applyAttrs.push(new Promise(resolve => { + // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, + user, target, move).then(() => { + // All other effects require the move to not have failed or have been cancelled to trigger + if (hitResult !== HitResult.FAIL) { + /** Are the move's effects tied to the first turn of a charge move? */ + const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget() ?? null, move)); + /** + * If the invoked move's effects are meant to trigger during the move's "charge turn," + * ignore all effects after this point. + * Otherwise, apply all self-targeted POST_APPLY effects. + */ + Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY + && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => { + // All effects past this point require the move to have hit the target + if (hitResult !== HitResult.NO_EFFECT) { + // Apply all non-self-targeted POST_APPLY effects + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY + && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { + /** + * If the move hit, and the target doesn't have Shield Dust, + * apply the chance to flinch the target gained from King's Rock + */ + if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) { + const flinched = new Utils.BooleanHolder(false); + user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); + if (flinched.value) { + target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); + } + } + // If the move was not protected against, apply all HIT effects + Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT + && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && (!attr.firstTargetOnly || firstTarget), user, target, this.move.getMove()).then(() => { + // Apply the target's post-defend ability effects (as long as the target is active or can otherwise apply them) + return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => { + // If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tags and tokens + target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING); + if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) { + target.lapseTag(BattlerTagType.SHELL_TRAP); + } + if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { + user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); + } + })).then(() => { + // Apply the user's post-attack ability effects + applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { + /** + * If the invoked move is an attack, apply the user's chance to + * steal an item from the target granted by Grip Claw + */ + if (this.move.getMove() instanceof AttackMove) { + this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + } + resolve(); + }); + }); + }) + ).then(() => resolve()); + }); + } else { + applyMoveAttrs(NoEffectAttr, user, null, move).then(() => resolve()); + } + }); + } else { + resolve(); + } + }); + })); + } + // Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved + const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ? + applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) : + null; + + if (!!postTarget) { + if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after + applyAttrs[applyAttrs.length - 1]?.then(() => postTarget); + } else { // Otherwise, push a new asynchronous move effect + applyAttrs.push(postTarget); + } + } + + // Wait for all move effects to finish applying, then end this phase + Promise.allSettled(applyAttrs).then(() => this.end()); + }); + }); + } + + end() { + const move = this.move.getMove(); + move.type = move.defaultType; + const user = this.getUserPokemon(); + /** + * If this phase isn't for the invoked move's last strike, + * unshift another MoveEffectPhase for the next strike. + * Otherwise, queue a message indicating the number of times the move has struck + * (if the move has struck more than once), then apply the heal from Shell Bell + * to the user. + */ + if (user) { + if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { + this.scene.unshiftPhase(this.getNewHitPhase()); + } else { + // Queue message for number of hits made by multi-move + // If multi-hit attack only hits once, still want to render a message + const hitsTotal = user.turnData.hitCount! - Math.max(user.turnData.hitsLeft!, 0); // TODO: are those bangs correct? + if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { + // If there are multiple hits, or if there are hits of the multi-hit move left + this.scene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); + } + this.scene.applyModifiers(HitHealModifier, this.player, user); + } + } + + super.end(); + } + + /** + * Resolves whether this phase's invoked move hits or misses the given target + * @param target {@linkcode Pokemon} the Pokemon targeted by the invoked move + * @returns `true` if the move does not miss the target; `false` otherwise + */ + hitCheck(target: Pokemon): boolean { + // Moves targeting the user and entry hazards can't miss + if ([MoveTarget.USER, MoveTarget.ENEMY_SIDE].includes(this.move.getMove().moveTarget)) { + return true; + } + + const user = this.getUserPokemon()!; // TODO: is this bang correct? + + // Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits. + // However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal + // multi-hit move and proceed with all hits + if (user.turnData.hitsLeft < user.turnData.hitCount) { + if (!this.move.getMove().hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { + return true; + } + } + + if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { + return true; + } + + // If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match + if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) { + return true; + } + + if (target.getTag(BattlerTagType.ALWAYS_GET_HIT)) { + return true; + } + + const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); + if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)) { + return false; + } + + const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here? + + if (moveAccuracy === -1) { + return true; + } + + const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove()); + const rand = user.randSeedInt(100, 1); + + return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct? + } + + /** Returns the {@linkcode Pokemon} using this phase's invoked move */ + getUserPokemon(): Pokemon | undefined { + if (this.battlerIndex > BattlerIndex.ENEMY_2) { + return this.scene.getPokemonById(this.battlerIndex) ?? undefined; + } + return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; + } + + /** Returns an array of all {@linkcode Pokemon} targeted by this phase's invoked move */ + getTargets(): Pokemon[] { + return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); + } + + /** Returns the first target of this phase's invoked move */ + getTarget(): Pokemon | undefined { + return this.getTargets()[0]; + } + + /** + * Removes the given {@linkcode Pokemon} from this phase's target list + * @param target {@linkcode Pokemon} the Pokemon to be removed + */ + removeTarget(target: Pokemon): void { + const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); + if (targetIndex !== -1) { + this.targets.splice(this.targets.findIndex(ind => ind === target.getBattlerIndex()), 1); + } + } + + /** + * Prevents subsequent strikes of this phase's invoked move from occurring + * @param target {@linkcode Pokemon} if defined, only stop subsequent + * strikes against this Pokemon + */ + stopMultiHit(target?: Pokemon): void { + /** If given a specific target, remove the target from subsequent strikes */ + if (target) { + this.removeTarget(target); + } + /** + * If no target specified, or the specified target was the last of this move's + * targets, completely cancel all subsequent strikes. + */ + if (!target || this.targets.length === 0 ) { + this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? + this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? + } + } + + /** Returns a new MoveEffectPhase with the same properties as this phase */ + getNewHitPhase() { + return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); + } +} diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts new file mode 100644 index 00000000000..4bce2185aea --- /dev/null +++ b/src/phases/move-end-phase.ts @@ -0,0 +1,23 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class MoveEndPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + if (pokemon.isActive(true)) { + pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); + } + + this.scene.arena.setIgnoreAbilities(false); + + this.end(); + } +} diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts new file mode 100644 index 00000000000..5166f287731 --- /dev/null +++ b/src/phases/move-header-phase.ts @@ -0,0 +1,30 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/move.js"; +import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; + +export class MoveHeaderPhase extends BattlePhase { + public pokemon: Pokemon; + public move: PokemonMove; + + constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) { + super(scene); + + this.pokemon = pokemon; + this.move = move; + } + + canMove(): boolean { + return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon); + } + + start() { + super.start(); + + if (this.canMove()) { + applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()).then(() => this.end()); + } else { + this.end(); + } + } +} diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts new file mode 100644 index 00000000000..b9656df856b --- /dev/null +++ b/src/phases/move-phase.ts @@ -0,0 +1,329 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyAbAttrs, RedirectMoveAbAttr, BlockRedirectAbAttr, IncreasePpAbAttr, applyPreAttackAbAttrs, PokemonTypeChangeAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr } from "#app/data/ability.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { CenterOfAttentionTag, BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { MoveFlags, BypassRedirectAttr, allMoves, CopyMoveAttr, applyMoveAttrs, BypassSleepAttr, HealStatusEffectAttr, ChargeAttr, PreMoveMessageAttr } from "#app/data/move.js"; +import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms.js"; +import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect.js"; +import { Type } from "#app/data/type.js"; +import { getTerrainBlockMessage } from "#app/data/weather.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { Moves } from "#app/enums/moves.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { MoveUsedEvent } from "#app/events/battle-scene.js"; +import Pokemon, { PokemonMove, MoveResult, TurnMove } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import { CommonAnimPhase } from "./common-anim-phase"; +import { MoveEffectPhase } from "./move-effect-phase"; +import { MoveEndPhase } from "./move-end-phase"; +import { ShowAbilityPhase } from "./show-ability-phase"; + +export class MovePhase extends BattlePhase { + public pokemon: Pokemon; + public move: PokemonMove; + public targets: BattlerIndex[]; + protected followUp: boolean; + protected ignorePp: boolean; + protected failed: boolean; + protected cancelled: boolean; + + constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) { + super(scene); + + this.pokemon = pokemon; + this.targets = targets; + this.move = move; + this.followUp = !!followUp; + this.ignorePp = !!ignorePp; + this.failed = false; + this.cancelled = false; + } + + canMove(): boolean { + return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp) && !!this.targets.length; + } + + /**Signifies the current move should fail but still use PP */ + fail(): void { + this.failed = true; + } + + /**Signifies the current move should cancel and retain PP */ + cancel(): void { + this.cancelled = true; + } + + start() { + super.start(); + + console.log(Moves[this.move.moveId]); + + if (!this.canMove()) { + if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) { + this.scene.queueMessage(`${this.move.getName()} is disabled!`); + } + if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails + this.fail(); + this.showMoveText(); + this.showFailedText(); + } + return this.end(); + } + + if (!this.followUp) { + if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { + this.scene.arena.setIgnoreAbilities(); + } + } else { + this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct? + this.pokemon.turnData.hitCount = 0; // TODO: is `0` correct? + } + + // Move redirection abilities (ie. Storm Drain) only support single target moves + const moveTarget = this.targets.length === 1 + ? new Utils.IntegerHolder(this.targets[0]) + : null; + if (moveTarget) { + const oldTarget = moveTarget.value; + this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, this.move.moveId, moveTarget)); + this.pokemon.getOpponents().forEach(p => { + const redirectTag = p.getTag(CenterOfAttentionTag) as CenterOfAttentionTag; + if (redirectTag && (!redirectTag.powder || (!this.pokemon.isOfType(Type.GRASS) && !this.pokemon.hasAbility(Abilities.OVERCOAT)))) { + moveTarget.value = p.getBattlerIndex(); + } + }); + //Check if this move is immune to being redirected, and restore its target to the intended target if it is. + if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) || this.move.getMove().hasAttr(BypassRedirectAttr))) { + //If an ability prevented this move from being redirected, display its ability pop up. + if ((this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr) && !this.move.getMove().hasAttr(BypassRedirectAttr)) && oldTarget !== moveTarget.value) { + this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); + } + moveTarget.value = oldTarget; + } + this.targets[0] = moveTarget.value; + } + + // Check for counterattack moves to switch target + if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { + if (this.pokemon.turnData.attacksReceived.length) { + const attack = this.pokemon.turnData.attacksReceived[0]; + this.targets[0] = attack.sourceBattlerIndex; + + // account for metal burst and comeuppance hitting remaining targets in double battles + // counterattack will redirect to remaining ally if original attacker faints + if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) { + if (this.scene.getField()[this.targets[0]].hp === 0) { + const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + //@ts-ignore + this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex(); //TODO: fix ts-ignore + } + } + } + if (this.targets[0] === BattlerIndex.ATTACKER) { + this.fail(); // Marks the move as failed for later in doMove + this.showMoveText(); + this.showFailedText(); + } + } + + const targets = this.scene.getField(true).filter(p => { + if (this.targets.indexOf(p.getBattlerIndex()) > -1) { + return true; + } + return false; + }); + + const doMove = () => { + this.pokemon.turnData.acted = true; // Record that the move was attempted, even if it fails + + this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); + + let ppUsed = 1; + // Filter all opponents to include only those this move is targeting + const targetedOpponents = this.pokemon.getOpponents().filter(o => this.targets.includes(o.getBattlerIndex())); + for (const opponent of targetedOpponents) { + if (this.move.ppUsed + ppUsed >= this.move.getMovePp()) { // If we're already at max PP usage, stop checking + break; + } + if (opponent.hasAbilityWithAttr(IncreasePpAbAttr)) { // Accounting for abilities like Pressure + ppUsed++; + } + } + + if (!this.followUp && this.canMove() && !this.cancelled) { + this.pokemon.lapseTags(BattlerTagLapseType.MOVE); + } + + const moveQueue = this.pokemon.getMoveQueue(); + if (this.cancelled || this.failed) { + if (this.failed) { + this.move.usePp(ppUsed); // Only use PP if the move failed + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); + } + + // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock + this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); + + this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. + moveQueue.shift(); // Remove the second turn of charge moves + return this.end(); + } + + this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); + + if (this.move.moveId) { + this.showMoveText(); + } + + // This should only happen when there are no valid targets left on the field + if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) { + this.showFailedText(); + this.cancel(); + + // Record a failed move so Abilities like Truant don't trigger next turn and soft-lock + this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); + + this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Remove any tags from moves like Fly/Dive/etc. + + moveQueue.shift(); + return this.end(); + } + + if (!moveQueue.length || !moveQueue.shift()?.ignorePP) { // using .shift here clears out two turn moves once they've been used + this.move.usePp(ppUsed); + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); + } + + if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { + this.scene.currentBattle.lastMove = this.move.moveId; + } + + // Assume conditions affecting targets only apply to moves with a single target + let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); + const cancelled = new Utils.BooleanHolder(false); + let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled); + if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) { + success = false; + } else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) { + success = false; + if (failedText === null) { + failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain?.terrainType!); // TODO: is this bang correct? + } + } + + /** + * Trigger pokemon type change before playing the move animation + * Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse, + * regardless of whether the move successfully executes or not. + */ + if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) { + applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); + } + + if (success) { + this.scene.unshiftPhase(this.getEffectPhase()); + } else { + this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); + if (!cancelled.value) { + this.showFailedText(failedText); + } + } + // Checks if Dancer ability is triggered + if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { + // Pokemon with Dancer can be on either side of the battle so we check in both cases + this.scene.getPlayerField().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + }); + this.scene.getEnemyField().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + }); + } + this.end(); + }; + + if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { + this.pokemon.status.incrementTurn(); + let activated = false; + let healed = false; + + switch (this.pokemon.status.effect) { + case StatusEffect.PARALYSIS: + if (!this.pokemon.randSeedInt(4)) { + activated = true; + this.cancelled = true; + } + break; + case StatusEffect.SLEEP: + applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); + healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; + activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); + this.cancelled = activated; + break; + case StatusEffect.FREEZE: + healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); + activated = !healed; + this.cancelled = activated; + break; + } + + if (activated) { + this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); + doMove(); + } else { + if (healed) { + this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); + this.pokemon.resetStatus(); + this.pokemon.updateInfo(); + } + doMove(); + } + } else { + doMove(); + } + } + + getEffectPhase(): MoveEffectPhase { + return new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move); + } + + showMoveText(): void { + if (this.move.getMove().hasAttr(ChargeAttr)) { + const lastMove = this.pokemon.getLastXMoves() as TurnMove[]; + if (!lastMove.length || lastMove[0].move !== this.move.getMove().id || lastMove[0].result !== MoveResult.OTHER) { + this.scene.queueMessage(i18next.t("battle:useMove", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), + moveName: this.move.getName() + }), 500); + return; + } + } + + if (this.pokemon.getTag(BattlerTagType.RECHARGING || BattlerTagType.INTERRUPTED)) { + return; + } + + this.scene.queueMessage(i18next.t("battle:useMove", { + pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), + moveName: this.move.getName() + }), 500); + applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents().find(() => true)!, this.move.getMove()); //TODO: is the bang correct here? + } + + showFailedText(failedText: string | null = null): void { + this.scene.queueMessage(failedText || i18next.t("battle:attackFailed")); + } + + end() { + if (!this.followUp && this.canMove()) { + this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); + } + + super.end(); + } +} diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts new file mode 100644 index 00000000000..5a422c9e6c7 --- /dev/null +++ b/src/phases/new-battle-phase.ts @@ -0,0 +1,11 @@ +import { BattlePhase } from "./battle-phase"; + +export class NewBattlePhase extends BattlePhase { + start() { + super.start(); + + this.scene.newBattle(); + + this.end(); + } +} diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts new file mode 100644 index 00000000000..c447e78f7b1 --- /dev/null +++ b/src/phases/new-biome-encounter-phase.ts @@ -0,0 +1,38 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/ability.js"; +import { getRandomWeatherType } from "#app/data/weather.js"; +import { NextEncounterPhase } from "./next-encounter-phase"; + +export class NewBiomeEncounterPhase extends NextEncounterPhase { + constructor(scene: BattleScene) { + super(scene); + } + + doEncounter(): void { + this.scene.playBgm(undefined, true); + + for (const pokemon of this.scene.getParty()) { + if (pokemon) { + pokemon.resetBattleData(); + } + } + + this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); + + for (const pokemon of this.scene.getParty().filter(p => p.isOnField())) { + applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); + } + + const enemyField = this.scene.getEnemyField(); + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, enemyField].flat(), + x: "+=300", + duration: 2000, + onComplete: () => { + if (!this.tryOverrideForBattleSpec()) { + this.doEncounterCommon(); + } + } + }); + } +} diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts new file mode 100644 index 00000000000..89987534fc0 --- /dev/null +++ b/src/phases/next-encounter-phase.ts @@ -0,0 +1,46 @@ +import BattleScene from "#app/battle-scene.js"; +import { EncounterPhase } from "./encounter-phase"; + +export class NextEncounterPhase extends EncounterPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + } + + doEncounter(): void { + this.scene.playBgm(undefined, true); + + for (const pokemon of this.scene.getParty()) { + if (pokemon) { + pokemon.resetBattleData(); + } + } + + this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType); + this.scene.arenaNextEnemy.setVisible(true); + + const enemyField = this.scene.getEnemyField(); + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer].flat(), + x: "+=300", + duration: 2000, + onComplete: () => { + this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType); + this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); + this.scene.arenaEnemy.setAlpha(1); + this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); + this.scene.arenaNextEnemy.setVisible(false); + if (this.scene.lastEnemyTrainer) { + this.scene.lastEnemyTrainer.destroy(); + } + + if (!this.tryOverrideForBattleSpec()) { + this.doEncounterCommon(); + } + } + }); + } +} diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts new file mode 100644 index 00000000000..ac6e66a2e9f --- /dev/null +++ b/src/phases/obtain-status-effect-phase.ts @@ -0,0 +1,48 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims.js"; +import { getStatusEffectObtainText, getStatusEffectOverlapText } from "#app/data/status-effect.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonPhase } from "./pokemon-phase"; +import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; + +export class ObtainStatusEffectPhase extends PokemonPhase { + private statusEffect: StatusEffect | undefined; + private cureTurn: integer | null; + private sourceText: string | null; + private sourcePokemon: Pokemon | null; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string, sourcePokemon?: Pokemon) { + super(scene, battlerIndex); + + this.statusEffect = statusEffect; + this.cureTurn = cureTurn!; // TODO: is this bang correct? + this.sourceText = sourceText!; // TODO: is this bang correct? + this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct? + } + + start() { + const pokemon = this.getPokemon(); + if (!pokemon?.status) { + if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) { + if (this.cureTurn) { + pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct? + } + pokemon.updateInfo(true); + new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, () => { + this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); + if (pokemon.status?.isPostTurn()) { + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); + } + this.end(); + }); + return; + } + } else if (pokemon.status.effect === this.statusEffect) { + this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon))); + } + this.end(); + } +} diff --git a/src/phases/outdated-phase.ts b/src/phases/outdated-phase.ts new file mode 100644 index 00000000000..72d1bb3671d --- /dev/null +++ b/src/phases/outdated-phase.ts @@ -0,0 +1,13 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; + +export class OutdatedPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + this.scene.ui.setMode(Mode.OUTDATED); + } +} diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts new file mode 100644 index 00000000000..d9179826a19 --- /dev/null +++ b/src/phases/party-heal-phase.ts @@ -0,0 +1,40 @@ +import BattleScene from "#app/battle-scene.js"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; + +export class PartyHealPhase extends BattlePhase { + private resumeBgm: boolean; + + constructor(scene: BattleScene, resumeBgm: boolean) { + super(scene); + + this.resumeBgm = resumeBgm; + } + + start() { + super.start(); + + const bgmPlaying = this.scene.isBgmPlaying(); + if (bgmPlaying) { + this.scene.fadeOutBgm(1000, false); + } + this.scene.ui.fadeOut(1000).then(() => { + for (const pokemon of this.scene.getParty()) { + pokemon.hp = pokemon.getMaxHp(); + pokemon.resetStatus(); + for (const move of pokemon.moveset) { + move!.ppUsed = 0; // TODO: is this bang correct? + } + pokemon.updateInfo(true); + } + const healSong = this.scene.playSoundWithoutBgm("heal"); + this.scene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { + healSong.destroy(); + if (this.resumeBgm && bgmPlaying) { + this.scene.playBgm(); + } + this.scene.ui.fadeIn(500).then(() => this.end()); + }); + }); + } +} diff --git a/src/phases/party-member-pokemon-phase.ts b/src/phases/party-member-pokemon-phase.ts new file mode 100644 index 00000000000..1f27826884e --- /dev/null +++ b/src/phases/party-member-pokemon-phase.ts @@ -0,0 +1,27 @@ +import BattleScene from "#app/battle-scene.js"; +import Pokemon from "#app/field/pokemon.js"; +import { FieldPhase } from "./field-phase"; + +export abstract class PartyMemberPokemonPhase extends FieldPhase { + protected partyMemberIndex: integer; + protected fieldIndex: integer; + protected player: boolean; + + constructor(scene: BattleScene, partyMemberIndex: integer, player: boolean) { + super(scene); + + this.partyMemberIndex = partyMemberIndex; + this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount() + ? partyMemberIndex + : -1; + this.player = player; + } + + getParty(): Pokemon[] { + return this.player ? this.scene.getParty() : this.scene.getEnemyParty(); + } + + getPokemon(): Pokemon { + return this.getParty()[this.partyMemberIndex]; + } +} diff --git a/src/phases/party-status-cure-phase.ts b/src/phases/party-status-cure-phase.ts new file mode 100644 index 00000000000..a11aa01b63a --- /dev/null +++ b/src/phases/party-status-cure-phase.ts @@ -0,0 +1,48 @@ +import BattleScene from "#app/battle-scene.js"; +import { Abilities } from "#app/enums/abilities.js"; +import Pokemon from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; +import { ShowAbilityPhase } from "./show-ability-phase"; + +/** + * Cures the party of all non-volatile status conditions, shows a message + * @param {BattleScene} scene The current scene + * @param {Pokemon} user The user of the move that cures the party + * @param {string} message The message that should be displayed + * @param {Abilities} abilityCondition Pokemon with this ability will not be affected ie. Soundproof + */ +export class PartyStatusCurePhase extends BattlePhase { + private user: Pokemon; + private message: string; + private abilityCondition: Abilities; + + constructor(scene: BattleScene, user: Pokemon, message: string, abilityCondition: Abilities) { + super(scene); + + this.user = user; + this.message = message; + this.abilityCondition = abilityCondition; + } + + start() { + super.start(); + for (const pokemon of this.scene.getParty()) { + if (!pokemon.isOnField() || pokemon === this.user) { + pokemon.resetStatus(false); + pokemon.updateInfo(true); + } else { + if (!pokemon.hasAbility(this.abilityCondition)) { + pokemon.resetStatus(); + pokemon.updateInfo(true); + } else { + // Manually show ability bar, since we're not hooked into the targeting system + pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); + } + } + } + if (this.message) { + this.scene.queueMessage(this.message); + } + this.end(); + } +} diff --git a/src/phases/player-party-member-pokemon-phase.ts b/src/phases/player-party-member-pokemon-phase.ts new file mode 100644 index 00000000000..4b1600b33d2 --- /dev/null +++ b/src/phases/player-party-member-pokemon-phase.ts @@ -0,0 +1,13 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; + +export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { + constructor(scene: BattleScene, partyMemberIndex: integer) { + super(scene, partyMemberIndex, true); + } + + getPlayerPokemon(): PlayerPokemon { + return super.getPokemon() as PlayerPokemon; + } +} diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts new file mode 100644 index 00000000000..6db8aeb4fca --- /dev/null +++ b/src/phases/pokemon-heal-phase.ts @@ -0,0 +1,104 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { getStatusEffectHealText } from "#app/data/status-effect.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { HitResult, DamageResult } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { HealingBoosterModifier } from "#app/modifier/modifier.js"; +import { HealAchv } from "#app/system/achv.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { CommonAnimPhase } from "./common-anim-phase"; + +export class PokemonHealPhase extends CommonAnimPhase { + private hpHealed: integer; + private message: string | null; + private showFullHpMessage: boolean; + private skipAnim: boolean; + private revive: boolean; + private healStatus: boolean; + private preventFullHeal: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false) { + super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); + + this.hpHealed = hpHealed; + this.message = message; + this.showFullHpMessage = showFullHpMessage; + this.skipAnim = skipAnim; + this.revive = revive; + this.healStatus = healStatus; + this.preventFullHeal = preventFullHeal; + } + + start() { + if (!this.skipAnim && (this.revive || this.getPokemon().hp) && !this.getPokemon().isFullHp()) { + super.start(); + } else { + this.end(); + } + } + + end() { + const pokemon = this.getPokemon(); + + if (!pokemon.isOnField() || (!this.revive && !pokemon.isActive())) { + super.end(); + return; + } + + const hasMessage = !!this.message; + const healOrDamage = (!pokemon.isFullHp() || this.hpHealed < 0); + let lastStatusEffect = StatusEffect.NONE; + + if (healOrDamage) { + const hpRestoreMultiplier = new Utils.IntegerHolder(1); + if (!this.revive) { + this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); + } + const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); + if (healAmount.value < 0) { + pokemon.damageAndUpdate(healAmount.value * -1, HitResult.HEAL as DamageResult); + healAmount.value = 0; + } + // Prevent healing to full if specified (in case of healing tokens so Sturdy doesn't cause a softlock) + if (this.preventFullHeal && pokemon.hp + healAmount.value >= pokemon.getMaxHp()) { + healAmount.value = (pokemon.getMaxHp() - pokemon.hp) - 1; + } + healAmount.value = pokemon.heal(healAmount.value); + if (healAmount.value) { + this.scene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL); + } + if (pokemon.isPlayer()) { + this.scene.validateAchvs(HealAchv, healAmount); + if (healAmount.value > this.scene.gameData.gameStats.highestHeal) { + this.scene.gameData.gameStats.highestHeal = healAmount.value; + } + } + if (this.healStatus && !this.revive && pokemon.status) { + lastStatusEffect = pokemon.status.effect; + pokemon.resetStatus(); + } + pokemon.updateInfo().then(() => super.end()); + } else if (this.healStatus && !this.revive && pokemon.status) { + lastStatusEffect = pokemon.status.effect; + pokemon.resetStatus(); + pokemon.updateInfo().then(() => super.end()); + } else if (this.showFullHpMessage) { + this.message = i18next.t("battle:hpIsFull", { pokemonName: getPokemonNameWithAffix(pokemon) }); + } + + if (this.message) { + this.scene.queueMessage(this.message); + } + + if (this.healStatus && lastStatusEffect && !hasMessage) { + this.scene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); + } + + if (!healOrDamage && !lastStatusEffect) { + super.end(); + } + } +} diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts new file mode 100644 index 00000000000..871ee57d7a5 --- /dev/null +++ b/src/phases/pokemon-phase.ts @@ -0,0 +1,29 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import Pokemon from "#app/field/pokemon.js"; +import { FieldPhase } from "./field-phase"; + +export abstract class PokemonPhase extends FieldPhase { + protected battlerIndex: BattlerIndex | integer; + public player: boolean; + public fieldIndex: integer; + + constructor(scene: BattleScene, battlerIndex?: BattlerIndex | integer) { + super(scene); + + if (battlerIndex === undefined) { + battlerIndex = scene.getField().find(p => p?.isActive())!.getBattlerIndex(); // TODO: is the bang correct here? + } + + this.battlerIndex = battlerIndex; + this.player = battlerIndex < 2; + this.fieldIndex = battlerIndex % 2; + } + + getPokemon(): Pokemon { + if (this.battlerIndex > BattlerIndex.ENEMY_2) { + return this.scene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? + } + return this.scene.getField()[this.battlerIndex]!; //TODO: is this bang correct? + } +} diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts new file mode 100644 index 00000000000..02413b41a23 --- /dev/null +++ b/src/phases/post-game-over-phase.ts @@ -0,0 +1,46 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { EndCardPhase } from "./end-card-phase"; +import { TitlePhase } from "./title-phase"; + +export class PostGameOverPhase extends Phase { + private endCardPhase: EndCardPhase | null; + + constructor(scene: BattleScene, endCardPhase?: EndCardPhase) { + super(scene); + + this.endCardPhase = endCardPhase!; // TODO: is this bang correct? + } + + start() { + super.start(); + + const saveAndReset = () => { + this.scene.gameData.saveAll(this.scene, true, true, true).then(success => { + if (!success) { + return this.scene.reset(true); + } + this.scene.gameData.tryClearSession(this.scene, this.scene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { + if (!success[0]) { + return this.scene.reset(true); + } + this.scene.reset(); + this.scene.unshiftPhase(new TitlePhase(this.scene)); + this.end(); + }); + }); + }; + + if (this.endCardPhase) { + this.scene.ui.fadeOut(500).then(() => { + this.scene.ui.getMessageHandler().bg.setVisible(true); + + this.endCardPhase?.endCard.destroy(); + this.endCardPhase?.text.destroy(); + saveAndReset(); + }); + } else { + saveAndReset(); + } + } +} diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts new file mode 100644 index 00000000000..e671bf30ed1 --- /dev/null +++ b/src/phases/post-summon-phase.ts @@ -0,0 +1,24 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyPostSummonAbAttrs, PostSummonAbAttr } from "#app/data/ability.js"; +import { ArenaTrapTag } from "#app/data/arena-tag.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class PostSummonPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + + if (pokemon.status?.effect === StatusEffect.TOXIC) { + pokemon.status.turnCount = 0; + } + this.scene.arena.applyTags(ArenaTrapTag, pokemon); + applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); + } +} diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts new file mode 100644 index 00000000000..8b533f2e90a --- /dev/null +++ b/src/phases/post-turn-status-effect-phase.ts @@ -0,0 +1,61 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability.js"; +import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims.js"; +import { getStatusEffectActivationText } from "#app/data/status-effect.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import { StatusEffect } from "#app/enums/status-effect.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class PostTurnStatusEffectPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + const pokemon = this.getPokemon(); + if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { + pokemon.status.incrementTurn(); + const cancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); + + if (!cancelled.value) { + this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + const damage = new Utils.NumberHolder(0); + switch (pokemon.status.effect) { + case StatusEffect.POISON: + damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); + break; + case StatusEffect.TOXIC: + damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + break; + case StatusEffect.BURN: + damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); + applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, damage); + break; + } + if (damage.value) { + // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... + this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); + pokemon.updateInfo(); + } + new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => this.end()); + } else { + this.end(); + } + } else { + this.end(); + } + } + + override end() { + if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + this.scene.initFinalBossPhaseTwo(this.getPokemon()); + } else { + super.end(); + } + } +} diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts new file mode 100644 index 00000000000..3d30d36907e --- /dev/null +++ b/src/phases/quiet-form-change-phase.ts @@ -0,0 +1,133 @@ +import BattleScene from "#app/battle-scene.js"; +import { SemiInvulnerableTag } from "#app/data/battler-tags.js"; +import { SpeciesFormChange, getSpeciesFormChangeMessage } from "#app/data/pokemon-forms.js"; +import { getTypeRgb } from "#app/data/type.js"; +import { BattleSpec } from "#app/enums/battle-spec.js"; +import Pokemon, { EnemyPokemon } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { BattlePhase } from "./battle-phase"; +import { MovePhase } from "./move-phase"; +import { PokemonHealPhase } from "./pokemon-heal-phase"; + +export class QuietFormChangePhase extends BattlePhase { + protected pokemon: Pokemon; + protected formChange: SpeciesFormChange; + + constructor(scene: BattleScene, pokemon: Pokemon, formChange: SpeciesFormChange) { + super(scene); + this.pokemon = pokemon; + this.formChange = formChange; + } + + start(): void { + super.start(); + + if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) { + return this.end(); + } + + const preName = getPokemonNameWithAffix(this.pokemon); + + if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) { + this.pokemon.changeForm(this.formChange).then(() => { + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + }); + return; + } + + const getPokemonSprite = () => { + const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); + sprite.setOrigin(0.5, 1); + sprite.play(this.pokemon.getBattleSpriteKey()).stop(); + sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); + [ "spriteColors", "fusionSpriteColors" ].map(k => { + if (this.pokemon.summonData?.speciesForm) { + k += "Base"; + } + sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; + }); + this.scene.field.add(sprite); + return sprite; + }; + + const [ pokemonTintSprite, pokemonFormTintSprite ] = [ getPokemonSprite(), getPokemonSprite() ]; + + this.pokemon.getSprite().on("animationupdate", (_anim, frame) => { + if (frame.textureKey === pokemonTintSprite.texture.key) { + pokemonTintSprite.setFrame(frame.textureFrame); + } else { + pokemonFormTintSprite.setFrame(frame.textureFrame); + } + }); + + pokemonTintSprite.setAlpha(0); + pokemonTintSprite.setTintFill(0xFFFFFF); + pokemonFormTintSprite.setVisible(false); + pokemonFormTintSprite.setTintFill(0xFFFFFF); + + this.scene.playSound("PRSFX- Transform"); + + this.scene.tweens.add({ + targets: pokemonTintSprite, + alpha: 1, + duration: 1000, + ease: "Cubic.easeIn", + onComplete: () => { + this.pokemon.setVisible(false); + this.pokemon.changeForm(this.formChange).then(() => { + pokemonFormTintSprite.setScale(0.01); + pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop(); + pokemonFormTintSprite.setVisible(true); + this.scene.tweens.add({ + targets: pokemonTintSprite, + delay: 250, + scale: 0.01, + ease: "Cubic.easeInOut", + duration: 500, + onComplete: () => pokemonTintSprite.destroy() + }); + this.scene.tweens.add({ + targets: pokemonFormTintSprite, + delay: 250, + scale: this.pokemon.getSpriteScale(), + ease: "Cubic.easeInOut", + duration: 500, + onComplete: () => { + this.pokemon.setVisible(true); + this.scene.tweens.add({ + targets: pokemonFormTintSprite, + delay: 250, + alpha: 0, + ease: "Cubic.easeOut", + duration: 1000, + onComplete: () => { + pokemonTintSprite.setVisible(false); + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + } + }); + } + }); + }); + } + }); + } + + end(): void { + if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { + this.scene.playBgm(); + this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); + this.pokemon.findAndRemoveTags(() => true); + this.pokemon.bossSegments = 5; + this.pokemon.bossSegmentIndex = 4; + this.pokemon.initBattleInfo(); + this.pokemon.cry(); + + const movePhase = this.scene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; + if (movePhase) { + movePhase.cancel(); + } + } + + super.end(); + } +} diff --git a/src/phases/reload-session-phase.ts b/src/phases/reload-session-phase.ts new file mode 100644 index 00000000000..a61c52323bf --- /dev/null +++ b/src/phases/reload-session-phase.ts @@ -0,0 +1,39 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; +import * as Utils from "#app/utils.js"; + +export class ReloadSessionPhase extends Phase { + private systemDataStr: string | null; + + constructor(scene: BattleScene, systemDataStr?: string) { + super(scene); + + this.systemDataStr = systemDataStr ?? null; + } + + start(): void { + this.scene.ui.setMode(Mode.SESSION_RELOAD); + + let delayElapsed = false; + let loaded = false; + + this.scene.time.delayedCall(Utils.fixedInt(1500), () => { + if (loaded) { + this.end(); + } else { + delayElapsed = true; + } + }); + + this.scene.gameData.clearLocalData(); + + (this.systemDataStr ? this.scene.gameData.initSystem(this.systemDataStr) : this.scene.gameData.loadSystem()).then(() => { + if (delayElapsed) { + this.end(); + } else { + loaded = true; + } + }); + } +} diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts new file mode 100644 index 00000000000..e1753670ad4 --- /dev/null +++ b/src/phases/return-phase.ts @@ -0,0 +1,26 @@ +import BattleScene from "#app/battle-scene.js"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms.js"; +import { SwitchSummonPhase } from "./switch-summon-phase"; + +export class ReturnPhase extends SwitchSummonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex, -1, true, false); + } + + switchAndSummon(): void { + this.end(); + } + + summon(): void { } + + onEnd(): void { + const pokemon = this.getPokemon(); + + pokemon.resetTurnData(); + pokemon.resetSummonData(); + + this.scene.updateFieldScale(); + + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); + } +} diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts new file mode 100644 index 00000000000..4a80325b7e7 --- /dev/null +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -0,0 +1,33 @@ +import BattleScene from "#app/battle-scene.js"; +import PokemonSpecies from "#app/data/pokemon-species.js"; +import { ModifierTypeFunc } from "#app/modifier/modifier-type.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; + +export class RibbonModifierRewardPhase extends ModifierRewardPhase { + private species: PokemonSpecies; + + constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { + super(scene, modifierTypeFunc); + + this.species = species; + } + + doReward(): Promise { + return new Promise(resolve => { + const newModifier = this.modifierType.newModifier(); + this.scene.addModifier(newModifier).then(() => { + this.scene.playSound("level_up_fanfare"); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:beatModeFirstTime", { + speciesName: this.species.name, + gameMode: this.scene.gameMode.getName(), + newModifier: newModifier?.type.name + }), null, () => { + resolve(); + }, null, true, 1500); + }); + }); + } +} diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts new file mode 100644 index 00000000000..f5e1a814612 --- /dev/null +++ b/src/phases/scan-ivs-phase.ts @@ -0,0 +1,69 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims.js"; +import { Stat } from "#app/enums/stat.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { getTextColor, TextStyle } from "#app/ui/text.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import { PokemonPhase } from "./pokemon-phase"; + +export class ScanIvsPhase extends PokemonPhase { + private shownIvs: integer; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, shownIvs: integer) { + super(scene, battlerIndex); + + this.shownIvs = shownIvs; + } + + start() { + super.start(); + + if (!this.shownIvs) { + return this.end(); + } + + const pokemon = this.getPokemon(); + + let enemyIvs: number[] = []; + let statsContainer: Phaser.GameObjects.Sprite[] = []; + let statsContainerLabels: Phaser.GameObjects.Sprite[] = []; + const enemyField = this.scene.getEnemyField(); + const uiTheme = (this.scene as BattleScene).uiTheme; // Assuming uiTheme is accessible + for (let e = 0; e < enemyField.length; e++) { + enemyIvs = enemyField[e].ivs; + const currentIvs = this.scene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists + const ivsToShow = this.scene.ui.getMessageHandler().getTopIvs(enemyIvs, this.shownIvs); + statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; + statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); + for (let s = 0; s < statsContainerLabels.length; s++) { + const ivStat = Stat[statsContainerLabels[s].frame.name]; + if (enemyIvs[ivStat] > currentIvs[ivStat] && ivsToShow.indexOf(Number(ivStat)) >= 0) { + const hexColour = enemyIvs[ivStat] === 31 ? getTextColor(TextStyle.PERFECT_IV, false, uiTheme) : getTextColor(TextStyle.SUMMARY_GREEN, false, uiTheme); + const hexTextColour = Phaser.Display.Color.HexStringToColor(hexColour).color; + statsContainerLabels[s].setTint(hexTextColour); + } + statsContainerLabels[s].setVisible(true); + } + } + + if (!this.scene.hideIvs) { + this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + this.scene.ui.setMode(Mode.CONFIRM, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { + this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); + }); + }, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + }); + }); + } else { + this.end(); + } + } +} diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts new file mode 100644 index 00000000000..68c2cd29f26 --- /dev/null +++ b/src/phases/select-biome-phase.ts @@ -0,0 +1,84 @@ +import BattleScene from "#app/battle-scene.js"; +import { biomeLinks, getBiomeName } from "#app/data/biomes.js"; +import { Biome } from "#app/enums/biome.js"; +import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier.js"; +import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import { BattlePhase } from "./battle-phase"; +import * as Utils from "#app/utils.js"; +import { PartyHealPhase } from "./party-heal-phase"; +import { SwitchBiomePhase } from "./switch-biome-phase"; + +export class SelectBiomePhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const currentBiome = this.scene.arena.biomeType; + + const setNextBiome = (nextBiome: Biome) => { + if (this.scene.currentBattle.waveIndex % 10 === 1) { + this.scene.applyModifiers(MoneyInterestModifier, true, this.scene); + this.scene.unshiftPhase(new PartyHealPhase(this.scene, false)); + } + this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); + this.end(); + }; + + if ((this.scene.gameMode.isClassic && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex + 9)) + || (this.scene.gameMode.isDaily && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) + || (this.scene.gameMode.hasShortBiomes && !(this.scene.currentBattle.waveIndex % 50))) { + setNextBiome(Biome.END); + } else if (this.scene.gameMode.hasRandomBiomes) { + setNextBiome(this.generateNextBiome()); + } else if (Array.isArray(biomeLinks[currentBiome])) { + let biomes: Biome[] = []; + this.scene.executeWithSeedOffset(() => { + biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) + .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) + .map(b => !Array.isArray(b) ? b : b[0]); + }, this.scene.currentBattle.waveIndex); + if (biomes.length > 1 && this.scene.findModifier(m => m instanceof MapModifier)) { + let biomeChoices: Biome[] = []; + this.scene.executeWithSeedOffset(() => { + biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) + ? [biomeLinks[currentBiome] as Biome] + : biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) + .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) + .map(b => Array.isArray(b) ? b[0] : b); + }, this.scene.currentBattle.waveIndex); + const biomeSelectItems = biomeChoices.map(b => { + const ret: OptionSelectItem = { + label: getBiomeName(b), + handler: () => { + this.scene.ui.setMode(Mode.MESSAGE); + setNextBiome(b); + return true; + } + }; + return ret; + }); + this.scene.ui.setMode(Mode.OPTION_SELECT, { + options: biomeSelectItems, + delay: 1000 + }); + } else { + setNextBiome(biomes[Utils.randSeedInt(biomes.length)]); + } + } else if (biomeLinks.hasOwnProperty(currentBiome)) { + setNextBiome(biomeLinks[currentBiome] as Biome); + } else { + setNextBiome(this.generateNextBiome()); + } + } + + generateNextBiome(): Biome { + if (!(this.scene.currentBattle.waveIndex % 50)) { + return Biome.END; + } + return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); + } +} diff --git a/src/phases/select-challenge-phase.ts b/src/phases/select-challenge-phase.ts new file mode 100644 index 00000000000..eaf830e0059 --- /dev/null +++ b/src/phases/select-challenge-phase.ts @@ -0,0 +1,17 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; + +export class SelectChallengePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.playBgm("menu"); + + this.scene.ui.setMode(Mode.CHALLENGE_SELECT); + } +} diff --git a/src/phases/select-gender-phase.ts b/src/phases/select-gender-phase.ts new file mode 100644 index 00000000000..3fc6916e233 --- /dev/null +++ b/src/phases/select-gender-phase.ts @@ -0,0 +1,46 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { Phase } from "#app/phase.js"; +import { SettingKeys } from "#app/system/settings/settings.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; + +export class SelectGenderPhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + super.start(); + + this.scene.ui.showText(i18next.t("menu:boyOrGirl"), null, () => { + this.scene.ui.setMode(Mode.OPTION_SELECT, { + options: [ + { + label: i18next.t("settings:boy"), + handler: () => { + this.scene.gameData.gender = PlayerGender.MALE; + this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 0); + this.scene.gameData.saveSystem().then(() => this.end()); + return true; + } + }, + { + label: i18next.t("settings:girl"), + handler: () => { + this.scene.gameData.gender = PlayerGender.FEMALE; + this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 1); + this.scene.gameData.saveSystem().then(() => this.end()); + return true; + } + } + ] + }); + }); + } + + end(): void { + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + } +} diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts new file mode 100644 index 00000000000..67ae904fb58 --- /dev/null +++ b/src/phases/select-modifier-phase.ts @@ -0,0 +1,234 @@ +import BattleScene from "#app/battle-scene.js"; +import { ModifierTier } from "#app/modifier/modifier-tier.js"; +import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type.js"; +import { ExtraModifierModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier.js"; +import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler.js"; +import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import Overrides from "#app/overrides"; + +export class SelectModifierPhase extends BattlePhase { + private rerollCount: integer; + private modifierTiers: ModifierTier[]; + + constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[]) { + super(scene); + + this.rerollCount = rerollCount; + this.modifierTiers = modifierTiers!; // TODO: is this bang correct? + } + + start() { + super.start(); + + if (!this.rerollCount) { + this.updateSeed(); + } else { + this.scene.reroll = false; + } + + const party = this.scene.getParty(); + regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + const modifierCount = new Utils.IntegerHolder(3); + if (this.isPlayer()) { + this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); + } + const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); + + const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { + if (rowCursor < 0 || cursor < 0) { + this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { + this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + this.scene.ui.revertMode(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers))); + }); + return false; + } + let modifierType: ModifierType; + let cost: integer; + switch (rowCursor) { + case 0: + switch (cursor) { + case 0: + const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); + if (this.scene.money < rerollCost) { + this.scene.ui.playError(); + return false; + } else { + this.scene.reroll = true; + this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= rerollCost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("buy"); + } + break; + case 1: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && m.isTransferrable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; + const itemModifier = itemModifiers[itemIndex]; + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + }, PartyUiHandler.FilterItemMaxStacks); + break; + case 2: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + }); + break; + case 3: + this.scene.lockModifierTiers = !this.scene.lockModifierTiers; + const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; + } + return true; + case 1: + if (typeOptions[cursor].type) { + modifierType = typeOptions[cursor].type; + } + break; + default: + const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; + if (shopOption.type) { + modifierType = shopOption.type; + } + cost = shopOption.cost; + break; + } + + if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? + this.scene.ui.playError(); + return false; + } + + const applyModifier = (modifier: Modifier, playSound: boolean = false) => { + const result = this.scene.addModifier(modifier, false, playSound); + if (cost) { + result.then(success => { + if (success) { + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= cost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("buy"); + (this.scene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); + } else { + this.scene.ui.playError(); + } + }); + } else { + const doEnd = () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + }; + if (result instanceof Promise) { + result.then(() => doEnd()); + } else { + doEnd(); + } + } + }; + + if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? + if (modifierType instanceof FusePokemonModifierType) { + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { + if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? + applyModifier(modifier, true); + }); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + }, modifierType.selectFilter); + } else { + const pokemonModifierType = modifierType as PokemonModifierType; + const isMoveModifier = modifierType instanceof PokemonMoveModifierType; + const isTmModifier = modifierType instanceof TmModifierType; + const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType; + const isPpRestoreModifier = (modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType); + const partyUiMode = isMoveModifier ? PartyUiMode.MOVE_MODIFIER + : isTmModifier ? PartyUiMode.TM_MODIFIER + : isRememberMoveModifier ? PartyUiMode.REMEMBER_MOVE_MODIFIER + : PartyUiMode.MODIFIER; + const tmMoveId = isTmModifier + ? (modifierType as TmModifierType).moveId + : undefined; + this.scene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { + if (slotIndex < 6) { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { + const modifier = !isMoveModifier + ? !isRememberMoveModifier + ? modifierType.newModifier(party[slotIndex]) + : modifierType.newModifier(party[slotIndex], option as integer) + : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + applyModifier(modifier!, true); // TODO: is the bang correct? + }); + } else { + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); + } + } else { + applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct? + } + + return !cost!;// TODO: is the bang correct? + }; + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + } + + updateSeed(): void { + this.scene.resetSeed(); + } + + isPlayer(): boolean { + return true; + } + + getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer { + let baseValue = 0; + if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + return baseValue; + } else if (lockRarities) { + const tierValues = [50, 125, 300, 750, 2000]; + for (const opt of typeOptions) { + baseValue += tierValues[opt.type.tier ?? 0]; + } + } else { + baseValue = 250; + } + return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount), Number.MAX_SAFE_INTEGER); + } + + getPoolType(): ModifierPoolType { + return ModifierPoolType.PLAYER; + } + + getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { + return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined); + } + + addModifier(modifier: Modifier): Promise { + return this.scene.addModifier(modifier, false, true); + } +} diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts new file mode 100644 index 00000000000..ad972a49225 --- /dev/null +++ b/src/phases/select-starter-phase.ts @@ -0,0 +1,112 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; +import { Gender } from "#app/data/gender.js"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms.js"; +import { getPokemonSpecies } from "#app/data/pokemon-species.js"; +import { Species } from "#app/enums/species.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import { overrideModifiers, overrideHeldItems } from "#app/modifier/modifier.js"; +import { Phase } from "#app/phase.js"; +import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler.js"; +import { Starter } from "#app/ui/starter-select-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; +import { TitlePhase } from "./title-phase"; +import Overrides from "#app/overrides"; + +export class SelectStarterPhase extends Phase { + + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.playBgm("menu"); + + this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + if (slotId === -1) { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + return this.end(); + } + this.scene.sessionSlotId = slotId; + this.initBattle(starters); + }); + }); + } + + /** + * Initialize starters before starting the first battle + * @param starters {@linkcode Pokemon} with which to start the first battle + */ + initBattle(starters: Starter[]) { + const party = this.scene.getParty(); + const loadPokemonAssets: Promise[] = []; + starters.forEach((starter: Starter, i: integer) => { + if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { + starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); + } + const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + if ( + starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && + starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!] + ) { + starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]!; + } + + let starterGender = starter.species.malePercent !== null + ? !starterProps.female ? Gender.MALE : Gender.FEMALE + : Gender.GENDERLESS; + if (Overrides.GENDER_OVERRIDE !== null) { + starterGender = Overrides.GENDER_OVERRIDE; + } + const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); + const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); + starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); + if (starter.passive) { + starterPokemon.passive = true; + } + starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); + if (starter.pokerus) { + starterPokemon.pokerus = true; + } + + if (starter.nickname) { + starterPokemon.nickname = starter.nickname; + } + + if (this.scene.gameMode.isSplicedOnly) { + starterPokemon.generateFusionSpecies(true); + } + starterPokemon.setVisible(false); + applyChallenges(this.scene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); + party.push(starterPokemon); + loadPokemonAssets.push(starterPokemon.loadAssets()); + }); + overrideModifiers(this.scene); + overrideHeldItems(this.scene, party[0]); + Promise.all(loadPokemonAssets).then(() => { + SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); + this.scene.time.delayedCall(500, () => this.scene.playBgm()); + if (this.scene.gameMode.isClassic) { + this.scene.gameData.gameStats.classicSessionsPlayed++; + } else { + this.scene.gameData.gameStats.endlessSessionsPlayed++; + } + this.scene.newBattle(); + this.scene.arena.init(); + this.scene.sessionPlayTime = 0; + this.scene.lastSavePlayTime = 0; + // Ensures Keldeo (or any future Pokemon that have this type of form change) starts in the correct form + this.scene.getParty().forEach((p: PlayerPokemon) => { + this.scene.triggerPokemonFormChange(p, SpeciesFormChangeMoveLearnedTrigger); + }); + this.end(); + }); + } +} diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts new file mode 100644 index 00000000000..fe72335e312 --- /dev/null +++ b/src/phases/select-target-phase.ts @@ -0,0 +1,32 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import { CommandPhase } from "./command-phase"; +import { PokemonPhase } from "./pokemon-phase"; + +export class SelectTargetPhase extends PokemonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex); + } + + start() { + super.start(); + + const turnCommand = this.scene.currentBattle.turnCommands[this.fieldIndex]; + const move = turnCommand?.move?.move; + this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => { + this.scene.ui.setMode(Mode.MESSAGE); + if (targets.length < 1) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = null; + this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); + } else { + turnCommand!.targets = targets; //TODO: is the bang correct here? + } + if (turnCommand?.command === Command.BALL && this.fieldIndex) { + this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; //TODO: is the bang correct here? + } + this.end(); + }); + } +} diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts new file mode 100644 index 00000000000..4cd2b68f881 --- /dev/null +++ b/src/phases/shiny-sparkle-phase.ts @@ -0,0 +1,16 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class ShinySparklePhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + this.getPokemon().sparkle(); + this.scene.time.delayedCall(1000, () => this.end()); + } +} diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts new file mode 100644 index 00000000000..ee0b98f7886 --- /dev/null +++ b/src/phases/show-ability-phase.ts @@ -0,0 +1,29 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export class ShowAbilityPhase extends PokemonPhase { + private passive: boolean; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, passive: boolean = false) { + super(scene, battlerIndex); + + this.passive = passive; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + + if (pokemon) { + this.scene.abilityBar.showAbility(pokemon, this.passive); + + if (pokemon?.battleData) { + pokemon.battleData.abilityRevealed = true; + } + } + + this.end(); + } +} diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts new file mode 100644 index 00000000000..9920472e801 --- /dev/null +++ b/src/phases/show-party-exp-bar-phase.ts @@ -0,0 +1,56 @@ +import BattleScene from "#app/battle-scene.js"; +import { ExpNotification } from "#app/enums/exp-notification.js"; +import { ExpBoosterModifier } from "#app/modifier/modifier.js"; +import * as Utils from "#app/utils.js"; +import { HidePartyExpBarPhase } from "./hide-party-exp-bar-phase"; +import { LevelUpPhase } from "./level-up-phase"; +import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; + +export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { + private expValue: number; + + constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { + super(scene, partyMemberIndex); + + this.expValue = expValue; + } + + start() { + super.start(); + + const pokemon = this.getPokemon(); + const exp = new Utils.NumberHolder(this.expValue); + this.scene.applyModifiers(ExpBoosterModifier, true, exp); + exp.value = Math.floor(exp.value); + + const lastLevel = pokemon.level; + pokemon.addExp(exp.value); + const newLevel = pokemon.level; + if (newLevel > lastLevel) { + this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + } + this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); + pokemon.updateInfo(); + + if (this.scene.expParty === ExpNotification.SKIP) { + this.end(); + } else if (this.scene.expParty === ExpNotification.ONLY_LEVEL_UP) { + if (newLevel > lastLevel) { // this means if we level up + // instead of displaying the exp gain in the small frame, we display the new level + // we use the same method for mode 0 & 1, by giving a parameter saying to display the exp or the level + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { + setTimeout(() => this.end(), 800 / Math.pow(2, this.scene.expGainsSpeed)); + }); + } else { + this.end(); + } + } else if (this.scene.expGainsSpeed < 3) { + this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { + setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); + }); + } else { + this.end(); + } + + } +} diff --git a/src/phases/show-trainer-phase.ts b/src/phases/show-trainer-phase.ts new file mode 100644 index 00000000000..8a869f582d8 --- /dev/null +++ b/src/phases/show-trainer-phase.ts @@ -0,0 +1,24 @@ +import BattleScene from "#app/battle-scene.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { BattlePhase } from "./battle-phase"; + +export class ShowTrainerPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.trainer.setVisible(true); + + this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + + this.scene.tweens.add({ + targets: this.scene.trainer, + x: 106, + duration: 1000, + onComplete: () => this.end() + }); + } +} diff --git a/src/phases/stat-change-phase.ts b/src/phases/stat-change-phase.ts new file mode 100644 index 00000000000..3469cc62942 --- /dev/null +++ b/src/phases/stat-change-phase.ts @@ -0,0 +1,234 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { applyPreStatChangeAbAttrs, ProtectStatAbAttr, applyAbAttrs, StatChangeMultiplierAbAttr, StatChangeCopyAbAttr, applyPostStatChangeAbAttrs, PostStatChangeAbAttr } from "#app/data/ability.js"; +import { MistTag, ArenaTagSide } from "#app/data/arena-tag.js"; +import { BattleStat, getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier.js"; +import { handleTutorial, Tutorial } from "#app/tutorial.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { PokemonPhase } from "./pokemon-phase"; + +export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; + +export class StatChangePhase extends PokemonPhase { + private stats: BattleStat[]; + private selfTarget: boolean; + private levels: integer; + private showMessage: boolean; + private ignoreAbilities: boolean; + private canBeCopied: boolean; + private onChange: StatChangeCallback | null; + + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatChangeCallback | null = null) { + super(scene, battlerIndex); + + this.selfTarget = selfTarget; + this.stats = stats; + this.levels = levels; + this.showMessage = showMessage; + this.ignoreAbilities = ignoreAbilities; + this.canBeCopied = canBeCopied; + this.onChange = onChange; + } + + start() { + const pokemon = this.getPokemon(); + + let random = false; + + if (this.stats.length === 1 && this.stats[0] === BattleStat.RAND) { + this.stats[0] = this.getRandomStat(); + random = true; + } + + this.aggregateStatChanges(random); + + if (!pokemon.isActive(true)) { + return this.end(); + } + + const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => { + const cancelled = new Utils.BooleanHolder(false); + + if (!this.selfTarget && this.levels < 0) { + this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled); + } + + if (!cancelled.value && !this.selfTarget && this.levels < 0) { + applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); + } + + return !cancelled.value; + }); + + const levels = new Utils.IntegerHolder(this.levels); + + if (!this.ignoreAbilities) { + applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); + } + + const battleStats = this.getPokemon().summonData.battleStats; + const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]); + + this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels); + + const end = () => { + if (this.showMessage) { + const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels); + for (const message of messages) { + this.scene.queueMessage(message); + } + } + + for (const stat of filteredStats) { + pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); + } + + if (levels.value > 0 && this.canBeCopied) { + for (const opponent of pokemon.getOpponents()) { + applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value); + } + } + + applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); + + // Look for any other stat change phases; if this is the last one, do White Herb check + const existingPhase = this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex); + if (!(existingPhase instanceof StatChangePhase)) { + // Apply White Herb if needed + const whiteHerb = this.scene.applyModifier(PokemonResetNegativeStatStageModifier, this.player, pokemon) as PokemonResetNegativeStatStageModifier; + // If the White Herb was applied, consume it + if (whiteHerb) { + --whiteHerb.stackCount; + if (whiteHerb.stackCount <= 0) { + this.scene.removeModifier(whiteHerb); + } + this.scene.updateModifiers(this.player); + } + } + + pokemon.updateInfo(); + + handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end()); + }; + + if (relLevels.filter(l => l).length && this.scene.moveAnimations) { + pokemon.enableMask(); + const pokemonMaskSprite = pokemon.maskSprite; + + const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale; + const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; + const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale(); + const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale(); + + // On increase, show the red sprite located at ATK + // On decrease, show the blue sprite located at SPD + const spriteColor = levels.value >= 1 ? BattleStat[BattleStat.ATK].toLowerCase() : BattleStat[BattleStat.SPD].toLowerCase(); + const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); + statSprite.setPipeline(this.scene.fieldSpritePipeline); + statSprite.setAlpha(0); + statSprite.setScale(6); + statSprite.setOrigin(0.5, 1); + + this.scene.playSound(`stat_${levels.value >= 1 ? "up" : "down"}`); + + statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined)); + + this.scene.tweens.add({ + targets: statSprite, + duration: 250, + alpha: 0.8375, + onComplete: () => { + this.scene.tweens.add({ + targets: statSprite, + delay: 1000, + duration: 250, + alpha: 0 + }); + } + }); + + this.scene.tweens.add({ + targets: statSprite, + duration: 1500, + y: `${levels.value >= 1 ? "-" : "+"}=${160 * 6}` + }); + + this.scene.time.delayedCall(1750, () => { + pokemon.disableMask(); + end(); + }); + } else { + end(); + } + } + + getRandomStat(): BattleStat { + const allStats = Utils.getEnumValues(BattleStat); + return this.getPokemon() ? allStats[this.getPokemon()!.randSeedInt(BattleStat.SPD + 1)] : BattleStat.ATK; // TODO: return default ATK on random? idk... + } + + aggregateStatChanges(random: boolean = false): void { + const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s)); + let existingPhase: StatChangePhase; + if (this.stats.length === 1) { + while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 + && (p.stats[0] === this.stats[0] || (random && p.stats[0] === BattleStat.RAND)) + && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { + if (existingPhase.stats[0] === BattleStat.RAND) { + existingPhase.stats[0] = this.getRandomStat(); + if (existingPhase.stats[0] !== this.stats[0]) { + continue; + } + } + this.levels += existingPhase.levels; + + if (!this.scene.tryRemovePhase(p => p === existingPhase)) { + break; + } + } + } + while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget + && ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva) + && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { + this.stats.push(...existingPhase.stats); + if (!this.scene.tryRemovePhase(p => p === existingPhase)) { + break; + } + } + } + + getStatChangeMessages(stats: BattleStat[], levels: integer, relLevels: integer[]): string[] { + const messages: string[] = []; + + const relLevelStatIndexes = {}; + for (let rl = 0; rl < relLevels.length; rl++) { + const relLevel = relLevels[rl]; + if (!relLevelStatIndexes[relLevel]) { + relLevelStatIndexes[relLevel] = []; + } + relLevelStatIndexes[relLevel].push(rl); + } + + Object.keys(relLevelStatIndexes).forEach(rl => { + const relLevelStats = stats.filter((_, i) => relLevelStatIndexes[rl].includes(i)); + let statsFragment = ""; + + if (relLevelStats.length > 1) { + statsFragment = relLevelStats.length >= 5 + ? i18next.t("battle:stats") + : `${relLevelStats.slice(0, -1).map(s => getBattleStatName(s)).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${getBattleStatName(relLevelStats[relLevelStats.length - 1])}`; + messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); + } else { + statsFragment = getBattleStatName(relLevelStats[0]); + messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); + } + }); + + return messages; + } +} diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts new file mode 100644 index 00000000000..bb9607285ad --- /dev/null +++ b/src/phases/summon-missing-phase.ts @@ -0,0 +1,15 @@ +import BattleScene from "#app/battle-scene.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; +import { SummonPhase } from "./summon-phase"; + +export class SummonMissingPhase extends SummonPhase { + constructor(scene: BattleScene, fieldIndex: integer) { + super(scene, fieldIndex); + } + + preSummon(): void { + this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); + this.scene.time.delayedCall(250, () => this.summon()); + } +} diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts new file mode 100644 index 00000000000..50424170ea7 --- /dev/null +++ b/src/phases/summon-phase.ts @@ -0,0 +1,194 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattleType } from "#app/battle.js"; +import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball.js"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { PlayerGender } from "#app/enums/player-gender.js"; +import { addPokeballOpenParticles } from "#app/field/anims.js"; +import Pokemon, { FieldPosition } from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import i18next from "i18next"; +import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; +import { PostSummonPhase } from "./post-summon-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { ShinySparklePhase } from "./shiny-sparkle-phase"; + +export class SummonPhase extends PartyMemberPokemonPhase { + private loaded: boolean; + + constructor(scene: BattleScene, fieldIndex: integer, player: boolean = true, loaded: boolean = false) { + super(scene, fieldIndex, player); + + this.loaded = loaded; + } + + start() { + super.start(); + + this.preSummon(); + } + + /** + * Sends out a Pokemon before the battle begins and shows the appropriate messages + */ + preSummon(): void { + const partyMember = this.getPokemon(); + // If the Pokemon about to be sent out is fainted or illegal under a challenge, switch to the first non-fainted legal Pokemon + if (!partyMember.isAllowedInBattle()) { + console.warn("The Pokemon about to be sent out is fainted or illegal under a challenge. Attempting to resolve..."); + + // First check if they're somehow still in play, if so remove them. + if (partyMember.isOnField()) { + partyMember.leaveField(); + } + + const party = this.getParty(); + + // Find the first non-fainted Pokemon index above the current one + const legalIndex = party.findIndex((p, i) => i > this.partyMemberIndex && p.isAllowedInBattle()); + if (legalIndex === -1) { + console.error("Party Details:\n", party); + console.error("All available Pokemon were fainted or illegal!"); + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + this.end(); + return; + } + + // Swaps the fainted Pokemon and the first non-fainted legal Pokemon in the party + [party[this.partyMemberIndex], party[legalIndex]] = [party[legalIndex], party[this.partyMemberIndex]]; + console.warn("Swapped %s %O with %s %O", getPokemonNameWithAffix(partyMember), partyMember, getPokemonNameWithAffix(party[0]), party[0]); + } + + if (this.player) { + this.scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); + if (this.player) { + this.scene.pbTray.hide(); + } + this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + this.scene.time.delayedCall(562, () => { + this.scene.trainer.setFrame("2"); + this.scene.time.delayedCall(64, () => { + this.scene.trainer.setFrame("3"); + }); + }); + this.scene.tweens.add({ + targets: this.scene.trainer, + x: -36, + duration: 1000, + onComplete: () => this.scene.trainer.setVisible(false) + }); + this.scene.time.delayedCall(750, () => this.summon()); + } else { + const trainerName = this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); + const pokemonName = this.getPokemon().getNameToRender(); + const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); + + this.scene.pbTrayEnemy.hide(); + this.scene.ui.showText(message, null, () => this.summon()); + } + } + + summon(): void { + const pokemon = this.getPokemon(); + + const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball)); + pokeball.setVisible(false); + pokeball.setOrigin(0.5, 0.625); + this.scene.field.add(pokeball); + + if (this.fieldIndex === 1) { + pokemon.setFieldPosition(FieldPosition.RIGHT, 0); + } else { + const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length; + pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); + } + + const fpOffset = pokemon.getFieldPositionOffset(); + + pokeball.setVisible(true); + + this.scene.tweens.add({ + targets: pokeball, + duration: 650, + x: (this.player ? 100 : 236) + fpOffset[0] + }); + + this.scene.tweens.add({ + targets: pokeball, + duration: 150, + ease: "Cubic.easeOut", + y: (this.player ? 70 : 34) + fpOffset[1], + onComplete: () => { + this.scene.tweens.add({ + targets: pokeball, + duration: 500, + ease: "Cubic.easeIn", + angle: 1440, + y: (this.player ? 132 : 86) + fpOffset[1], + onComplete: () => { + this.scene.playSound("pb_rel"); + pokeball.destroy(); + this.scene.add.existing(pokemon); + this.scene.field.add(pokemon); + if (!this.player) { + const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; + if (playerPokemon?.visible) { + this.scene.field.moveBelow(pokemon, playerPokemon); + } + this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); + } + addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.pokeball); + this.scene.updateModifiers(this.player); + this.scene.updateFieldScale(); + pokemon.showInfo(); + pokemon.playAnim(); + pokemon.setVisible(true); + pokemon.getSprite().setVisible(true); + pokemon.setScale(0.5); + pokemon.tint(getPokeballTintColor(pokemon.pokeball)); + pokemon.untint(250, "Sine.easeIn"); + this.scene.updateFieldScale(); + this.scene.tweens.add({ + targets: pokemon, + duration: 250, + ease: "Sine.easeIn", + scale: pokemon.getSpriteScale(), + onComplete: () => { + pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); + pokemon.getSprite().clearTint(); + pokemon.resetSummonData(); + this.scene.time.delayedCall(1000, () => this.end()); + } + }); + } + }); + } + }); + } + + onEnd(): void { + const pokemon = this.getPokemon(); + + if (pokemon.isShiny()) { + this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex())); + } + + pokemon.resetTurnData(); + + if (!this.loaded || this.scene.currentBattle.battleType === BattleType.TRAINER || (this.scene.currentBattle.waveIndex % 10) === 1) { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + this.queuePostSummon(); + } + } + + queuePostSummon(): void { + this.scene.pushPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); + } + + end() { + this.onEnd(); + + super.end(); + } +} diff --git a/src/phases/switch-biome-phase.ts b/src/phases/switch-biome-phase.ts new file mode 100644 index 00000000000..f20cd59b240 --- /dev/null +++ b/src/phases/switch-biome-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import { Biome } from "#app/enums/biome.js"; +import { getBiomeKey } from "#app/field/arena.js"; +import { BattlePhase } from "./battle-phase"; + +export class SwitchBiomePhase extends BattlePhase { + private nextBiome: Biome; + + constructor(scene: BattleScene, nextBiome: Biome) { + super(scene); + + this.nextBiome = nextBiome; + } + + start() { + super.start(); + + if (this.nextBiome === undefined) { + return this.end(); + } + + this.scene.tweens.add({ + targets: [this.scene.arenaEnemy, this.scene.lastEnemyTrainer], + x: "+=300", + duration: 2000, + onComplete: () => { + this.scene.arenaEnemy.setX(this.scene.arenaEnemy.x - 600); + + this.scene.newArena(this.nextBiome); + + const biomeKey = getBiomeKey(this.nextBiome); + const bgTexture = `${biomeKey}_bg`; + this.scene.arenaBgTransition.setTexture(bgTexture); + this.scene.arenaBgTransition.setAlpha(0); + this.scene.arenaBgTransition.setVisible(true); + this.scene.arenaPlayerTransition.setBiome(this.nextBiome); + this.scene.arenaPlayerTransition.setAlpha(0); + this.scene.arenaPlayerTransition.setVisible(true); + + this.scene.tweens.add({ + targets: [this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition], + duration: 1000, + delay: 1000, + ease: "Sine.easeInOut", + alpha: (target: any) => target === this.scene.arenaPlayer ? 0 : 1, + onComplete: () => { + this.scene.arenaBg.setTexture(bgTexture); + this.scene.arenaPlayer.setBiome(this.nextBiome); + this.scene.arenaPlayer.setAlpha(1); + this.scene.arenaEnemy.setBiome(this.nextBiome); + this.scene.arenaEnemy.setAlpha(1); + this.scene.arenaNextEnemy.setBiome(this.nextBiome); + this.scene.arenaBgTransition.setVisible(false); + this.scene.arenaPlayerTransition.setVisible(false); + if (this.scene.lastEnemyTrainer) { + this.scene.lastEnemyTrainer.destroy(); + } + + this.end(); + } + }); + } + }); + } +} diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts new file mode 100644 index 00000000000..93b0943febf --- /dev/null +++ b/src/phases/switch-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import { BattlePhase } from "./battle-phase"; +import { SwitchSummonPhase } from "./switch-summon-phase"; + +/** + * Opens the party selector UI and transitions into a {@linkcode SwitchSummonPhase} + * for the player (if a switch would be valid for the current battle state). + */ +export class SwitchPhase extends BattlePhase { + protected fieldIndex: integer; + private isModal: boolean; + private doReturn: boolean; + + /** + * Creates a new SwitchPhase + * @param scene {@linkcode BattleScene} Current battle scene + * @param fieldIndex Field index to switch out + * @param isModal Indicates if the switch should be forced (true) or is + * optional (false). + * @param doReturn Indicates if the party member on the field should be + * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. + */ + constructor(scene: BattleScene, fieldIndex: integer, isModal: boolean, doReturn: boolean) { + super(scene); + + this.fieldIndex = fieldIndex; + this.isModal = isModal; + this.doReturn = doReturn; + } + + start() { + super.start(); + + // Skip modal switch if impossible (no remaining party members that aren't in battle) + if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { + return super.end(); + } + + // Skip if the fainted party member has been revived already. doReturn is + // only passed as `false` from FaintPhase (as opposed to other usages such + // as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this + // if the mon should have already been returned but is still alive and well + // on the field. see also; battle.test.ts + if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) { + return super.end(); + } + + // Check if there is any space still in field + if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { + return super.end(); + } + + // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once + const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0; + + this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { + if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); + } + this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + }, PartyUiHandler.FilterNonFainted); + } +} diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts new file mode 100644 index 00000000000..3e401925cea --- /dev/null +++ b/src/phases/switch-summon-phase.ts @@ -0,0 +1,168 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr } from "#app/data/ability.js"; +import { allMoves, ForceSwitchOutAttr } from "#app/data/move.js"; +import { getPokeballTintColor } from "#app/data/pokeball.js"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { SwitchEffectTransferModifier } from "#app/modifier/modifier.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import i18next from "i18next"; +import { PostSummonPhase } from "./post-summon-phase"; +import { SummonPhase } from "./summon-phase"; + +export class SwitchSummonPhase extends SummonPhase { + private slotIndex: integer; + private doReturn: boolean; + private batonPass: boolean; + + private lastPokemon: Pokemon; + + /** + * Constructor for creating a new SwitchSummonPhase + * @param scene {@linkcode BattleScene} the scene the phase is associated with + * @param fieldIndex integer representing position on the battle field + * @param slotIndex integer for the index of pokemon (in party of 6) to switch into + * @param doReturn boolean whether to render "comeback" dialogue + * @param batonPass boolean if the switch is from baton pass + * @param player boolean if the switch is from the player + */ + constructor(scene: BattleScene, fieldIndex: integer, slotIndex: integer, doReturn: boolean, batonPass: boolean, player?: boolean) { + super(scene, fieldIndex, player !== undefined ? player : true); + + this.slotIndex = slotIndex; + this.doReturn = doReturn; + this.batonPass = batonPass; + } + + start(): void { + super.start(); + } + + preSummon(): void { + if (!this.player) { + if (this.slotIndex === -1) { + //@ts-ignore + this.slotIndex = this.scene.currentBattle.trainer?.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); // TODO: what would be the default trainer-slot fallback? + } + if (this.slotIndex > -1) { + this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); + this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); + } + } + + if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) { + if (this.player) { + return this.switchAndSummon(); + } else { + this.scene.time.delayedCall(750, () => this.switchAndSummon()); + return; + } + } + + const pokemon = this.getPokemon(); + + if (!this.batonPass) { + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + } + + this.scene.ui.showText(this.player ? + i18next.t("battle:playerComeBack", { pokemonName: getPokemonNameWithAffix(pokemon) }) : + i18next.t("battle:trainerComeBack", { + trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), + pokemonName: getPokemonNameWithAffix(pokemon) + }) + ); + this.scene.playSound("pb_rel"); + pokemon.hideInfo(); + pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); + this.scene.tweens.add({ + targets: pokemon, + duration: 250, + ease: "Sine.easeIn", + scale: 0.5, + onComplete: () => { + pokemon.leaveField(!this.batonPass, false); + this.scene.time.delayedCall(750, () => this.switchAndSummon()); + } + }); + } + + switchAndSummon() { + const party = this.player ? this.getParty() : this.scene.getEnemyParty(); + const switchedInPokemon = party[this.slotIndex]; + this.lastPokemon = this.getPokemon(); + applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); + if (this.batonPass && switchedInPokemon) { + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id)); + if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { + const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier + && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; + if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { + this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false); + } + } + } + if (switchedInPokemon) { + party[this.slotIndex] = this.lastPokemon; + party[this.fieldIndex] = switchedInPokemon; + const showTextAndSummon = () => { + this.scene.ui.showText(this.player ? + i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(switchedInPokemon) }) : + i18next.t("battle:trainerGo", { + trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), + pokemonName: this.getPokemon().getNameToRender() + }) + ); + // Ensure improperly persisted summon data (such as tags) is cleared upon switching + if (!this.batonPass) { + switchedInPokemon.resetBattleData(); + switchedInPokemon.resetSummonData(); + } + this.summon(); + }; + if (this.player) { + showTextAndSummon(); + } else { + this.scene.time.delayedCall(1500, () => { + this.hideEnemyTrainer(); + this.scene.pbTrayEnemy.hide(); + showTextAndSummon(); + }); + } + } else { + this.end(); + } + } + + onEnd(): void { + super.onEnd(); + + const pokemon = this.getPokemon(); + + const moveId = this.lastPokemon?.scene.currentBattle.lastMove; + const lastUsedMove = moveId ? allMoves[moveId] : undefined; + + const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; + const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); + + // Compensate for turn spent summoning + // Or compensate for force switch move if switched out pokemon is not fainted + if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) { + pokemon.battleSummonData.turnCount--; + } + + if (this.batonPass && pokemon) { + pokemon.transferSummon(this.lastPokemon); + } + + this.lastPokemon?.resetSummonData(); + + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + } + + queuePostSummon(): void { + this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); + } +} diff --git a/src/phases/test-message-phase.ts b/src/phases/test-message-phase.ts new file mode 100644 index 00000000000..14fed24ef4b --- /dev/null +++ b/src/phases/test-message-phase.ts @@ -0,0 +1,8 @@ +import BattleScene from "#app/battle-scene.js"; +import { MessagePhase } from "./message-phase"; + +export class TestMessagePhase extends MessagePhase { + constructor(scene: BattleScene, message: string) { + super(scene, message, null, true); + } +} diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts new file mode 100644 index 00000000000..c74dca97f5c --- /dev/null +++ b/src/phases/title-phase.ts @@ -0,0 +1,303 @@ +import { loggedInUser } from "#app/account.js"; +import BattleScene from "#app/battle-scene.js"; +import { BattleType } from "#app/battle.js"; +import { getDailyRunStarters, fetchDailyRunSeed } from "#app/data/daily-run.js"; +import { Gender } from "#app/data/gender.js"; +import { getBiomeKey } from "#app/field/arena.js"; +import { GameModes, GameMode, getGameMode } from "#app/game-mode.js"; +import { regenerateModifierPoolThresholds, ModifierPoolType, modifierTypes, getDailyRunStarterModifiers } from "#app/modifier/modifier-type.js"; +import { Phase } from "#app/phase.js"; +import { SessionSaveData } from "#app/system/game-data.js"; +import { Unlockables } from "#app/system/unlockables.js"; +import { vouchers } from "#app/system/voucher.js"; +import { OptionSelectItem, OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler.js"; +import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { Modifier } from "#app/modifier/modifier.js"; +import { CheckSwitchPhase } from "./check-switch-phase"; +import { EncounterPhase } from "./encounter-phase"; +import { SelectChallengePhase } from "./select-challenge-phase"; +import { SelectStarterPhase } from "./select-starter-phase"; +import { SummonPhase } from "./summon-phase"; + + +export class TitlePhase extends Phase { + private loaded: boolean; + private lastSessionData: SessionSaveData; + public gameMode: GameModes; + + constructor(scene: BattleScene) { + super(scene); + + this.loaded = false; + } + + start(): void { + super.start(); + + this.scene.ui.clearText(); + this.scene.ui.fadeIn(250); + + this.scene.playBgm("title", true); + + this.scene.gameData.getSession(loggedInUser?.lastSessionSlot ?? -1).then(sessionData => { + if (sessionData) { + this.lastSessionData = sessionData; + const biomeKey = getBiomeKey(sessionData.arena.biome); + const bgTexture = `${biomeKey}_bg`; + this.scene.arenaBg.setTexture(bgTexture); + } + this.showOptions(); + }).catch(err => { + console.error(err); + this.showOptions(); + }); + } + + showOptions(): void { + const options: OptionSelectItem[] = []; + if (loggedInUser && loggedInUser.lastSessionSlot > -1) { + options.push({ + label: i18next.t("continue", {ns: "menu"}), + handler: () => { + this.loadSaveSlot(this.lastSessionData || !loggedInUser ? -1 : loggedInUser.lastSessionSlot); + return true; + } + }); + } + options.push({ + label: i18next.t("menu:newGame"), + handler: () => { + const setModeAndEnd = (gameMode: GameModes) => { + this.gameMode = gameMode; + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + }; + if (this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { + const options: OptionSelectItem[] = [ + { + label: GameMode.getModeName(GameModes.CLASSIC), + handler: () => { + setModeAndEnd(GameModes.CLASSIC); + return true; + } + }, + { + label: GameMode.getModeName(GameModes.CHALLENGE), + handler: () => { + setModeAndEnd(GameModes.CHALLENGE); + return true; + } + }, + { + label: GameMode.getModeName(GameModes.ENDLESS), + handler: () => { + setModeAndEnd(GameModes.ENDLESS); + return true; + } + } + ]; + if (this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + options.push({ + label: GameMode.getModeName(GameModes.SPLICED_ENDLESS), + handler: () => { + setModeAndEnd(GameModes.SPLICED_ENDLESS); + return true; + } + }); + } + options.push({ + label: i18next.t("menu:cancel"), + handler: () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }); + this.scene.ui.showText(i18next.t("menu:selectGameMode"), null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + } else { + this.gameMode = GameModes.CLASSIC; + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.clearText(); + this.end(); + } + return true; + } + }, + { + label: i18next.t("menu:loadGame"), + handler: () => { + this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, + (slotId: integer) => { + if (slotId === -1) { + return this.showOptions(); + } + this.loadSaveSlot(slotId); + }); + return true; + } + }, + { + label: i18next.t("menu:dailyRun"), + handler: () => { + this.initDailyRun(); + return true; + }, + keepOpen: true + }, + { + label: i18next.t("menu:settings"), + handler: () => { + this.scene.ui.setOverlayMode(Mode.SETTINGS); + return true; + }, + keepOpen: true + }); + const config: OptionSelectConfig = { + options: options, + noCancel: true, + yOffset: 47 + }; + this.scene.ui.setMode(Mode.TITLE, config); + } + + loadSaveSlot(slotId: integer): void { + this.scene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.resetModeChain(); + this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : undefined).then((success: boolean) => { + if (success) { + this.loaded = true; + this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); + } else { + this.end(); + } + }).catch(err => { + console.error(err); + this.scene.ui.showText(i18next.t("menu:failedToLoadSession"), null); + }); + } + + initDailyRun(): void { + this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + this.scene.clearPhaseQueue(); + if (slotId === -1) { + this.scene.pushPhase(new TitlePhase(this.scene)); + return super.end(); + } + this.scene.sessionSlotId = slotId; + + const generateDaily = (seed: string) => { + this.scene.gameMode = getGameMode(GameModes.DAILY); + + this.scene.setSeed(seed); + this.scene.resetSeed(1); + + this.scene.money = this.scene.gameMode.getStartingMoney(); + + const starters = getDailyRunStarters(this.scene, seed); + const startingLevel = this.scene.gameMode.getStartingLevel(); + + const party = this.scene.getParty(); + const loadPokemonAssets: Promise[] = []; + for (const starter of starters) { + const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); + const starterGender = starter.species.malePercent !== null + ? !starterProps.female ? Gender.MALE : Gender.FEMALE + : Gender.GENDERLESS; + const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); + starterPokemon.setVisible(false); + party.push(starterPokemon); + loadPokemonAssets.push(starterPokemon.loadAssets()); + } + + regenerateModifierPoolThresholds(party, ModifierPoolType.DAILY_STARTER); + + const modifiers: Modifier[] = Array(3).fill(null).map(() => modifierTypes.EXP_SHARE().withIdFromFunc(modifierTypes.EXP_SHARE).newModifier()) + .concat(Array(3).fill(null).map(() => modifierTypes.GOLDEN_EXP_CHARM().withIdFromFunc(modifierTypes.GOLDEN_EXP_CHARM).newModifier())) + .concat(getDailyRunStarterModifiers(party)) + .filter((m) => m !== null); + + for (const m of modifiers) { + this.scene.addModifier(m, true, false, false, true); + } + this.scene.updateModifiers(true, true); + + Promise.all(loadPokemonAssets).then(() => { + this.scene.time.delayedCall(500, () => this.scene.playBgm()); + this.scene.gameData.gameStats.dailyRunSessionsPlayed++; + this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); + this.scene.newBattle(); + this.scene.arena.init(); + this.scene.sessionPlayTime = 0; + this.scene.lastSavePlayTime = 0; + this.end(); + }); + }; + + // If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date. + if (!Utils.isLocal) { + fetchDailyRunSeed().then(seed => { + if (seed) { + generateDaily(seed); + } else { + throw new Error("Daily run seed is null!"); + } + }).catch(err => { + console.error("Failed to load daily run:\n", err); + }); + } else { + generateDaily(btoa(new Date().toISOString().substring(0, 10))); + } + }); + } + + end(): void { + if (!this.loaded && !this.scene.gameMode.isDaily) { + this.scene.arena.preloadBgm(); + this.scene.gameMode = getGameMode(this.gameMode); + if (this.gameMode === GameModes.CHALLENGE) { + this.scene.pushPhase(new SelectChallengePhase(this.scene)); + } else { + this.scene.pushPhase(new SelectStarterPhase(this.scene)); + } + this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); + } else { + this.scene.playBgm(); + } + + this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); + + if (this.loaded) { + const availablePartyMembers = this.scene.getParty().filter(p => p.isAllowedInBattle()).length; + + this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); + if (this.scene.currentBattle.double && availablePartyMembers > 1) { + this.scene.pushPhase(new SummonPhase(this.scene, 1, true, true)); + } + + if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { + const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (availablePartyMembers > minPartySize) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); + if (this.scene.currentBattle.double) { + this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + } + } + } + } + + for (const achv of Object.keys(this.scene.gameData.achvUnlocks)) { + if (vouchers.hasOwnProperty(achv)) { + this.scene.validateVoucher(vouchers[achv]); + } + } + + super.end(); + } +} diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts new file mode 100644 index 00000000000..fe3d0482483 --- /dev/null +++ b/src/phases/toggle-double-position-phase.ts @@ -0,0 +1,31 @@ +import BattleScene from "#app/battle-scene.js"; +import { FieldPosition } from "#app/field/pokemon.js"; +import { BattlePhase } from "./battle-phase"; + +export class ToggleDoublePositionPhase extends BattlePhase { + private double: boolean; + + constructor(scene: BattleScene, double: boolean) { + super(scene); + + this.double = double; + } + + start() { + super.start(); + + const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true)); + if (playerPokemon) { + playerPokemon.setFieldPosition(this.double && this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { + if (playerPokemon.getFieldIndex() === 1) { + const party = this.scene.getParty(); + party[1] = party[0]; + party[0] = playerPokemon; + } + this.end(); + }); + } else { + this.end(); + } + } +} diff --git a/src/phases/trainer-message-test-phase.ts b/src/phases/trainer-message-test-phase.ts new file mode 100644 index 00000000000..4ea451660c3 --- /dev/null +++ b/src/phases/trainer-message-test-phase.ts @@ -0,0 +1,41 @@ +import BattleScene from "#app/battle-scene.js"; +import { trainerConfigs } from "#app/data/trainer-config.js"; +import { TrainerType } from "#app/enums/trainer-type.js"; +import { BattlePhase } from "./battle-phase"; +import { TestMessagePhase } from "./test-message-phase"; + +export class TrainerMessageTestPhase extends BattlePhase { + private trainerTypes: TrainerType[]; + + constructor(scene: BattleScene, ...trainerTypes: TrainerType[]) { + super(scene); + + this.trainerTypes = trainerTypes; + } + + start() { + super.start(); + + const testMessages: string[] = []; + + for (const t of Object.keys(trainerConfigs)) { + const type = parseInt(t); + if (this.trainerTypes.length && !this.trainerTypes.find(tt => tt === type as TrainerType)) { + continue; + } + const config = trainerConfigs[type]; + [config.encounterMessages, config.femaleEncounterMessages, config.victoryMessages, config.femaleVictoryMessages, config.defeatMessages, config.femaleDefeatMessages] + .map(messages => { + if (messages?.length) { + testMessages.push(...messages); + } + }); + } + + for (const message of testMessages) { + this.scene.pushPhase(new TestMessagePhase(this.scene, message)); + } + + this.end(); + } +} diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts new file mode 100644 index 00000000000..7b8ee05de44 --- /dev/null +++ b/src/phases/trainer-victory-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import { getCharVariantFromDialogue } from "#app/data/dialogue.js"; +import { TrainerSlot } from "#app/data/trainer-config.js"; +import { TrainerType } from "#app/enums/trainer-type.js"; +import { modifierTypes } from "#app/modifier/modifier-type.js"; +import { vouchers } from "#app/system/voucher.js"; +import i18next from "i18next"; +import * as Utils from "#app/utils.js"; +import { BattlePhase } from "./battle-phase"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; +import { MoneyRewardPhase } from "./money-reward-phase"; + +export class TrainerVictoryPhase extends BattlePhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + this.scene.disableMenu = true; + + this.scene.playBgm(this.scene.currentBattle.trainer?.config.victoryBgm); + + this.scene.unshiftPhase(new MoneyRewardPhase(this.scene, this.scene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? + + const modifierRewardFuncs = this.scene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? + for (const modifierRewardFunc of modifierRewardFuncs) { + this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc)); + } + + const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? + if (vouchers.hasOwnProperty(TrainerType[trainerType])) { + if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { + this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType])); + } + } + + this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { + const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); + message = message!; // tell TS compiler it's defined now + + const showMessage = () => { + const originalFunc = showMessageOrEnd; + showMessageOrEnd = () => this.scene.ui.showDialogue(message, this.scene.currentBattle.trainer?.getName(), null, originalFunc); + + showMessageOrEnd(); + }; + let showMessageOrEnd = () => this.end(); + if (victoryMessages?.length) { + if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { + const originalFunc = showMessageOrEnd; + showMessageOrEnd = () => this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => originalFunc())); + this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(this.scene.currentBattle.trainer?.getKey()!, getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); // TODO: is this bang correct? + } else { + showMessage(); + } + } else { + showMessageOrEnd(); + } + }, null, true); + + this.showEnemyTrainer(); + } +} diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts new file mode 100644 index 00000000000..62589e99b79 --- /dev/null +++ b/src/phases/turn-end-phase.ts @@ -0,0 +1,71 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/ability.js"; +import { BattlerTagLapseType } from "#app/data/battler-tags.js"; +import { allMoves } from "#app/data/move.js"; +import { TerrainType } from "#app/data/terrain.js"; +import { Moves } from "#app/enums/moves.js"; +import { WeatherType } from "#app/enums/weather-type.js"; +import { TurnEndEvent } from "#app/events/battle-scene.js"; +import Pokemon from "#app/field/pokemon.js"; +import { getPokemonNameWithAffix } from "#app/messages.js"; +import { TurnHealModifier, EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, TurnStatusEffectModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier.js"; +import i18next from "i18next"; +import { FieldPhase } from "./field-phase"; +import { MessagePhase } from "./message-phase"; +import { PokemonHealPhase } from "./pokemon-heal-phase"; + +export class TurnEndPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.currentBattle.incrementTurn(this.scene); + this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); + + const handlePokemon = (pokemon: Pokemon) => { + pokemon.lapseTags(BattlerTagLapseType.TURN_END); + + if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) { + this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name }))); + pokemon.summonData.disabledMove = Moves.NONE; + } + + this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + + if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { + this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), + Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); + } + + if (!pokemon.isPlayer()) { + this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); + this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + } + + applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); + + this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); + + this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); + + pokemon.battleSummonData.turnCount++; + }; + + this.executeForAll(handlePokemon); + + this.scene.arena.lapseTags(); + + if (this.scene.arena.weather && !this.scene.arena.weather.lapse()) { + this.scene.arena.trySetWeather(WeatherType.NONE, false); + } + + if (this.scene.arena.terrain && !this.scene.arena.terrain.lapse()) { + this.scene.arena.trySetTerrain(TerrainType.NONE, false); + } + + this.end(); + } +} diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts new file mode 100644 index 00000000000..a999d57ca0f --- /dev/null +++ b/src/phases/turn-init-phase.ts @@ -0,0 +1,65 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex } from "#app/battle.js"; +import { TurnInitEvent } from "#app/events/battle-scene.js"; +import { PlayerPokemon } from "#app/field/pokemon.js"; +import i18next from "i18next"; +import { FieldPhase } from "./field-phase"; +import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; +import { CommandPhase } from "./command-phase"; +import { EnemyCommandPhase } from "./enemy-command-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { TurnStartPhase } from "./turn-start-phase"; + +export class TurnInitPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + this.scene.getPlayerField().forEach(p => { + // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch + if (p.isOnField() && !p.isAllowedInBattle()) { + this.scene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true); + + const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle()); + + if (!allowedPokemon.length) { + // If there are no longer any legal pokemon in the party, game over. + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new GameOverPhase(this.scene)); + } else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) { + // If there is at least one pokemon in the back that is legal to switch in, force a switch. + p.switchOut(false); + } else { + // If there are no pokemon in the back but we're not game overing, just hide the pokemon. + // This should only happen in double battles. + p.leaveField(); + } + if (allowedPokemon.length === 1 && this.scene.currentBattle.double) { + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } + } + }); + + //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); + this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); + + this.scene.getField().forEach((pokemon, i) => { + if (pokemon?.isActive()) { + if (pokemon.isPlayer()) { + this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); + } + + pokemon.resetTurnData(); + + this.scene.pushPhase(pokemon.isPlayer() ? new CommandPhase(this.scene, i) : new EnemyCommandPhase(this.scene, i - BattlerIndex.ENEMY)); + } + }); + + this.scene.pushPhase(new TurnStartPhase(this.scene)); + + this.end(); + } +} diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts new file mode 100644 index 00000000000..1320cb6235c --- /dev/null +++ b/src/phases/turn-start-phase.ts @@ -0,0 +1,172 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability.js"; +import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { Stat } from "#app/enums/stat.js"; +import { PokemonMove } from "#app/field/pokemon.js"; +import { BypassSpeedChanceModifier } from "#app/modifier/modifier.js"; +import { Command } from "#app/ui/command-ui-handler.js"; +import * as Utils from "#app/utils.js"; +import { AttemptCapturePhase } from "./attempt-capture-phase"; +import { AttemptRunPhase } from "./attempt-run-phase"; +import { BerryPhase } from "./berry-phase"; +import { FieldPhase } from "./field-phase"; +import { MoveHeaderPhase } from "./move-header-phase"; +import { MovePhase } from "./move-phase"; +import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; +import { SwitchSummonPhase } from "./switch-summon-phase"; +import { TurnEndPhase } from "./turn-end-phase"; +import { WeatherEffectPhase } from "./weather-effect-phase"; + +export class TurnStartPhase extends FieldPhase { + constructor(scene: BattleScene) { + super(scene); + } + + start() { + super.start(); + + const field = this.scene.getField(); + const order = this.getOrder(); + + const battlerBypassSpeed = {}; + + this.scene.getField(true).filter(p => p.summonData).map(p => { + const bypassSpeed = new Utils.BooleanHolder(false); + const canCheckHeldItems = new Utils.BooleanHolder(true); + applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); + applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems); + if (canCheckHeldItems.value) { + this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + } + battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; + }); + + const moveOrder = order.slice(0); + + moveOrder.sort((a, b) => { + const aCommand = this.scene.currentBattle.turnCommands[a]; + const bCommand = this.scene.currentBattle.turnCommands[b]; + + if (aCommand?.command !== bCommand?.command) { + if (aCommand?.command === Command.FIGHT) { + return 1; + } else if (bCommand?.command === Command.FIGHT) { + return -1; + } + } else if (aCommand?.command === Command.FIGHT) { + const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here? + const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here? + + const aPriority = new Utils.IntegerHolder(aMove.priority); + const bPriority = new Utils.IntegerHolder(bMove.priority); + + applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? + applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? + + applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here? + applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here? + + if (aPriority.value !== bPriority.value) { + const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value); + const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value; + if (bracketDifference === 0 && hasSpeedDifference) { + return battlerBypassSpeed[a].value ? -1 : 1; + } + return aPriority.value < bPriority.value ? 1 : -1; + } + } + + if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { + return battlerBypassSpeed[a].value ? -1 : 1; + } + + const aIndex = order.indexOf(a); + const bIndex = order.indexOf(b); + + return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0; + }); + + let orderIndex = 0; + + for (const o of moveOrder) { + + const pokemon = field[o]; + const turnCommand = this.scene.currentBattle.turnCommands[o]; + + if (turnCommand?.skip) { + continue; + } + + switch (turnCommand?.command) { + case Command.FIGHT: + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; + } + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr(MoveHeaderAttr)) { + this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); + } + if (pokemon.isPlayer()) { + if (turnCommand.cursor === -1) { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? + } else { + const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? + this.scene.pushPhase(playerPhase); + } + } else { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? + } + break; + case Command.BALL: + this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? + break; + case Command.POKEMON: + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here? + break; + case Command.RUN: + let runningPokemon = pokemon; + if (this.scene.currentBattle.double) { + const playerActivePokemon = field.filter(pokemon => { + if (!!pokemon) { + return pokemon.isPlayer() && pokemon.isActive(); + } else { + return; + } + }); + // if only one pokemon is alive, use that one + if (playerActivePokemon.length > 1) { + // find which active pokemon has faster speed + const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; + // check if either active pokemon has the ability "Run Away" + const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); + runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } + } + this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); + break; + } + } + + + this.scene.pushPhase(new WeatherEffectPhase(this.scene)); + + for (const o of order) { + if (field[o].status && field[o].status.isPostTurn()) { + this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); + } + } + + this.scene.pushPhase(new BerryPhase(this.scene)); + this.scene.pushPhase(new TurnEndPhase(this.scene)); + + /** + * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front + * of the queue and dequeues to start the next phase + * this is important since stuff like SwitchSummon, AttemptRun, AttemptCapture Phases break the "flow" and should take precedence + */ + this.end(); + } +} diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts new file mode 100644 index 00000000000..4757af5e15d --- /dev/null +++ b/src/phases/unavailable-phase.ts @@ -0,0 +1,17 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Mode } from "#app/ui/ui.js"; +import { LoginPhase } from "./login-phase"; + +export class UnavailablePhase extends Phase { + constructor(scene: BattleScene) { + super(scene); + } + + start(): void { + this.scene.ui.setMode(Mode.UNAVAILABLE, () => { + this.scene.unshiftPhase(new LoginPhase(this.scene, true)); + this.end(); + }); + } +} diff --git a/src/phases/unlock-phase.ts b/src/phases/unlock-phase.ts new file mode 100644 index 00000000000..ce06e2445ac --- /dev/null +++ b/src/phases/unlock-phase.ts @@ -0,0 +1,27 @@ +import BattleScene from "#app/battle-scene.js"; +import { Phase } from "#app/phase.js"; +import { Unlockables, getUnlockableName } from "#app/system/unlockables.js"; +import { Mode } from "#app/ui/ui.js"; +import i18next from "i18next"; + +export class UnlockPhase extends Phase { + private unlockable: Unlockables; + + constructor(scene: BattleScene, unlockable: Unlockables) { + super(scene); + + this.unlockable = unlockable; + } + + start(): void { + this.scene.time.delayedCall(2000, () => { + this.scene.gameData.unlocks[this.unlockable] = true; + this.scene.playSound("level_up_fanfare"); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:unlockedSomething", { unlockedThing: getUnlockableName(this.unlockable) }), null, () => { + this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); + this.end(); + }, null, true, 1500); + }); + } +} diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts new file mode 100644 index 00000000000..b7587de4dbb --- /dev/null +++ b/src/phases/victory-phase.ts @@ -0,0 +1,151 @@ +import BattleScene from "#app/battle-scene.js"; +import { BattlerIndex, BattleType } from "#app/battle.js"; +import { modifierTypes } from "#app/modifier/modifier-type.js"; +import { ExpShareModifier, ExpBalanceModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier.js"; +import * as Utils from "#app/utils.js"; +import Overrides from "#app/overrides"; +import { BattleEndPhase } from "./battle-end-phase"; +import { NewBattlePhase } from "./new-battle-phase"; +import { PokemonPhase } from "./pokemon-phase"; +import { AddEnemyBuffModifierPhase } from "./add-enemy-buff-modifier-phase"; +import { EggLapsePhase } from "./egg-lapse-phase"; +import { ExpPhase } from "./exp-phase"; +import { GameOverPhase } from "./game-over-phase"; +import { ModifierRewardPhase } from "./modifier-reward-phase"; +import { SelectModifierPhase } from "./select-modifier-phase"; +import { ShowPartyExpBarPhase } from "./show-party-exp-bar-phase"; +import { TrainerVictoryPhase } from "./trainer-victory-phase"; + +export class VictoryPhase extends PokemonPhase { + constructor(scene: BattleScene, battlerIndex: BattlerIndex) { + super(scene, battlerIndex); + } + + start() { + super.start(); + + this.scene.gameData.gameStats.pokemonDefeated++; + + const participantIds = this.scene.currentBattle.playerParticipantIds; + const party = this.scene.getParty(); + const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier; + const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier; + const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier; + const nonFaintedPartyMembers = party.filter(p => p.hp); + const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < this.scene.getMaxExpLevel()); + const partyMemberExp: number[] = []; + + if (participantIds.size) { + let expValue = this.getPokemon().getExpValue(); + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + expValue = Math.floor(expValue * 1.5); + } + for (const partyMember of nonFaintedPartyMembers) { + const pId = partyMember.id; + const participated = participantIds.has(pId); + if (participated) { + partyMember.addFriendship(2); + } + if (!expPartyMembers.includes(partyMember)) { + continue; + } + if (!participated && !expShareModifier) { + partyMemberExp.push(0); + continue; + } + let expMultiplier = 0; + if (participated) { + expMultiplier += (1 / participantIds.size); + if (participantIds.size > 1 && multipleParticipantExpBonusModifier) { + expMultiplier += multipleParticipantExpBonusModifier.getStackCount() * 0.2; + } + } else if (expShareModifier) { + expMultiplier += (expShareModifier.getStackCount() * 0.2) / participantIds.size; + } + if (partyMember.pokerus) { + expMultiplier *= 1.5; + } + if (Overrides.XP_MULTIPLIER_OVERRIDE !== null) { + expMultiplier = Overrides.XP_MULTIPLIER_OVERRIDE; + } + const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier); + this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp); + partyMemberExp.push(Math.floor(pokemonExp.value)); + } + + if (expBalanceModifier) { + let totalLevel = 0; + let totalExp = 0; + expPartyMembers.forEach((expPartyMember, epm) => { + totalExp += partyMemberExp[epm]; + totalLevel += expPartyMember.level; + }); + + const medianLevel = Math.floor(totalLevel / expPartyMembers.length); + + const recipientExpPartyMemberIndexes: number[] = []; + expPartyMembers.forEach((expPartyMember, epm) => { + if (expPartyMember.level <= medianLevel) { + recipientExpPartyMemberIndexes.push(epm); + } + }); + + const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length); + + expPartyMembers.forEach((_partyMember, pm) => { + partyMemberExp[pm] = Phaser.Math.Linear(partyMemberExp[pm], recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0, 0.2 * expBalanceModifier.getStackCount()); + }); + } + + for (let pm = 0; pm < expPartyMembers.length; pm++) { + const exp = partyMemberExp[pm]; + + if (exp) { + const partyMemberIndex = party.indexOf(expPartyMembers[pm]); + this.scene.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this.scene, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this.scene, partyMemberIndex, exp)); + } + } + } + + if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType ? !p?.isFainted(true) : p.isOnField())) { + this.scene.pushPhase(new BattleEndPhase(this.scene)); + if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.pushPhase(new TrainerVictoryPhase(this.scene)); + } + if (this.scene.gameMode.isEndless || !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { + this.scene.pushPhase(new EggLapsePhase(this.scene)); + if (this.scene.currentBattle.waveIndex % 10) { + this.scene.pushPhase(new SelectModifierPhase(this.scene)); + } else if (this.scene.gameMode.isDaily) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); + if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); + } + } else { + const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); + } + if (this.scene.currentBattle.waveIndex <= 750 && (this.scene.currentBattle.waveIndex <= 500 || (this.scene.currentBattle.waveIndex % 30) === superExpWave)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, (this.scene.currentBattle.waveIndex % 30) !== superExpWave || this.scene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM)); + } + if (this.scene.currentBattle.waveIndex <= 150 && !(this.scene.currentBattle.waveIndex % 50)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); + } + if (this.scene.gameMode.isEndless && !(this.scene.currentBattle.waveIndex % 50)) { + this.scene.pushPhase(new ModifierRewardPhase(this.scene, !(this.scene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS)); + this.scene.pushPhase(new AddEnemyBuffModifierPhase(this.scene)); + } + } + this.scene.pushPhase(new NewBattlePhase(this.scene)); + } else { + this.scene.currentBattle.battleType = BattleType.CLEAR; + this.scene.score += this.scene.gameMode.getClearScoreBonus(); + this.scene.updateScoreText(); + this.scene.pushPhase(new GameOverPhase(this.scene, true)); + } + } + + this.end(); + } +} diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts new file mode 100644 index 00000000000..6f5fbc0fce3 --- /dev/null +++ b/src/phases/weather-effect-phase.ts @@ -0,0 +1,67 @@ +import BattleScene from "#app/battle-scene.js"; +import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js"; +import { CommonAnim } from "#app/data/battle-anims.js"; +import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather.js"; +import { WeatherType } from "#app/enums/weather-type.js"; +import Pokemon, { HitResult } from "#app/field/pokemon.js"; +import * as Utils from "#app/utils.js"; +import { CommonAnimPhase } from "./common-anim-phase"; + +export class WeatherEffectPhase extends CommonAnimPhase { + public weather: Weather | null; + + constructor(scene: BattleScene) { + super(scene, undefined, undefined, CommonAnim.SUNNY + ((scene?.arena?.weather?.weatherType || WeatherType.NONE) - 1)); + this.weather = scene?.arena?.weather; + } + + start() { + // Update weather state with any changes that occurred during the turn + this.weather = this.scene?.arena?.weather; + + if (!this.weather) { + this.end(); + return; + } + + this.setAnimation(CommonAnim.SUNNY + (this.weather.weatherType - 1)); + + if (this.weather.isDamaging()) { + + const cancelled = new Utils.BooleanHolder(false); + + this.executeForAll((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled)); + + if (!cancelled.value) { + const inflictDamage = (pokemon: Pokemon) => { + const cancelled = new Utils.BooleanHolder(false); + + applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather , cancelled); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); + + if (cancelled.value) { + return; + } + + const damage = Math.ceil(pokemon.getMaxHp() / 16); + + this.scene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? + pokemon.damageAndUpdate(damage, HitResult.EFFECTIVE, false, false, true); + }; + + this.executeForAll((pokemon: Pokemon) => { + const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; + if (!immune) { + inflictDamage(pokemon); + } + }); + } + } + + this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct? + this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); + + super.start(); + }); + } +} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 40f24fc8326..a9acd80fdee 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -27,7 +27,6 @@ import { Tutorial } from "../tutorial"; import { speciesEggMoves } from "../data/egg-moves"; import { allMoves } from "../data/move"; import { TrainerVariant } from "../field/trainer"; -import { OutdatedPhase, ReloadSessionPhase } from "#app/phases"; import { Variant, variantData } from "#app/data/variant"; import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad"; import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard"; @@ -43,6 +42,8 @@ import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; import { WeatherType } from "#app/enums/weather-type.js"; import { TerrainType } from "#app/data/terrain.js"; +import { OutdatedPhase } from "#app/phases/outdated-phase.js"; +import { ReloadSessionPhase } from "#app/phases/reload-session-phase.js"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 4d8097897e9..7263ae3a3de 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -8,13 +8,21 @@ import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; import { EaseType } from "#enums/ease-type"; import { MoneyFormat } from "#enums/money-format"; import { PlayerGender } from "#enums/player-gender"; +import { getIsInitialized, initI18n } from "#app/plugins/i18n.js"; + +function getTranslation(key: string): string { + if (!getIsInitialized()) { + initI18n(); + } + return i18next.t(key); +} const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? { value: (i * 10).toString(), label: (i * 10).toString(), } : { value: "Mute", - label: i18next.t("settings:mute") + label: getTranslation("settings:mute") }); const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = new Array(9).fill(null).map((_, i) => { const value = ((i + 1) * 10).toString(); diff --git a/src/system/voucher.ts b/src/system/voucher.ts index 0c71e3c0286..2f94308d9c8 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -1,9 +1,10 @@ import BattleScene from "../battle-scene"; import i18next from "i18next"; -import { Achv, AchvTier, achvs, getAchievementDescription } from "./achv"; +import { AchvTier, achvs, getAchievementDescription } from "./achv"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import { ConditionFn } from "#app/@types/common.js"; +import { trainerConfigs } from "#app/data/trainer-config.js"; export enum VoucherType { REGULAR, @@ -88,42 +89,36 @@ export interface Vouchers { export const vouchers: Vouchers = {}; -const voucherAchvs: Achv[] = [ achvs.CLASSIC_VICTORY ]; - export function initVouchers() { - import("../data/trainer-config").then(tc => { - const trainerConfigs = tc.trainerConfigs; + for (const achv of [achvs.CLASSIC_VICTORY]) { + const voucherType = achv.score >= 150 + ? VoucherType.GOLDEN + : achv.score >= 100 + ? VoucherType.PREMIUM + : achv.score >= 75 + ? VoucherType.PLUS + : VoucherType.REGULAR; + vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); + } - for (const achv of voucherAchvs) { - const voucherType = achv.score >= 150 - ? VoucherType.GOLDEN - : achv.score >= 100 - ? VoucherType.PREMIUM - : achv.score >= 75 - ? VoucherType.PLUS - : VoucherType.REGULAR; - vouchers[achv.id] = new Voucher(voucherType, getAchievementDescription(achv.localizationKey)); - } + const bossTrainerTypes = Object.keys(trainerConfigs) + .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL && trainerConfigs[tt].hasVoucher); - const bossTrainerTypes = Object.keys(trainerConfigs) - .filter(tt => trainerConfigs[tt].isBoss && trainerConfigs[tt].getDerivedType() !== TrainerType.RIVAL && trainerConfigs[tt].hasVoucher); - - for (const trainerType of bossTrainerTypes) { - const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 - ? VoucherType.PLUS - : VoucherType.PREMIUM; - const key = TrainerType[trainerType]; - const trainerName = trainerConfigs[trainerType].name; - const trainer = trainerConfigs[trainerType]; - const title = trainer.title ? ` (${trainer.title})` : ""; - vouchers[key] = new Voucher( - voucherType, - `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, - ); - } - const voucherKeys = Object.keys(vouchers); - for (const k of voucherKeys) { - vouchers[k].id = k; - } - }); + for (const trainerType of bossTrainerTypes) { + const voucherType = trainerConfigs[trainerType].moneyMultiplier < 10 + ? VoucherType.PLUS + : VoucherType.PREMIUM; + const key = TrainerType[trainerType]; + const trainerName = trainerConfigs[trainerType].name; + const trainer = trainerConfigs[trainerType]; + const title = trainer.title ? ` (${trainer.title})` : ""; + vouchers[key] = new Voucher( + voucherType, + `${i18next.t("voucher:defeatTrainer", { trainerName })} ${title}`, + ); + } + const voucherKeys = Object.keys(vouchers); + for (const k of voucherKeys) { + vouchers[k].id = k; + } } diff --git a/src/test/abilities/ability_timing.test.ts b/src/test/abilities/ability_timing.test.ts index 3906233a7bf..c117c62d45b 100644 --- a/src/test/abilities/ability_timing.test.ts +++ b/src/test/abilities/ability_timing.test.ts @@ -1,4 +1,3 @@ -import { CommandPhase, MessagePhase, TurnInitPhase } from "#app/phases"; import i18next, { initI18n } from "#app/plugins/i18n"; import GameManager from "#test/utils/gameManager"; import { Mode } from "#app/ui/ui"; @@ -8,6 +7,9 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Ability Timing", () => { diff --git a/src/test/abilities/aura_break.test.ts b/src/test/abilities/aura_break.test.ts index a34475cb1ad..bca400bc0e3 100644 --- a/src/test/abilities/aura_break.test.ts +++ b/src/test/abilities/aura_break.test.ts @@ -1,5 +1,4 @@ import { allMoves } from "#app/data/move.js"; -import { MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +7,7 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Abilities - Aura Break", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/battery.test.ts b/src/test/abilities/battery.test.ts index 2345e63d987..766c1c30ecc 100644 --- a/src/test/abilities/battery.test.ts +++ b/src/test/abilities/battery.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Battery", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index 1a5c71b4c15..c28a00e821d 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/costar.test.ts b/src/test/abilities/costar.test.ts index ef3fb3a2ab0..9410ee55069 100644 --- a/src/test/abilities/costar.test.ts +++ b/src/test/abilities/costar.test.ts @@ -2,12 +2,13 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { Abilities } from "#app/enums/abilities.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { CommandPhase, MessagePhase } from "#app/phases.js"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index 8b1b959bea8..969375c397e 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -4,10 +4,14 @@ import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { StatusEffect } from "#app/data/status-effect.js"; -import { CommandPhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { Mode } from "#app/ui/ui.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/dry_skin.test.ts b/src/test/abilities/dry_skin.test.ts index 20b85eab767..1e3860da985 100644 --- a/src/test/abilities/dry_skin.test.ts +++ b/src/test/abilities/dry_skin.test.ts @@ -1,5 +1,4 @@ import { Species } from "#app/enums/species.js"; -import { TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,7 @@ import { Moves } from "#enums/moves"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Dry Skin", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/flash_fire.test.ts b/src/test/abilities/flash_fire.test.ts index b77b271b754..28c59903b68 100644 --- a/src/test/abilities/flash_fire.test.ts +++ b/src/test/abilities/flash_fire.test.ts @@ -5,11 +5,12 @@ import { Moves } from "#enums/moves"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; -import { MovePhase, TurnEndPhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { StatusEffect } from "#app/data/status-effect.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { BattlerIndex } from "#app/battle.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Flash Fire", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 52ae323839d..d033604fe00 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -1,10 +1,4 @@ import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { - BerryPhase, - MoveEndPhase, - TurnEndPhase, - TurnStartPhase, -} from "#app/phases"; import GameManager from "#app/test/utils/gameManager"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -16,6 +10,10 @@ import { SPLASH_ONLY } from "../utils/testUtils"; import { BattleStat } from "#app/data/battle-stat.js"; import { StatusEffect } from "#app/enums/status-effect.js"; import Pokemon from "#app/field/pokemon.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Abilities - Gulp Missile", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/heatproof.test.ts b/src/test/abilities/heatproof.test.ts index 8249ba6996f..64a45c5023f 100644 --- a/src/test/abilities/heatproof.test.ts +++ b/src/test/abilities/heatproof.test.ts @@ -1,5 +1,5 @@ import { Species } from "#app/enums/species.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/hustle.test.ts b/src/test/abilities/hustle.test.ts index dde310fda2a..8f5547a5518 100644 --- a/src/test/abilities/hustle.test.ts +++ b/src/test/abilities/hustle.test.ts @@ -1,7 +1,6 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { Stat } from "#app/enums/stat.js"; -import { DamagePhase, MoveEffectPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -9,6 +8,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Abilities - Hustle", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/ice_face.test.ts b/src/test/abilities/ice_face.test.ts index cdf8d5928ee..905e0dfdaf7 100644 --- a/src/test/abilities/ice_face.test.ts +++ b/src/test/abilities/ice_face.test.ts @@ -1,5 +1,3 @@ -import { QuietFormChangePhase } from "#app/form-change-phase"; -import { MoveEffectPhase, MoveEndPhase, TurnEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +6,11 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Abilities - Ice Face", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/intimidate.test.ts b/src/test/abilities/intimidate.test.ts index 2b4c1041bfe..842b33108a3 100644 --- a/src/test/abilities/intimidate.test.ts +++ b/src/test/abilities/intimidate.test.ts @@ -7,11 +7,16 @@ import { generateStarter, getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; import { Status, StatusEffect } from "#app/data/status-effect"; import { GameModes, getGameMode } from "#app/game-mode"; -import { CommandPhase, DamagePhase, EncounterPhase, EnemyCommandPhase, SelectStarterPhase, TurnInitPhase } from "#app/phases"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Abilities - Intimidate", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/intrepid_sword.test.ts b/src/test/abilities/intrepid_sword.test.ts index dcc91421165..c1c05b59997 100644 --- a/src/test/abilities/intrepid_sword.test.ts +++ b/src/test/abilities/intrepid_sword.test.ts @@ -1,10 +1,10 @@ import { BattleStat } from "#app/data/battle-stat"; -import { CommandPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; describe("Abilities - Intrepid Sword", () => { diff --git a/src/test/abilities/libero.test.ts b/src/test/abilities/libero.test.ts index 2581eac068d..d35cb8b6e2d 100644 --- a/src/test/abilities/libero.test.ts +++ b/src/test/abilities/libero.test.ts @@ -2,7 +2,6 @@ import { allMoves } from "#app/data/move.js"; import { Type } from "#app/data/type.js"; import { Weather, WeatherType } from "#app/data/weather.js"; import { PlayerPokemon } from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; @@ -13,6 +12,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vi import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/magic_guard.test.ts b/src/test/abilities/magic_guard.test.ts index c86d65ca453..c7404f83a54 100644 --- a/src/test/abilities/magic_guard.test.ts +++ b/src/test/abilities/magic_guard.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Moves } from "#enums/moves"; import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagSide, getArenaTag } from "#app/data/arena-tag"; diff --git a/src/test/abilities/moxie.test.ts b/src/test/abilities/moxie.test.ts index f99068dea41..6550dcab526 100644 --- a/src/test/abilities/moxie.test.ts +++ b/src/test/abilities/moxie.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, VictoryPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -10,6 +9,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { VictoryPhase } from "#app/phases/victory-phase.js"; describe("Abilities - Moxie", () => { diff --git a/src/test/abilities/mycelium_might.test.ts b/src/test/abilities/mycelium_might.test.ts index d519eb67626..2fcdc28b279 100644 --- a/src/test/abilities/mycelium_might.test.ts +++ b/src/test/abilities/mycelium_might.test.ts @@ -1,4 +1,5 @@ -import { MovePhase, TurnEndPhase } from "#app/phases"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts index 182f780763c..ef0ad7785d2 100644 --- a/src/test/abilities/parental_bond.test.ts +++ b/src/test/abilities/parental_bond.test.ts @@ -2,7 +2,6 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { StatusEffect } from "#app/data/status-effect.js"; import { Type } from "#app/data/type.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { BerryPhase, CommandPhase, DamagePhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -11,6 +10,12 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/pastel_veil.test.ts b/src/test/abilities/pastel_veil.test.ts index e3d52a720b3..cb6be666d5f 100644 --- a/src/test/abilities/pastel_veil.test.ts +++ b/src/test/abilities/pastel_veil.test.ts @@ -2,13 +2,14 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { CommandPhase, TurnEndPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { StatusEffect } from "#app/data/status-effect.js"; import { allAbilities } from "#app/data/ability.js"; import { Abilities } from "#app/enums/abilities.js"; import { BattlerIndex } from "#app/battle.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Pastel Veil", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index dd8fd836e51..e6a319d229f 100644 --- a/src/test/abilities/power_construct.test.ts +++ b/src/test/abilities/power_construct.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/power_spot.test.ts b/src/test/abilities/power_spot.test.ts index 368f8a48110..467fc677ac0 100644 --- a/src/test/abilities/power_spot.test.ts +++ b/src/test/abilities/power_spot.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Power Spot", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/protean.test.ts b/src/test/abilities/protean.test.ts index 78768ce32db..ed63613945a 100644 --- a/src/test/abilities/protean.test.ts +++ b/src/test/abilities/protean.test.ts @@ -2,7 +2,6 @@ import { allMoves } from "#app/data/move.js"; import { Type } from "#app/data/type.js"; import { Weather, WeatherType } from "#app/data/weather.js"; import { PlayerPokemon } from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; @@ -13,6 +12,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vi import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/quick_draw.test.ts b/src/test/abilities/quick_draw.test.ts index 75bb9ec6a0a..6e3416b0724 100644 --- a/src/test/abilities/quick_draw.test.ts +++ b/src/test/abilities/quick_draw.test.ts @@ -1,5 +1,4 @@ import { allAbilities, BypassSpeedChanceAbAttr } from "#app/data/ability"; -import { FaintPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { FaintPhase } from "#app/phases/faint-phase.js"; describe("Abilities - Quick Draw", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/sand_veil.test.ts b/src/test/abilities/sand_veil.test.ts index 6aab362634a..010878db68d 100644 --- a/src/test/abilities/sand_veil.test.ts +++ b/src/test/abilities/sand_veil.test.ts @@ -1,7 +1,6 @@ import { BattleStatMultiplierAbAttr, allAbilities } from "#app/data/ability.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { WeatherType } from "#app/data/weather.js"; -import { CommandPhase, MoveEffectPhase, MoveEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -9,6 +8,9 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/sap_sipper.test.ts b/src/test/abilities/sap_sipper.test.ts index 6fbe57978e9..dfb4ab7e976 100644 --- a/src/test/abilities/sap_sipper.test.ts +++ b/src/test/abilities/sap_sipper.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { TerrainType } from "#app/data/terrain.js"; -import { MoveEndPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; // See also: TypeImmunityAbAttr describe("Abilities - Sap Sipper", () => { diff --git a/src/test/abilities/schooling.test.ts b/src/test/abilities/schooling.test.ts index e55b7795006..62a7e98bc76 100644 --- a/src/test/abilities/schooling.test.ts +++ b/src/test/abilities/schooling.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/screen_cleaner.test.ts b/src/test/abilities/screen_cleaner.test.ts index a73f56dd3eb..403efcce1c0 100644 --- a/src/test/abilities/screen_cleaner.test.ts +++ b/src/test/abilities/screen_cleaner.test.ts @@ -1,5 +1,4 @@ import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { PostSummonPhase, TurnEndPhase, } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Screen Cleaner", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/serene_grace.test.ts b/src/test/abilities/serene_grace.test.ts index d46587e45c7..5e4841f005a 100644 --- a/src/test/abilities/serene_grace.test.ts +++ b/src/test/abilities/serene_grace.test.ts @@ -1,6 +1,5 @@ import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -12,6 +11,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerIndex } from "#app/battle.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Abilities - Serene Grace", () => { diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index 50a0f0b63fb..33b34124cc4 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -1,6 +1,7 @@ import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, MoveEffectPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; diff --git a/src/test/abilities/shield_dust.test.ts b/src/test/abilities/shield_dust.test.ts index f1534551e92..b40689a180a 100644 --- a/src/test/abilities/shield_dust.test.ts +++ b/src/test/abilities/shield_dust.test.ts @@ -1,6 +1,7 @@ import { applyAbAttrs, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, MoveEffectPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; diff --git a/src/test/abilities/shields_down.test.ts b/src/test/abilities/shields_down.test.ts index 4d85e8aa47c..e07c12ebb63 100644 --- a/src/test/abilities/shields_down.test.ts +++ b/src/test/abilities/shields_down.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/stall.test.ts b/src/test/abilities/stall.test.ts index 44519064300..5410d2e953e 100644 --- a/src/test/abilities/stall.test.ts +++ b/src/test/abilities/stall.test.ts @@ -1,4 +1,3 @@ -import { MovePhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -6,6 +5,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MovePhase } from "#app/phases/move-phase.js"; describe("Abilities - Stall", () => { diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts index 5d5514bc3a1..3ca1a55ebee 100644 --- a/src/test/abilities/steely_spirit.test.ts +++ b/src/test/abilities/steely_spirit.test.ts @@ -1,7 +1,6 @@ import { allAbilities } from "#app/data/ability.js"; import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase, SelectTargetPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -9,6 +8,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; describe("Abilities - Steely Spirit", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/sturdy.test.ts b/src/test/abilities/sturdy.test.ts index 4caa7b0bd14..602b2c04eb1 100644 --- a/src/test/abilities/sturdy.test.ts +++ b/src/test/abilities/sturdy.test.ts @@ -1,5 +1,4 @@ import { EnemyPokemon } from "#app/field/pokemon.js"; -import { DamagePhase, MoveEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/sweet_veil.test.ts b/src/test/abilities/sweet_veil.test.ts index d650455664f..8ab384ae59e 100644 --- a/src/test/abilities/sweet_veil.test.ts +++ b/src/test/abilities/sweet_veil.test.ts @@ -2,13 +2,15 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { CommandPhase, MovePhase, TurnEndPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Abilities } from "#app/enums/abilities.js"; import { BattlerIndex } from "#app/battle.js"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Abilities - Sweet Veil", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/unseen_fist.test.ts b/src/test/abilities/unseen_fist.test.ts index a6cad8b03ce..7d47d73bb16 100644 --- a/src/test/abilities/unseen_fist.test.ts +++ b/src/test/abilities/unseen_fist.test.ts @@ -1,4 +1,3 @@ -import { TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -6,6 +5,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/volt_absorb.test.ts b/src/test/abilities/volt_absorb.test.ts index 985459e133b..0e3d5c9792f 100644 --- a/src/test/abilities/volt_absorb.test.ts +++ b/src/test/abilities/volt_absorb.test.ts @@ -1,5 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/wind_power.test.ts b/src/test/abilities/wind_power.test.ts index 670544a89ef..24f01cceebc 100644 --- a/src/test/abilities/wind_power.test.ts +++ b/src/test/abilities/wind_power.test.ts @@ -1,5 +1,5 @@ import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/wind_rider.test.ts b/src/test/abilities/wind_rider.test.ts index e27349efe41..92c38507e4f 100644 --- a/src/test/abilities/wind_rider.test.ts +++ b/src/test/abilities/wind_rider.test.ts @@ -1,5 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/wonder_skin.test.ts b/src/test/abilities/wonder_skin.test.ts index a2815152df6..d6e2b2443c4 100644 --- a/src/test/abilities/wonder_skin.test.ts +++ b/src/test/abilities/wonder_skin.test.ts @@ -1,6 +1,6 @@ import { allAbilities } from "#app/data/ability.js"; import { allMoves } from "#app/data/move.js"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/zen_mode.test.ts b/src/test/abilities/zen_mode.test.ts index 1bc7a6af4ce..72fdc5442c5 100644 --- a/src/test/abilities/zen_mode.test.ts +++ b/src/test/abilities/zen_mode.test.ts @@ -1,7 +1,5 @@ import { Stat } from "#app/data/pokemon-stat"; import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase"; -import { CommandPhase, DamagePhase, EnemyCommandPhase, MessagePhase, PostSummonPhase, SwitchPhase, SwitchSummonPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -12,6 +10,17 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { BattlerIndex } from "#app/battle.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/abilities/zero_to_hero.test.ts b/src/test/abilities/zero_to_hero.test.ts index 7924b30eb76..ee6c07096a8 100644 --- a/src/test/abilities/zero_to_hero.test.ts +++ b/src/test/abilities/zero_to_hero.test.ts @@ -1,6 +1,6 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; -import { QuietFormChangePhase } from "#app/form-change-phase.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/arena/arena_gravity.test.ts b/src/test/arena/arena_gravity.test.ts index 66d6994fb80..68c31258454 100644 --- a/src/test/arena/arena_gravity.test.ts +++ b/src/test/arena/arena_gravity.test.ts @@ -1,13 +1,14 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Arena - Gravity", () => { let phaserGame: Phaser.Game; diff --git a/src/test/arena/weather_fog.test.ts b/src/test/arena/weather_fog.test.ts index e5718b73a3c..350007ae943 100644 --- a/src/test/arena/weather_fog.test.ts +++ b/src/test/arena/weather_fog.test.ts @@ -1,7 +1,7 @@ import { allMoves } from "#app/data/move.js"; import { WeatherType } from "#app/data/weather.js"; import { Abilities } from "#app/enums/abilities.js"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; diff --git a/src/test/arena/weather_strong_winds.test.ts b/src/test/arena/weather_strong_winds.test.ts index d9f626cfb83..79fba30c019 100644 --- a/src/test/arena/weather_strong_winds.test.ts +++ b/src/test/arena/weather_strong_winds.test.ts @@ -1,5 +1,4 @@ import { allMoves } from "#app/data/move.js"; -import { TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Weather - Strong Winds", () => { let phaserGame: Phaser.Game; diff --git a/src/test/battle/battle-order.test.ts b/src/test/battle/battle-order.test.ts index 6aa919186b4..208b921b843 100644 --- a/src/test/battle/battle-order.test.ts +++ b/src/test/battle/battle-order.test.ts @@ -1,5 +1,4 @@ import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, SelectTargetPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -11,6 +10,10 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Battle order", () => { diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts index a4713f90506..43d8ddce4b0 100644 --- a/src/test/battle/battle.test.ts +++ b/src/test/battle/battle.test.ts @@ -2,21 +2,6 @@ import { allSpecies } from "#app/data/pokemon-species"; import { TempBattleStat } from "#app/data/temp-battle-stat.js"; import { GameModes } from "#app/game-mode"; import { getGameMode } from "#app/game-mode.js"; -import { - BattleEndPhase, - CommandPhase, DamagePhase, - EncounterPhase, - EnemyCommandPhase, - LoginPhase, - NextEncounterPhase, - SelectGenderPhase, - SelectModifierPhase, - SelectStarterPhase, - SummonPhase, - SwitchPhase, - TitlePhase, - TurnInitPhase, VictoryPhase, -} from "#app/phases"; import GameManager from "#app/test/utils/gameManager"; import { generateStarter, getMovePosition, } from "#app/test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -28,6 +13,21 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { NextEncounterPhase } from "#app/phases/next-encounter-phase.js"; +import { SelectGenderPhase } from "#app/phases/select-gender-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { SummonPhase } from "#app/phases/summon-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { VictoryPhase } from "#app/phases/victory-phase.js"; describe("Test Battle Phase", () => { let phaserGame: Phaser.Game; diff --git a/src/test/battle/double_battle.test.ts b/src/test/battle/double_battle.test.ts index 76b7defe33d..d2ee3812b3e 100644 --- a/src/test/battle/double_battle.test.ts +++ b/src/test/battle/double_battle.test.ts @@ -1,4 +1,3 @@ -import { BattleEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition, } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -7,6 +6,8 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; import { Status, StatusEffect } from "#app/data/status-effect.js"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Double Battles", () => { let phaserGame: Phaser.Game; diff --git a/src/test/battle/special_battle.test.ts b/src/test/battle/special_battle.test.ts index 6130df703f5..9b0fd1b3ab1 100644 --- a/src/test/battle/special_battle.test.ts +++ b/src/test/battle/special_battle.test.ts @@ -1,4 +1,4 @@ -import { CommandPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; import GameManager from "#test/utils/gameManager"; import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index 369a84e21fa..a69b45cdfd2 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -2,9 +2,9 @@ import { describe, expect, it, vi } from "vitest"; import Pokemon from "#app/field/pokemon.js"; import BattleScene from "#app/battle-scene.js"; import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags.js"; -import { StatChangePhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; vi.mock("#app/battle-scene.js"); diff --git a/src/test/battlerTags/stockpiling.test.ts b/src/test/battlerTags/stockpiling.test.ts index 005f1e1593c..1a39d11e1bd 100644 --- a/src/test/battlerTags/stockpiling.test.ts +++ b/src/test/battlerTags/stockpiling.test.ts @@ -2,9 +2,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import Pokemon, { PokemonSummonData } from "#app/field/pokemon.js"; import BattleScene from "#app/battle-scene.js"; import { StockpilingTag } from "#app/data/battler-tags.js"; -import { StatChangePhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import * as messages from "#app/messages.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; beforeEach(() => { vi.spyOn(messages, "getPokemonNameWithAffix").mockImplementation(() => ""); diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index 40ef81fed73..ecf144c96c5 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -4,11 +4,13 @@ import { Abilities } from "#app/enums/abilities.js"; import { BerryType } from "#app/enums/berry-type.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { CommandPhase, MoveEndPhase, SelectTargetPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; const TIMEOUT = 20 * 1000; // 20 seconds diff --git a/src/test/items/leek.test.ts b/src/test/items/leek.test.ts index 4abc470c6f0..1e46bda9f0f 100644 --- a/src/test/items/leek.test.ts +++ b/src/test/items/leek.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { CritBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import * as Utils from "#app/utils"; import { Moves } from "#enums/moves"; diff --git a/src/test/items/leftovers.test.ts b/src/test/items/leftovers.test.ts index e791c4426a1..1a1c95ad9e6 100644 --- a/src/test/items/leftovers.test.ts +++ b/src/test/items/leftovers.test.ts @@ -1,4 +1,3 @@ -import { DamagePhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -6,6 +5,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Items - Leftovers", () => { diff --git a/src/test/items/lock_capsule.test.ts b/src/test/items/lock_capsule.test.ts index 32103a6d780..0909e51ea2c 100644 --- a/src/test/items/lock_capsule.test.ts +++ b/src/test/items/lock_capsule.test.ts @@ -4,8 +4,8 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { Abilities } from "#app/enums/abilities.js"; import { Moves } from "#app/enums/moves.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { SelectModifierPhase } from "#app/phases.js"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; describe("Items - Lock Capsule", () => { let phaserGame: Phaser.Game; diff --git a/src/test/items/scope_lens.test.ts b/src/test/items/scope_lens.test.ts index 4efc7ab9d05..fa605ca7129 100644 --- a/src/test/items/scope_lens.test.ts +++ b/src/test/items/scope_lens.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { CritBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import * as Utils from "#app/utils"; import { Moves } from "#enums/moves"; diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 69f55cb2bbc..dc54a5a1c36 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -1,5 +1,4 @@ import { StatusEffect } from "#app/data/status-effect"; -import { CommandPhase, EnemyCommandPhase, MessagePhase, TurnEndPhase } from "#app/phases"; import i18next, { initI18n } from "#app/plugins/i18n"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; @@ -10,6 +9,10 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Items - Toxic orb", () => { diff --git a/src/test/moves/astonish.test.ts b/src/test/moves/astonish.test.ts index 358e4a9bec3..21a82f09d33 100644 --- a/src/test/moves/astonish.test.ts +++ b/src/test/moves/astonish.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { BerryPhase, CommandPhase, MoveEndPhase, TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -8,6 +7,10 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index a10c9b6b60a..5429efec2bf 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -4,7 +4,7 @@ import { WeatherType } from "#app/data/weather.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import Pokemon from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { NumberHolder } from "#app/utils.js"; diff --git a/src/test/moves/baton_pass.test.ts b/src/test/moves/baton_pass.test.ts index 9f0cb3619b2..790eddbf45c 100644 --- a/src/test/moves/baton_pass.test.ts +++ b/src/test/moves/baton_pass.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { PostSummonPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#app/test/utils/gameManager"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -7,6 +6,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Baton Pass", () => { diff --git a/src/test/moves/beak_blast.test.ts b/src/test/moves/beak_blast.test.ts index 61a022ac9eb..8938b4c7af8 100644 --- a/src/test/moves/beak_blast.test.ts +++ b/src/test/moves/beak_blast.test.ts @@ -5,9 +5,11 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { BerryPhase, MovePhase, TurnEndPhase } from "#app/phases"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { StatusEffect } from "#app/enums/status-effect.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/beat_up.test.ts b/src/test/moves/beat_up.test.ts index a5e4b3cbd34..a0f168ea30f 100644 --- a/src/test/moves/beat_up.test.ts +++ b/src/test/moves/beat_up.test.ts @@ -5,8 +5,8 @@ import { Species } from "#app/enums/species.js"; import { Moves } from "#app/enums/moves.js"; import { Abilities } from "#app/enums/abilities.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { MoveEffectPhase } from "#app/phases.js"; import { StatusEffect } from "#app/enums/status-effect.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; const TIMEOUT = 20 * 1000; // 20 sec timeout diff --git a/src/test/moves/belly_drum.test.ts b/src/test/moves/belly_drum.test.ts index 74afc910faf..e579a4587ad 100644 --- a/src/test/moves/belly_drum.test.ts +++ b/src/test/moves/belly_drum.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts index c5ce8375102..c8291a99b59 100644 --- a/src/test/moves/ceaseless_edge.test.ts +++ b/src/test/moves/ceaseless_edge.test.ts @@ -2,13 +2,14 @@ import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/clangorous_soul.test.ts b/src/test/moves/clangorous_soul.test.ts index 5493466ba56..5b2e8b6e06d 100644 --- a/src/test/moves/clangorous_soul.test.ts +++ b/src/test/moves/clangorous_soul.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/crafty_shield.test.ts b/src/test/moves/crafty_shield.test.ts index de2829aacf6..c3e50bc52c2 100644 --- a/src/test/moves/crafty_shield.test.ts +++ b/src/test/moves/crafty_shield.test.ts @@ -5,9 +5,10 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/double_team.test.ts b/src/test/moves/double_team.test.ts index 2153b856517..1c89d5b6350 100644 --- a/src/test/moves/double_team.test.ts +++ b/src/test/moves/double_team.test.ts @@ -1,6 +1,6 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { Abilities } from "#app/enums/abilities.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/dragon_rage.test.ts b/src/test/moves/dragon_rage.test.ts index 6ec7521f678..8a27f4006f4 100644 --- a/src/test/moves/dragon_rage.test.ts +++ b/src/test/moves/dragon_rage.test.ts @@ -3,7 +3,7 @@ import { Type } from "#app/data/type"; import { Species } from "#app/enums/species.js"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/dragon_tail.test.ts b/src/test/moves/dragon_tail.test.ts index 7374451e643..28c47a83454 100644 --- a/src/test/moves/dragon_tail.test.ts +++ b/src/test/moves/dragon_tail.test.ts @@ -1,6 +1,5 @@ import { allMoves } from "#app/data/move.js"; import { SPLASH_ONLY } from "../utils/testUtils"; -import { BattleEndPhase, BerryPhase, TurnEndPhase} from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -9,6 +8,9 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vi import GameManager from "../utils/gameManager"; import { getMovePosition } from "../utils/gameManagerUtils"; import { BattlerIndex } from "#app/battle.js"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/dynamax_cannon.test.ts b/src/test/moves/dynamax_cannon.test.ts index 57846c1aef7..5e81241ef46 100644 --- a/src/test/moves/dynamax_cannon.test.ts +++ b/src/test/moves/dynamax_cannon.test.ts @@ -1,12 +1,14 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { DamagePhase, MoveEffectPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Moves - Dynamax Cannon", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/fillet_away.test.ts b/src/test/moves/fillet_away.test.ts index 6965ced46d9..fcad704ef29 100644 --- a/src/test/moves/fillet_away.test.ts +++ b/src/test/moves/fillet_away.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/fissure.test.ts b/src/test/moves/fissure.test.ts index 979bc40646c..65d692a5cc1 100644 --- a/src/test/moves/fissure.test.ts +++ b/src/test/moves/fissure.test.ts @@ -1,7 +1,6 @@ import { BattleStat } from "#app/data/battle-stat"; import { Species } from "#app/enums/species.js"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { DamagePhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import { Moves } from "#enums/moves"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Fissure", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/flame_burst.test.ts b/src/test/moves/flame_burst.test.ts index 0f9e311ca86..d6679f921df 100644 --- a/src/test/moves/flame_burst.test.ts +++ b/src/test/moves/flame_burst.test.ts @@ -2,12 +2,13 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; -import { SelectTargetPhase, TurnEndPhase } from "#app/phases"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#app/enums/abilities.js"; import { allAbilities } from "#app/data/ability.js"; import Pokemon from "#app/field/pokemon.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Flame Burst", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/flower_shield.test.ts b/src/test/moves/flower_shield.test.ts index 7ca5fb8bc62..9001e8ceacb 100644 --- a/src/test/moves/flower_shield.test.ts +++ b/src/test/moves/flower_shield.test.ts @@ -2,7 +2,7 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { SemiInvulnerableTag } from "#app/data/battler-tags.js"; import { Type } from "#app/data/type.js"; import { Biome } from "#app/enums/biome.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/focus_punch.test.ts b/src/test/moves/focus_punch.test.ts index f5cf85ffae0..385234f0b71 100644 --- a/src/test/moves/focus_punch.test.ts +++ b/src/test/moves/focus_punch.test.ts @@ -5,8 +5,12 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { BerryPhase, MessagePhase, MoveHeaderPhase, SwitchSummonPhase, TurnStartPhase } from "#app/phases"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { MoveHeaderPhase } from "#app/phases/move-header-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/follow_me.test.ts b/src/test/moves/follow_me.test.ts index 420dd7e0762..a0fff9afbf8 100644 --- a/src/test/moves/follow_me.test.ts +++ b/src/test/moves/follow_me.test.ts @@ -1,13 +1,15 @@ import { BattlerIndex } from "#app/battle.js"; import { Stat } from "#app/data/pokemon-stat"; import { Abilities } from "#app/enums/abilities.js"; -import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/foresight.test.ts b/src/test/moves/foresight.test.ts index 3fef1569eba..91d3e3c37e0 100644 --- a/src/test/moves/foresight.test.ts +++ b/src/test/moves/foresight.test.ts @@ -5,7 +5,7 @@ import { Species } from "#app/enums/species.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { Moves } from "#app/enums/moves.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { MoveEffectPhase } from "#app/phases.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Moves - Foresight", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/freezy_frost.test.ts b/src/test/moves/freezy_frost.test.ts index 3ccd31bd29e..b4c30279c21 100644 --- a/src/test/moves/freezy_frost.test.ts +++ b/src/test/moves/freezy_frost.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat"; -import { MoveEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; import { allMoves } from "#app/data/move.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Freezy Frost", () => { describe("integration tests", () => { diff --git a/src/test/moves/fusion_flare.test.ts b/src/test/moves/fusion_flare.test.ts index 9ae42e7977f..aa38357ddd3 100644 --- a/src/test/moves/fusion_flare.test.ts +++ b/src/test/moves/fusion_flare.test.ts @@ -1,11 +1,11 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { TurnStartPhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { StatusEffect } from "#app/data/status-effect"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; describe("Moves - Fusion Flare", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/fusion_flare_bolt.test.ts b/src/test/moves/fusion_flare_bolt.test.ts index c2214d5442b..1b95062ee81 100644 --- a/src/test/moves/fusion_flare_bolt.test.ts +++ b/src/test/moves/fusion_flare_bolt.test.ts @@ -1,13 +1,16 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { MoveEffectPhase, MovePhase, MoveEndPhase, DamagePhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Stat } from "#app/data/pokemon-stat"; import { allMoves } from "#app/data/move"; import { BattlerIndex } from "#app/battle"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; describe("Moves - Fusion Flare and Fusion Bolt", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/glaive_rush.test.ts b/src/test/moves/glaive_rush.test.ts index b9c9d2199d3..f97ba1f0367 100644 --- a/src/test/moves/glaive_rush.test.ts +++ b/src/test/moves/glaive_rush.test.ts @@ -1,12 +1,13 @@ import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; -import { DamagePhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Glaive Rush", () => { diff --git a/src/test/moves/growth.test.ts b/src/test/moves/growth.test.ts index bfa3cc54896..0c60bb723f4 100644 --- a/src/test/moves/growth.test.ts +++ b/src/test/moves/growth.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -10,6 +9,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Growth", () => { diff --git a/src/test/moves/hard_press.test.ts b/src/test/moves/hard_press.test.ts index baf63a1ad23..255b9f1f4b1 100644 --- a/src/test/moves/hard_press.test.ts +++ b/src/test/moves/hard_press.test.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/move.js"; -import { MoveEffectPhase } from "#app/phases"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/haze.test.ts b/src/test/moves/haze.test.ts index 092575b8000..d5e3efcbd9d 100644 --- a/src/test/moves/haze.test.ts +++ b/src/test/moves/haze.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat"; -import { MoveEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Haze", () => { describe("integration tests", () => { diff --git a/src/test/moves/hyper_beam.test.ts b/src/test/moves/hyper_beam.test.ts index f33ce4f5478..ac8075081fb 100644 --- a/src/test/moves/hyper_beam.test.ts +++ b/src/test/moves/hyper_beam.test.ts @@ -3,11 +3,12 @@ import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { BerryPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; // 20 sec timeout for all tests diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 9de1f8c492b..4577ffc574a 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -3,7 +3,7 @@ import Move, { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import Pokemon from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { NumberHolder } from "#app/utils.js"; diff --git a/src/test/moves/lucky_chant.test.ts b/src/test/moves/lucky_chant.test.ts index 1232ce9ffc3..643a5eddb00 100644 --- a/src/test/moves/lucky_chant.test.ts +++ b/src/test/moves/lucky_chant.test.ts @@ -4,8 +4,9 @@ import { getMovePosition } from "../utils/gameManagerUtils"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; import { Abilities } from "#app/enums/abilities.js"; -import { BerryPhase, TurnEndPhase } from "#app/phases.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/magnet_rise.test.ts b/src/test/moves/magnet_rise.test.ts index 9b3c6c457e2..4ab32b5d048 100644 --- a/src/test/moves/magnet_rise.test.ts +++ b/src/test/moves/magnet_rise.test.ts @@ -1,9 +1,10 @@ -import { CommandPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Magnet Rise", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/make_it_rain.test.ts b/src/test/moves/make_it_rain.test.ts index a4440401c4b..5b0a8c6d62a 100644 --- a/src/test/moves/make_it_rain.test.ts +++ b/src/test/moves/make_it_rain.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat.js"; -import { MoveEndPhase, StatChangePhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -8,6 +7,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/mat_block.test.ts b/src/test/moves/mat_block.test.ts index 3a4d23d1497..27a55cab289 100644 --- a/src/test/moves/mat_block.test.ts +++ b/src/test/moves/mat_block.test.ts @@ -5,8 +5,10 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase, TurnEndPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/miracle_eye.test.ts b/src/test/moves/miracle_eye.test.ts index 45de8b7e4fb..3e1e151e7d4 100644 --- a/src/test/moves/miracle_eye.test.ts +++ b/src/test/moves/miracle_eye.test.ts @@ -5,8 +5,8 @@ import { Species } from "#app/enums/species.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { Moves } from "#app/enums/moves.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { MoveEffectPhase } from "#app/phases.js"; import { BattlerIndex } from "#app/battle.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; describe("Moves - Miracle Eye", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/multi_target.test.ts b/src/test/moves/multi_target.test.ts index 4cb2dfb764d..6e8a7c99e9b 100644 --- a/src/test/moves/multi_target.test.ts +++ b/src/test/moves/multi_target.test.ts @@ -1,7 +1,7 @@ import { getMoveTargets } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { Species } from "#app/enums/species.js"; -import { TurnEndPhase } from "#app/phases.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { Moves } from "#enums/moves"; import Phaser from "phaser"; diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts index 8988109f431..fcd68446eff 100644 --- a/src/test/moves/octolock.test.ts +++ b/src/test/moves/octolock.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat"; import { TrappedTag } from "#app/data/battler-tags.js"; -import { CommandPhase, MoveEndPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,9 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Octolock", () => { describe("integration tests", () => { diff --git a/src/test/moves/parting_shot.test.ts b/src/test/moves/parting_shot.test.ts index b8b0faba4ce..32995d2d563 100644 --- a/src/test/moves/parting_shot.test.ts +++ b/src/test/moves/parting_shot.test.ts @@ -1,5 +1,4 @@ import { SPLASH_ONLY } from "../utils/testUtils"; -import { BerryPhase, MessagePhase, TurnInitPhase, FaintPhase } from "#app/phases"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -8,6 +7,10 @@ import { afterEach, beforeAll, beforeEach, describe, expect, test, it } from "vi import GameManager from "../utils/gameManager"; import { getMovePosition } from "../utils/gameManagerUtils"; import { BattleStat } from "#app/data/battle-stat"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/protect.test.ts b/src/test/moves/protect.test.ts index 34e208e0914..4d97ef5ce82 100644 --- a/src/test/moves/protect.test.ts +++ b/src/test/moves/protect.test.ts @@ -5,10 +5,10 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; import { allMoves } from "#app/data/move.js"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/purify.test.ts b/src/test/moves/purify.test.ts index 7959927d63f..3020e4b47ac 100644 --- a/src/test/moves/purify.test.ts +++ b/src/test/moves/purify.test.ts @@ -1,6 +1,5 @@ import { Status, StatusEffect } from "#app/data/status-effect.js"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon.js"; -import { MoveEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,7 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { BattlerIndex } from "#app/battle.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/quick_guard.test.ts b/src/test/moves/quick_guard.test.ts index 58165f3d916..8bf647f2027 100644 --- a/src/test/moves/quick_guard.test.ts +++ b/src/test/moves/quick_guard.test.ts @@ -5,8 +5,9 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/rage_powder.test.ts b/src/test/moves/rage_powder.test.ts index 92cdcc9b4f7..17b687feead 100644 --- a/src/test/moves/rage_powder.test.ts +++ b/src/test/moves/rage_powder.test.ts @@ -1,5 +1,4 @@ import { BattlerIndex } from "#app/battle.js"; -import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -7,6 +6,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index f5ea489a75e..79dd4f8202b 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -3,7 +3,7 @@ import Move, { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import Pokemon from "#app/field/pokemon.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { NumberHolder } from "#app/utils.js"; diff --git a/src/test/moves/rollout.test.ts b/src/test/moves/rollout.test.ts index 728fe1ecd45..1fc208c6724 100644 --- a/src/test/moves/rollout.test.ts +++ b/src/test/moves/rollout.test.ts @@ -1,5 +1,5 @@ import { allMoves } from "#app/data/move.js"; -import { CommandPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/roost.test.ts b/src/test/moves/roost.test.ts index a9036dcb478..c40bb18cdb1 100644 --- a/src/test/moves/roost.test.ts +++ b/src/test/moves/roost.test.ts @@ -2,11 +2,12 @@ import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Moves } from "#app/enums/moves.js"; import { Species } from "#app/enums/species.js"; -import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/shell_trap.test.ts b/src/test/moves/shell_trap.test.ts index b027541c252..c600b1ee1cc 100644 --- a/src/test/moves/shell_trap.test.ts +++ b/src/test/moves/shell_trap.test.ts @@ -6,9 +6,11 @@ import { Species } from "#app/enums/species.js"; import { allMoves } from "#app/data/move.js"; import { BattlerIndex } from "#app/battle.js"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, MoveEndPhase, MovePhase } from "#app/phases.js"; import { SPLASH_ONLY } from "../utils/testUtils"; import { MoveResult } from "#app/field/pokemon.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/spikes.test.ts b/src/test/moves/spikes.test.ts index bbbb3347580..ae3c676b893 100644 --- a/src/test/moves/spikes.test.ts +++ b/src/test/moves/spikes.test.ts @@ -1,4 +1,4 @@ -import { CommandPhase } from "#app/phases"; +import { CommandPhase } from "#app/phases/command-phase.js"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts index ec0a53028ff..51d84a5e151 100644 --- a/src/test/moves/spit_up.test.ts +++ b/src/test/moves/spit_up.test.ts @@ -3,7 +3,6 @@ import { StockpilingTag } from "#app/data/battler-tags.js"; import { allMoves } from "#app/data/move.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js"; -import { MovePhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -11,6 +10,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Spit Up", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/spotlight.test.ts b/src/test/moves/spotlight.test.ts index 0893ba975d7..40ab78471ae 100644 --- a/src/test/moves/spotlight.test.ts +++ b/src/test/moves/spotlight.test.ts @@ -1,12 +1,14 @@ import { BattlerIndex } from "#app/battle.js"; import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts index 375eeab3c95..0b208e20f81 100644 --- a/src/test/moves/stockpile.test.ts +++ b/src/test/moves/stockpile.test.ts @@ -1,7 +1,6 @@ import { BattleStat } from "#app/data/battle-stat"; import { StockpilingTag } from "#app/data/battler-tags.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js"; -import { CommandPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -10,6 +9,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Stockpile", () => { describe("integration tests", () => { diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts index aed30445fd2..6a054393acc 100644 --- a/src/test/moves/swallow.test.ts +++ b/src/test/moves/swallow.test.ts @@ -2,7 +2,6 @@ import { BattleStat } from "#app/data/battle-stat"; import { StockpilingTag } from "#app/data/battler-tags.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js"; -import { MovePhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -10,6 +9,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Swallow", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/tackle.test.ts b/src/test/moves/tackle.test.ts index 512b23ae363..f442645baa9 100644 --- a/src/test/moves/tackle.test.ts +++ b/src/test/moves/tackle.test.ts @@ -1,5 +1,4 @@ import { Stat } from "#app/data/pokemon-stat"; -import { CommandPhase, EnemyCommandPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -8,6 +7,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Tackle", () => { diff --git a/src/test/moves/tail_whip.test.ts b/src/test/moves/tail_whip.test.ts index 7630b31f7de..ba4a7459094 100644 --- a/src/test/moves/tail_whip.test.ts +++ b/src/test/moves/tail_whip.test.ts @@ -1,5 +1,4 @@ import { BattleStat } from "#app/data/battle-stat"; -import { CommandPhase, EnemyCommandPhase, TurnInitPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Command } from "#app/ui/command-ui-handler"; @@ -9,6 +8,9 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; describe("Moves - Tail whip", () => { diff --git a/src/test/moves/tailwind.test.ts b/src/test/moves/tailwind.test.ts index e32e10a4290..115a97f3be4 100644 --- a/src/test/moves/tailwind.test.ts +++ b/src/test/moves/tailwind.test.ts @@ -1,7 +1,7 @@ import { ArenaTagSide } from "#app/data/arena-tag.js"; import { Stat } from "#app/data/pokemon-stat.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { TurnEndPhase } from "#app/phases"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index 84a71ee5256..d72f3ed3fac 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -1,12 +1,13 @@ import { Abilities } from "#app/enums/abilities.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; -import { BerryPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/moves/tidy_up.test.ts b/src/test/moves/tidy_up.test.ts index e35a438c562..64a63df08df 100644 --- a/src/test/moves/tidy_up.test.ts +++ b/src/test/moves/tidy_up.test.ts @@ -1,6 +1,5 @@ import { BattleStat } from "#app/data/battle-stat.js"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; -import { MoveEndPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -9,6 +8,8 @@ import { Species } from "#enums/species"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { SPLASH_ONLY } from "#test/utils/testUtils"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - Tidy Up", () => { diff --git a/src/test/moves/u_turn.test.ts b/src/test/moves/u_turn.test.ts index 2c12a4da43b..b93f997c487 100644 --- a/src/test/moves/u_turn.test.ts +++ b/src/test/moves/u_turn.test.ts @@ -1,5 +1,4 @@ import { Abilities } from "#app/enums/abilities.js"; -import { SwitchPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#app/test/utils/gameManager"; import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { Moves } from "#enums/moves"; @@ -8,6 +7,8 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { StatusEffect } from "#app/enums/status-effect.js"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; describe("Moves - U-turn", () => { let phaserGame: Phaser.Game; diff --git a/src/test/moves/wide_guard.test.ts b/src/test/moves/wide_guard.test.ts index 94f382022c2..1f22428de4b 100644 --- a/src/test/moves/wide_guard.test.ts +++ b/src/test/moves/wide_guard.test.ts @@ -5,8 +5,9 @@ import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { getMovePosition } from "../utils/gameManagerUtils"; -import { BerryPhase, CommandPhase } from "#app/phases.js"; import { BattleStat } from "#app/data/battle-stat.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; const TIMEOUT = 20 * 1000; diff --git a/src/test/phases/phases.test.ts b/src/test/phases/phases.test.ts index c61eb1d41b8..2ed1e48c706 100644 --- a/src/test/phases/phases.test.ts +++ b/src/test/phases/phases.test.ts @@ -1,9 +1,11 @@ import BattleScene from "#app/battle-scene.js"; -import { LoginPhase, TitlePhase, UnavailablePhase } from "#app/phases.js"; import { Mode } from "#app/ui/ui.js"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { UnavailablePhase } from "#app/phases/unavailable-phase.js"; describe("Phases", () => { let phaserGame: Phaser.Game; diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index 020b26b7f66..dbbdb1999b9 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; -import { EncounterPhase, SelectStarterPhase, TitlePhase } from "#app/phases"; import { Mode } from "#app/ui/ui"; import { GameModes } from "#app/game-mode"; import StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; @@ -14,6 +13,9 @@ import { Nature} from "#app/data/nature"; import { Button } from "#enums/buttons"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; describe("UI - Starter select", () => { diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index 9315971e484..21aed9b5b87 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -2,7 +2,6 @@ import { BerryType } from "#app/enums/berry-type"; import { Button } from "#app/enums/buttons"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { BattleEndPhase, SelectModifierPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; @@ -11,6 +10,8 @@ import Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; describe("UI - Transfer Items", () => { diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts index eb0191812e8..f93260f15b7 100644 --- a/src/test/ui/type-hints.test.ts +++ b/src/test/ui/type-hints.test.ts @@ -1,7 +1,6 @@ import { Button } from "#app/enums/buttons.js"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { CommandPhase } from "#app/phases"; import FightUiHandler from "#app/ui/fight-ui-handler.js"; import { Mode } from "#app/ui/ui.js"; import GameManager from "#test/utils/gameManager"; @@ -9,6 +8,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import MockText from "../utils/mocks/mocksContainer/mockText"; import { SPLASH_ONLY } from "../utils/testUtils"; +import { CommandPhase } from "#app/phases/command-phase.js"; describe("UI - Type Hints", () => { let phaserGame: Phaser.Game; diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 6333179e3b2..d60cbd62836 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -1,7 +1,6 @@ import GameWrapper from "#test/utils/gameWrapper"; import { Mode } from "#app/ui/ui"; import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils"; -import { CommandPhase, EncounterPhase, FaintPhase, LoginPhase, MovePhase, NewBattlePhase, SelectStarterPhase, SelectTargetPhase, TitlePhase, TurnEndPhase, TurnInitPhase, TurnStartPhase } from "#app/phases"; import BattleScene from "#app/battle-scene.js"; import PhaseInterceptor from "#test/utils/phaseInterceptor"; import TextInterceptor from "#test/utils/TextInterceptor"; @@ -31,6 +30,18 @@ import { vi } from "vitest"; import { ClassicModeHelper } from "./helpers/classicModeHelper"; import { DailyModeHelper } from "./helpers/dailyModeHelper"; import { SettingsHelper } from "./helpers/settingsHelper"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { NewBattlePhase } from "#app/phases/new-battle-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; /** * Class to manage the game state and transitions between phases. diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index e6854d5bc79..cf59dd81183 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -1,7 +1,8 @@ import { Species } from "#app/enums/species.js"; import { GameModes, getGameMode } from "#app/game-mode.js"; import overrides from "#app/overrides.js"; -import { EncounterPhase, SelectStarterPhase } from "#app/phases.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; import { Mode } from "#app/ui/ui.js"; import { generateStarter } from "../gameManagerUtils"; import { GameManagerHelper } from "./gameManagerHelper"; diff --git a/src/test/utils/helpers/dailyModeHelper.ts b/src/test/utils/helpers/dailyModeHelper.ts index c83a2e587d9..a143e212fcb 100644 --- a/src/test/utils/helpers/dailyModeHelper.ts +++ b/src/test/utils/helpers/dailyModeHelper.ts @@ -1,6 +1,7 @@ import { Button } from "#app/enums/buttons.js"; import overrides from "#app/overrides.js"; -import { EncounterPhase, TitlePhase } from "#app/phases.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler.js"; import { Mode } from "#app/ui/ui.js"; import { GameManagerHelper } from "./gameManagerHelper"; diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index 9438952aa92..3179e63a6d0 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -1,6 +1,6 @@ import { vi } from "vitest"; -import { MoveEffectPhase } from "#app/phases.js"; import { GameManagerHelper } from "./gameManagerHelper"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; /** * Helper to handle a Pokemon's move diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 5a8b4ae01b2..2304d726757 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -1,44 +1,42 @@ -import { - BattleEndPhase, - BerryPhase, - CheckSwitchPhase, - CommandPhase, - DamagePhase, - EggLapsePhase, - EncounterPhase, - EnemyCommandPhase, - FaintPhase, - LoginPhase, - MessagePhase, - MoveEffectPhase, - MoveEndPhase, - MovePhase, - NewBattlePhase, - NextEncounterPhase, - PartyHealPhase, - PostSummonPhase, - SelectGenderPhase, - SelectModifierPhase, - SelectStarterPhase, - SelectTargetPhase, - ShinySparklePhase, - ShowAbilityPhase, - StatChangePhase, - SummonPhase, - SwitchPhase, - SwitchSummonPhase, - TitlePhase, - ToggleDoublePositionPhase, - TurnEndPhase, - TurnInitPhase, - TurnStartPhase, - UnavailablePhase, - VictoryPhase -} from "#app/phases"; import UI, { Mode } from "#app/ui/ui"; import { Phase } from "#app/phase"; import ErrorInterceptor from "#app/test/utils/errorInterceptor"; -import { QuietFormChangePhase } from "#app/form-change-phase"; +import { BattleEndPhase } from "#app/phases/battle-end-phase.js"; +import { BerryPhase } from "#app/phases/berry-phase.js"; +import { CheckSwitchPhase } from "#app/phases/check-switch-phase.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { DamagePhase } from "#app/phases/damage-phase.js"; +import { EggLapsePhase } from "#app/phases/egg-lapse-phase.js"; +import { EncounterPhase } from "#app/phases/encounter-phase.js"; +import { EnemyCommandPhase } from "#app/phases/enemy-command-phase.js"; +import { FaintPhase } from "#app/phases/faint-phase.js"; +import { LoginPhase } from "#app/phases/login-phase.js"; +import { MessagePhase } from "#app/phases/message-phase.js"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; +import { MoveEndPhase } from "#app/phases/move-end-phase.js"; +import { MovePhase } from "#app/phases/move-phase.js"; +import { NewBattlePhase } from "#app/phases/new-battle-phase.js"; +import { NextEncounterPhase } from "#app/phases/next-encounter-phase.js"; +import { PostSummonPhase } from "#app/phases/post-summon-phase.js"; +import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase.js"; +import { SelectGenderPhase } from "#app/phases/select-gender-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { SelectTargetPhase } from "#app/phases/select-target-phase.js"; +import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase.js"; +import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; +import { StatChangePhase } from "#app/phases/stat-change-phase.js"; +import { SummonPhase } from "#app/phases/summon-phase.js"; +import { SwitchPhase } from "#app/phases/switch-phase.js"; +import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; +import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase.js"; +import { TurnEndPhase } from "#app/phases/turn-end-phase.js"; +import { TurnInitPhase } from "#app/phases/turn-init-phase.js"; +import { TurnStartPhase } from "#app/phases/turn-start-phase.js"; +import { UnavailablePhase } from "#app/phases/unavailable-phase.js"; +import { VictoryPhase } from "#app/phases/victory-phase.js"; +import { PartyHealPhase } from "#app/phases/party-heal-phase.js"; export default class PhaseInterceptor { public scene; diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index d8b3e5e3ee8..04691886d9c 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -1,4 +1,3 @@ -import { CommandPhase } from "../phases"; import BattleScene from "../battle-scene"; import { getPokeballName } from "../data/pokeball"; import { addTextObject, getTextStyleOptions, TextStyle } from "./text"; @@ -7,6 +6,7 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import {Button} from "#enums/buttons"; +import { CommandPhase } from "#app/phases/command-phase.js"; export default class BallUiHandler extends UiHandler { private pokeballSelectContainer: Phaser.GameObjects.Container; diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index 12211fa71cc..73c47da41fe 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -5,12 +5,13 @@ import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import {Button} from "#enums/buttons"; import i18next from "i18next"; -import { SelectStarterPhase, TitlePhase } from "#app/phases.js"; import { Challenge } from "#app/data/challenge.js"; import * as Utils from "../utils"; import { Challenges } from "#app/enums/challenges.js"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Color, ShadowColor } from "#app/enums/color.js"; +import { SelectStarterPhase } from "#app/phases/select-starter-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; /** * Handles all the UI for choosing optional challenges. diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 11814a25240..b22ea5d20fc 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -1,4 +1,3 @@ -import { CommandPhase } from "../phases"; import BattleScene from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; import PartyUiHandler, { PartyUiMode } from "./party-ui-handler"; @@ -7,6 +6,7 @@ import UiHandler from "./ui-handler"; import i18next from "i18next"; import {Button} from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; export enum Command { FIGHT = 0, diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index f567861e0b7..733873b974e 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -1,8 +1,8 @@ import BattleScene from "../battle-scene"; -import { EggHatchPhase } from "../egg-hatch-phase"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import {Button} from "#enums/buttons"; +import { EggHatchPhase } from "#app/phases/egg-hatch-phase.js"; export default class EggHatchSceneHandler extends UiHandler { public eggHatchContainer: Phaser.GameObjects.Container; diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 4ade6ca5d20..71d137fbfd7 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -5,11 +5,11 @@ import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; -import { CommandPhase } from "../phases"; import { MoveCategory } from "#app/data/move.js"; import i18next from "i18next"; import {Button} from "#enums/buttons"; import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; export default class FightUiHandler extends UiHandler { public static readonly MOVES_CONTAINER_NAME = "moves"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 5e1ca7ccbb0..66c777944d1 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,3 @@ -import { CommandPhase, SelectModifierPhase } from "../phases"; import BattleScene from "../battle-scene"; import Pokemon, { MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./text"; @@ -23,6 +22,8 @@ import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages.js"; +import { CommandPhase } from "#app/phases/command-phase.js"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase.js"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 9f2df1f2329..67e870838a2 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -16,7 +16,6 @@ import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "../ import PokemonSpecies, { allSpecies, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Type } from "../data/type"; import { GameModes } from "../game-mode"; -import { SelectChallengePhase, TitlePhase } from "../phases"; import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "../system/game-data"; import { Tutorial, handleTutorial } from "../tutorial"; import * as Utils from "../utils"; @@ -44,6 +43,8 @@ import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType } import { StarterContainer } from "./starter-container"; import { DropDownColumn, FilterBar } from "./filter-bar"; import { ScrollBar } from "./scroll-bar"; +import { SelectChallengePhase } from "#app/phases/select-challenge-phase.js"; +import { TitlePhase } from "#app/phases/title-phase.js"; export type StarterSelectCallback = (starters: Starter[]) => void;