From 8410aee3a1315dac368bb3d6b59ae0caf27d5e84 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Thu, 22 May 2025 15:25:48 -0400 Subject: [PATCH 01/20] [Sprite] Fix Cacturne and Kyurem variants --- public/images/pokemon/332.png | Bin 6892 -> 6886 bytes public/images/pokemon/646.png | Bin 17027 -> 50251 bytes public/images/pokemon/back/332.png | Bin 19460 -> 19467 bytes public/images/pokemon/back/female/332.png | Bin 19460 -> 19467 bytes public/images/pokemon/female/332.png | Bin 6911 -> 6904 bytes public/images/pokemon/variant/332.json | 38 +++++++++--------- public/images/pokemon/variant/back/332.json | 36 ++++++++--------- .../pokemon/variant/back/female/332.json | 36 ++++++++--------- public/images/pokemon/variant/female/332.json | 38 +++++++++--------- 9 files changed, 76 insertions(+), 72 deletions(-) diff --git a/public/images/pokemon/332.png b/public/images/pokemon/332.png index a9979480673646722fc78640e946c91e4e45b676..44e374426b4dd37171447e1c4d155753e44a3cee 100644 GIT binary patch delta 6534 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 diff --git a/public/images/pokemon/646.png b/public/images/pokemon/646.png index 64b0af2d1518727150cbcd49eaa5747ed8f11b80..e54083bfc7318d9a27185f7b948294ba32950d8b 100644 GIT binary patch literal 50251 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 literal 17027 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 P-RnCl*`L%vjAZ>At>jlK literal 19460 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 P-RnCl*`L%vjAZ>At>jlK literal 19460 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 diff --git a/public/images/pokemon/variant/332.json b/public/images/pokemon/variant/332.json index e9b487bca25..336ef4a22f3 100644 --- a/public/images/pokemon/variant/332.json +++ b/public/images/pokemon/variant/332.json @@ -1,34 +1,36 @@ { "1": { - "319452": "831a1f", - "4a7310": "982443", - "7ba563": "b44040", - "bdef84": "ec8c8c", "8cbd63": "c54b4b", - "215200": "710f2e", + "a5d670": "df5252", + "4aa552": "9f2f2c", "a5d674": "e16363", - "196b21": "891222", + "7aa953": "c54b4b", + "7ba563": "b44040", + "215200": "710f2e", "f7ce00": "7aa1df", "525252": "123a5a", - "63b56b": "b2332f", - "a5d673": "df5252", "8c6b3a": "448bc3", - "4aa552": "9f2f2c" + "bdef84": "ec8c8c", + "63b56b": "b2332f", + "319452": "831a1f", + "196b21": "891222", + "4a7310": "982443" }, "2": { - "319452": "b08d72", - "4a7310": "4f3956", - "7ba563": "704e7e", - "bdef84": "a779ba", "8cbd63": "e3d7a6", - "215200": "583823", + "a5d670": "d7cda7", + "4aa552": "c5a77f", "a5d674": "8c669b", - "196b21": "78582c", + "7aa953": "704e7e", + "7ba563": "704e7e", + "215200": "583823", "f7ce00": "f2aacd", "525252": "a53b6f", - "63b56b": "cfc191", - "a5d673": "d7cda7", "8c6b3a": "df87bb", - "4aa552": "c5a77f" + "bdef84": "a779ba", + "63b56b": "cfc191", + "319452": "b08d72", + "196b21": "78582c", + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/332.json b/public/images/pokemon/variant/back/332.json index c13c07c34b4..fbfb3705202 100644 --- a/public/images/pokemon/variant/back/332.json +++ b/public/images/pokemon/variant/back/332.json @@ -1,28 +1,28 @@ { "1": { + "196b21": "831a1f", + "7ba563": "b44040", + "215201": "630d28", + "215200": "710f2f", + "a5d674": "df5252", + "8cbd63": "c54b4b", + "63b56b": "b2332f", + "a5d670": "e16363", "319452": "831a1f", "4aa552": "9f2f2c", - "7ba563": "b44040", - "8cbd63": "c54b4b", - "215200": "710f2f", - "196b21": "831a1f", - "a5d674": "df5252", - "4a7310": "982443", - "a5d673": "e16363", - "63b56b": "b2332f", - "215201": "630d28" + "4a7310": "982443" }, "2": { + "196b21": "b08d72", + "7ba563": "704e7e", + "215201": "583823", + "215200": "3f3249", + "a5d674": "d7cda7", + "8cbd63": "e3d7a6", + "63b56b": "cfc191", + "a5d670": "8c669b", "319452": "b08d72", "4aa552": "c5a77f", - "7ba563": "704e7e", - "8cbd63": "e3d7a6", - "215200": "3f3249", - "196b21": "b08d72", - "a5d674": "d7cda7", - "4a7310": "4f3956", - "a5d673": "8c669b", - "63b56b": "cfc191", - "215201": "583823" + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/332.json b/public/images/pokemon/variant/back/female/332.json index 9ec50cb7e92..17f8d3c5f74 100644 --- a/public/images/pokemon/variant/back/female/332.json +++ b/public/images/pokemon/variant/back/female/332.json @@ -1,28 +1,28 @@ { "1": { + "196b21": "780d4a", + "7ba563": "b44040", + "215201": "710f2e", + "215200": "710f2f", + "a5d674": "de5b6f", + "8cbd63": "bf3d64", + "63b56b": "9e2056", + "a5d670": "e16363", "319452": "780d4a", "4aa552": "8a1652", - "7ba563": "b44040", - "8cbd63": "bf3d64", - "215200": "710f2f", - "196b21": "780d4a", - "a5d674": "de5b6f", - "4a7310": "982443", - "a5d673": "e16363", - "63b56b": "9e2056", - "215201": "710f2e" + "4a7310": "982443" }, "2": { + "196b21": "b59c72", + "7ba563": "805a9c", + "215201": "694d37", + "215200": "41334d", + "a5d674": "f6f7df", + "8cbd63": "ebe9ca", + "63b56b": "e3ddb8", + "a5d670": "a473ba", "319452": "b59c72", "4aa552": "c9b991", - "7ba563": "805a9c", - "8cbd63": "ebe9ca", - "215200": "41334d", - "196b21": "b59c72", - "a5d674": "f6f7df", - "4a7310": "4f3956", - "a5d673": "a473ba", - "63b56b": "e3ddb8", - "215201": "694d37" + "4a7310": "4f3956" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/female/332.json b/public/images/pokemon/variant/female/332.json index c86429d13c4..7a1dc0f1457 100644 --- a/public/images/pokemon/variant/female/332.json +++ b/public/images/pokemon/variant/female/332.json @@ -1,34 +1,36 @@ { "1": { - "319452": "780d4a", - "4a7310": "982443", - "7ba563": "b44040", - "bdef84": "ec8c8c", "8cbd63": "bf3d64", - "215200": "710f2e", + "a5d670": "de5b6f", + "4aa552": "8a1652", "a5d674": "e16363", - "196b21": "7d1157", + "7aa953": "bf3d64", + "7ba563": "b44040", + "215200": "710f2e", "f7ce00": "5bcfc3", "525252": "20668c", - "63b56b": "9e2056", - "a5d673": "de5b6f", "8c6b3a": "33a3b0", - "4aa552": "8a1652" + "bdef84": "ec8c8c", + "63b56b": "9e2056", + "319452": "780d4a", + "196b21": "7d1157", + "4a7310": "982443" }, "2": { - "319452": "b59c72", - "4a7310": "4f3956", - "7ba563": "805a9c", - "bdef84": "c193cf", "8cbd63": "f6f7df", - "215200": "694d37", + "a5d670": "ebe9ca", + "4aa552": "c9b991", "a5d674": "a473ba", - "196b21": "9c805f", + "7aa953": "805a9c", + "7ba563": "805a9c", + "215200": "694d37", "f7ce00": "f2aab6", "525252": "983364", - "63b56b": "e3ddb8", - "a5d673": "ebe9ca", "8c6b3a": "df879f", - "4aa552": "c9b991" + "bdef84": "c193cf", + "63b56b": "e3ddb8", + "319452": "b59c72", + "196b21": "9c805f", + "4a7310": "4f3956" } } \ No newline at end of file From e053ead67cbe4c19aeadb9d61ae5bb791c7a0411 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 25 May 2025 13:37:52 -0500 Subject: [PATCH 02/20] [Bug] Fix crash caused by switching in a transformed pokemon (#5864) * Force reset summon data and load assets prior to switch in * Update src/phases/switch-summon-phase.ts --- src/phases/switch-summon-phase.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index a063b6e6863..3e9cc7718f1 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -124,6 +124,12 @@ export class SwitchSummonPhase extends SummonPhase { const switchedInPokemon: Pokemon | undefined = party[this.slotIndex]; this.lastPokemon = this.getPokemon(); + // Defensive programming: Overcome the bug where the summon data has somehow not been reset + // prior to switching in a new Pokemon. + // Force the switch to occur and load the assets for the new pokemon, ignoring override. + switchedInPokemon.resetSummonData(); + switchedInPokemon.loadAssets(true); + applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon); applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); if (!switchedInPokemon) { @@ -131,6 +137,7 @@ export class SwitchSummonPhase extends SummonPhase { return; } + if (this.switchType === SwitchType.BATON_PASS) { // If switching via baton pass, update opposing tags coming from the prior pokemon (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) => From b803f6f18a9b7a921dff2910bfd440814032bf5e Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 02:38:30 -0700 Subject: [PATCH 03/20] [i18n] Update locales (#5875) --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 42cd5cf577f..e9ccbadb6ea 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 42cd5cf577f475c22bc82d55e7ca358eb4f3184f +Subproject commit e9ccbadb6eaa3b797f3dec919745befda2ec74bd From 999cbf911e900723a433a8d949f8cfb2084421d0 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 03:56:52 -0700 Subject: [PATCH 04/20] [Bug] Fix Pichu form weights (61.5 -> 2) --- src/data/pokemon-species.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 59167ba47f6..6f05d17db6f 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1697,8 +1697,8 @@ export function initSpecies() { new PokemonSpecies(Species.CHINCHOU, 2, false, false, false, "Angler Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 0.5, 12, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 330, 75, 38, 38, 56, 56, 67, 190, 50, 66, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.LANTURN, 2, false, false, false, "Light Pokémon", PokemonType.WATER, PokemonType.ELECTRIC, 1.2, 22.5, Abilities.VOLT_ABSORB, Abilities.ILLUMINATE, Abilities.WATER_ABSORB, 460, 125, 58, 58, 76, 76, 67, 75, 50, 161, GrowthRate.SLOW, 50, false), new PokemonSpecies(Species.PICHU, 2, false, false, false, "Tiny Mouse Pokémon", PokemonType.ELECTRIC, null, 0.3, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, GrowthRate.MEDIUM_FAST, 50, false, false, - new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), - new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 61.5, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Normal", "", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), + new PokemonForm("Spiky-Eared", "spiky", PokemonType.ELECTRIC, null, 1.4, 2, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 205, 20, 40, 15, 35, 35, 60, 190, 70, 41, false, null, true), ), new PokemonSpecies(Species.CLEFFA, 2, false, false, false, "Star Shape Pokémon", PokemonType.FAIRY, null, 0.3, 3, Abilities.CUTE_CHARM, Abilities.MAGIC_GUARD, Abilities.FRIEND_GUARD, 218, 50, 25, 28, 45, 55, 15, 150, 140, 44, GrowthRate.FAST, 25, false), new PokemonSpecies(Species.IGGLYBUFF, 2, false, false, false, "Balloon Pokémon", PokemonType.NORMAL, PokemonType.FAIRY, 0.3, 1, Abilities.CUTE_CHARM, Abilities.COMPETITIVE, Abilities.FRIEND_GUARD, 210, 90, 30, 15, 40, 20, 15, 170, 50, 42, GrowthRate.FAST, 25, false), From a98f89759169201b671e1f17c973dce81af28bab Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 27 May 2025 05:02:39 -0700 Subject: [PATCH 05/20] [Bug] Fix Dipplin's weight (was mistakenly set to the lbs value) --- src/data/pokemon-species.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 6f05d17db6f..5c97f360094 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -3121,7 +3121,7 @@ export function initSpecies() { ), new PokemonSpecies(Species.WALKING_WAKE, 9, false, false, false, "Paradox Pokémon", PokemonType.WATER, PokemonType.DRAGON, 3.5, 280, Abilities.PROTOSYNTHESIS, Abilities.NONE, Abilities.NONE, 590, 99, 83, 91, 125, 83, 109, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Gouging Fire and Raging Bolt new PokemonSpecies(Species.IRON_LEAVES, 9, false, false, false, "Paradox Pokémon", PokemonType.GRASS, PokemonType.PSYCHIC, 1.5, 125, Abilities.QUARK_DRIVE, Abilities.NONE, Abilities.NONE, 590, 90, 130, 88, 70, 108, 104, 10, 0, 295, GrowthRate.SLOW, null, false), //Custom Catchrate, matching Iron Boulder and Iron Crown - new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 9.7, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), + new PokemonSpecies(Species.DIPPLIN, 9, false, false, false, "Candy Apple Pokémon", PokemonType.GRASS, PokemonType.DRAGON, 0.4, 4.4, Abilities.SUPERSWEET_SYRUP, Abilities.GLUTTONY, Abilities.STICKY_HOLD, 485, 80, 80, 110, 95, 80, 40, 45, 50, 170, GrowthRate.ERRATIC, 50, false), new PokemonSpecies(Species.POLTCHAGEIST, 9, false, false, false, "Matcha Pokémon", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, GrowthRate.SLOW, null, false, false, new PokemonForm("Counterfeit Form", "counterfeit", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, true), new PokemonForm("Artisan Form", "artisan", PokemonType.GRASS, PokemonType.GHOST, 0.1, 1.1, Abilities.HOSPITALITY, Abilities.NONE, Abilities.HEATPROOF, 308, 40, 45, 45, 74, 54, 50, 120, 50, 62, false, null, false, true), From d5789105f344abd6b6234d2b14410d03ae91e4bf Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 12:29:03 -0500 Subject: [PATCH 06/20] [Refactor][UI/UX] Cleanup battle-info ui code (#5696) * Create battle-info directory and move battle-info.ts to it * Move player and enemy battle info to their own files * Move subclass specific parts of constructor to subclass constructor * Fixup mock gameobject methods to match phaser gameobject returns * Make statOrder specific to subclass * Create getShinyDescriptor function in utils * Move icon construction to its own function * Cleanup enemybattleinfo constructor to use chaining * Make flyout exclusive to EnemyBattleInfo * Move EnemyPokemon specific init Logic to its class * Break up initInfo into different methods * Remove hp bar segment dividers from base battle info * Move setMini to pokemoninfo * Breakup updateInfo into smaller parts * Remove hp info handling from base updateInfo * Use phaser object chaining methods * Add some docs * Add missing chain usage * Use getShinyDescriptor in pokemon-info-container * Minor cleanup of updatePokemonExp * Fixup setSizeToFrame mock * Ensure pokemon hp numbers are not visible during stat display * Update src/utils/common.ts Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> * Make summary-ui-handler use new shinyDescriptor method * Remove `undefined` parameter pass Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * Address kev's review comments Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Ensure hp number display fades in/out * Ensure ribbon and caught indicator fade with stat display * Update src/ui/battle-info/battle-info.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Move construction of stats and type icons to their own methods * Make setPositionRelative return this * Improve doc comment on paddingX param * Fix mock sprite's setPositionRelative --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/field/pokemon.ts | 31 +- src/main.ts | 2 +- src/typings/phaser/index.d.ts | 12 +- src/ui-inputs.ts | 2 +- src/ui/battle-flyout.ts | 4 +- src/ui/battle-info.ts | 986 ------------------ src/ui/battle-info/battle-info.ts | 688 ++++++++++++ src/ui/battle-info/enemy-battle-info.ts | 235 +++++ src/ui/battle-info/player-battle-info.ts | 242 +++++ src/ui/fight-ui-handler.ts | 6 +- src/ui/pokemon-info-container.ts | 27 +- src/ui/summary-ui-handler.ts | 26 +- src/utils/common.ts | 16 + test/testUtils/mocks/mockGameObject.ts | 1 + .../mocks/mocksContainer/mockContainer.ts | 187 ++-- .../mocks/mocksContainer/mockGraphics.ts | 53 +- .../mocks/mocksContainer/mockRectangle.ts | 42 +- .../mocks/mocksContainer/mockSprite.ts | 153 +-- .../mocks/mocksContainer/mockText.ts | 104 +- test/testUtils/testFileInitialization.ts | 4 +- 20 files changed, 1602 insertions(+), 1219 deletions(-) delete mode 100644 src/ui/battle-info.ts create mode 100644 src/ui/battle-info/battle-info.ts create mode 100644 src/ui/battle-info/enemy-battle-info.ts create mode 100644 src/ui/battle-info/player-battle-info.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 85b003517a6..329ba06fd09 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,7 +5,9 @@ import { globalScene } from "#app/global-scene"; import type { Variant } from "#app/sprites/variant"; import { populateVariantColors, variantColorCache } from "#app/sprites/variant"; import { variantData } from "#app/sprites/variant"; -import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; +import BattleInfo from "#app/ui/battle-info/battle-info"; +import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info"; +import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info"; import type Move from "#app/data/moves/move"; import { HighCritAttr, @@ -3347,22 +3349,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.battleInfo.updateInfo(this, instant); } - /** - * Show or hide the type effectiveness multiplier window - * Passing undefined will hide the window - */ - updateEffectiveness(effectiveness?: string) { - this.battleInfo.updateEffectiveness(effectiveness); - } - toggleStats(visible: boolean): void { this.battleInfo.toggleStats(visible); } - toggleFlyout(visible: boolean): void { - this.battleInfo.toggleFlyout(visible); - } - /** * Adds experience to this PlayerPokemon, subject to wave based level caps. * @param exp The amount of experience to add @@ -5518,6 +5508,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } export class PlayerPokemon extends Pokemon { + protected battleInfo: PlayerBattleInfo; public compatibleTms: Moves[]; constructor( @@ -6038,6 +6029,7 @@ export class PlayerPokemon extends Pokemon { } export class EnemyPokemon extends Pokemon { + protected battleInfo: EnemyBattleInfo; public trainerSlot: TrainerSlot; public aiType: AiType; public bossSegments: number; @@ -6709,6 +6701,19 @@ export class EnemyPokemon extends Pokemon { return ret; } + + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.battleInfo.updateEffectiveness(effectiveness); + } + + toggleFlyout(visible: boolean): void { + this.battleInfo.toggleFlyout(visible); + } } /** diff --git a/src/main.ts b/src/main.ts index 7db663d14c7..38bfcbe5636 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,7 @@ window.addEventListener("unhandledrejection", event => { const setPositionRelative = function (guideObject: Phaser.GameObjects.GameObject, x: number, y: number) { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); }; Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index f3665768cec..26fbcff75bd 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -20,37 +20,37 @@ declare module "phaser" { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Sprite { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Image { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface NineSlice { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Text { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } interface Rectangle { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): this; } } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 0c13cdb9512..e4f11e1c93c 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -161,7 +161,7 @@ export class UiInputs { buttonInfo(pressed = true): void { if (globalScene.showMovesetFlyout) { - for (const p of globalScene.getField().filter(p => p?.isActive(true))) { + for (const p of globalScene.getEnemyField().filter(p => p?.isActive(true))) { p.toggleFlyout(pressed); } } diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index e590bebcf5a..f8ef5fc1ec4 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -1,4 +1,4 @@ -import type { default as Pokemon } from "../field/pokemon"; +import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; import { addTextObject, TextStyle } from "./text"; import { fixedInt } from "#app/utils/common"; import { globalScene } from "#app/global-scene"; @@ -126,7 +126,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { * Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event * @param pokemon {@linkcode Pokemon} to link to this flyout */ - initInfo(pokemon: Pokemon) { + initInfo(pokemon: EnemyPokemon) { this.pokemon = pokemon; this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts deleted file mode 100644 index 2822e8364ec..00000000000 --- a/src/ui/battle-info.ts +++ /dev/null @@ -1,986 +0,0 @@ -import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; -import { getLevelTotalExp, getLevelRelExp } from "../data/exp"; -import { getLocalizedSpriteKey, fixedInt } from "#app/utils/common"; -import { addTextObject, TextStyle } from "./text"; -import { getGenderSymbol, getGenderColor, Gender } from "../data/gender"; -import { StatusEffect } from "#enums/status-effect"; -import { globalScene } from "#app/global-scene"; -import { getTypeRgb } from "#app/data/type"; -import { PokemonType } from "#enums/pokemon-type"; -import { getVariantTint } from "#app/sprites/variant"; -import { Stat } from "#enums/stat"; -import BattleFlyout from "./battle-flyout"; -import { WindowVariant, addWindow } from "./ui-theme"; -import i18next from "i18next"; -import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; - -export default class BattleInfo extends Phaser.GameObjects.Container { - public static readonly EXP_GAINS_DURATION_BASE = 1650; - - private baseY: number; - - private player: boolean; - private mini: boolean; - private boss: boolean; - private bossSegments: number; - private offset: boolean; - private lastName: string | null; - private lastTeraType: PokemonType; - private lastStatus: StatusEffect; - private lastHp: number; - private lastMaxHp: number; - private lastHpFrame: string | null; - private lastExp: number; - private lastLevelExp: number; - private lastLevel: number; - private lastLevelCapped: boolean; - private lastStats: string; - - private box: Phaser.GameObjects.Sprite; - private nameText: Phaser.GameObjects.Text; - private genderText: Phaser.GameObjects.Text; - private ownedIcon: Phaser.GameObjects.Sprite; - private championRibbon: Phaser.GameObjects.Sprite; - private teraIcon: Phaser.GameObjects.Sprite; - private shinyIcon: Phaser.GameObjects.Sprite; - private fusionShinyIcon: Phaser.GameObjects.Sprite; - private splicedIcon: Phaser.GameObjects.Sprite; - private statusIndicator: Phaser.GameObjects.Sprite; - private levelContainer: Phaser.GameObjects.Container; - private hpBar: Phaser.GameObjects.Image; - private hpBarSegmentDividers: Phaser.GameObjects.Rectangle[]; - private levelNumbersContainer: Phaser.GameObjects.Container; - private hpNumbersContainer: Phaser.GameObjects.Container; - private type1Icon: Phaser.GameObjects.Sprite; - private type2Icon: Phaser.GameObjects.Sprite; - private type3Icon: Phaser.GameObjects.Sprite; - private expBar: Phaser.GameObjects.Image; - - // #region Type effectiveness hint objects - private effectivenessContainer: Phaser.GameObjects.Container; - private effectivenessWindow: Phaser.GameObjects.NineSlice; - private effectivenessText: Phaser.GameObjects.Text; - private currentEffectiveness?: string; - // #endregion - - public expMaskRect: Phaser.GameObjects.Graphics; - - private statsContainer: Phaser.GameObjects.Container; - private statsBox: Phaser.GameObjects.Sprite; - private statValuesContainer: Phaser.GameObjects.Container; - private statNumbers: Phaser.GameObjects.Sprite[]; - - public flyoutMenu?: BattleFlyout; - - private statOrder: Stat[]; - private readonly statOrderPlayer = [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; - private readonly statOrderEnemy = [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; - - constructor(x: number, y: number, player: boolean) { - super(globalScene, x, y); - this.baseY = y; - this.player = player; - this.mini = !player; - this.boss = false; - this.offset = false; - this.lastName = null; - this.lastTeraType = PokemonType.UNKNOWN; - this.lastStatus = StatusEffect.NONE; - this.lastHp = -1; - this.lastMaxHp = -1; - this.lastHpFrame = null; - this.lastExp = -1; - this.lastLevelExp = -1; - this.lastLevel = -1; - - // Initially invisible and shown via Pokemon.showInfo - this.setVisible(false); - - this.box = globalScene.add.sprite(0, 0, this.getTextureName()); - this.box.setName("box"); - this.box.setOrigin(1, 0.5); - this.add(this.box); - - this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO); - this.nameText.setName("text_name"); - this.nameText.setOrigin(0, 0); - this.add(this.nameText); - - this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO); - this.genderText.setName("text_gender"); - this.genderText.setOrigin(0, 0); - this.genderText.setPositionRelative(this.nameText, 0, 2); - this.add(this.genderText); - - if (!this.player) { - this.ownedIcon = globalScene.add.sprite(0, 0, "icon_owned"); - this.ownedIcon.setName("icon_owned"); - this.ownedIcon.setVisible(false); - this.ownedIcon.setOrigin(0, 0); - this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75); - this.add(this.ownedIcon); - - this.championRibbon = globalScene.add.sprite(0, 0, "champion_ribbon"); - this.championRibbon.setName("icon_champion_ribbon"); - this.championRibbon.setVisible(false); - this.championRibbon.setOrigin(0, 0); - this.championRibbon.setPositionRelative(this.nameText, 8, 11.75); - this.add(this.championRibbon); - } - - this.teraIcon = globalScene.add.sprite(0, 0, "icon_tera"); - this.teraIcon.setName("icon_tera"); - this.teraIcon.setVisible(false); - this.teraIcon.setOrigin(0, 0); - this.teraIcon.setScale(0.5); - this.teraIcon.setPositionRelative(this.nameText, 0, 2); - this.teraIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.teraIcon); - - this.shinyIcon = globalScene.add.sprite(0, 0, "shiny_star"); - this.shinyIcon.setName("icon_shiny"); - this.shinyIcon.setVisible(false); - this.shinyIcon.setOrigin(0, 0); - this.shinyIcon.setScale(0.5); - this.shinyIcon.setPositionRelative(this.nameText, 0, 2); - this.shinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.shinyIcon); - - this.fusionShinyIcon = globalScene.add.sprite(0, 0, "shiny_star_2"); - this.fusionShinyIcon.setName("icon_fusion_shiny"); - this.fusionShinyIcon.setVisible(false); - this.fusionShinyIcon.setOrigin(0, 0); - this.fusionShinyIcon.setScale(0.5); - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - this.add(this.fusionShinyIcon); - - this.splicedIcon = globalScene.add.sprite(0, 0, "icon_spliced"); - this.splicedIcon.setName("icon_spliced"); - this.splicedIcon.setVisible(false); - this.splicedIcon.setOrigin(0, 0); - this.splicedIcon.setScale(0.5); - this.splicedIcon.setPositionRelative(this.nameText, 0, 2); - this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); - this.add(this.splicedIcon); - - this.statusIndicator = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("statuses")); - this.statusIndicator.setName("icon_status"); - this.statusIndicator.setVisible(false); - this.statusIndicator.setOrigin(0, 0); - this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5); - this.add(this.statusIndicator); - - this.levelContainer = globalScene.add.container(player ? -41 : -50, player ? -10 : -5); - this.levelContainer.setName("container_level"); - this.add(this.levelContainer); - - const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); - this.levelContainer.add(levelOverlay); - - this.hpBar = globalScene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp"); - this.hpBar.setName("hp_bar"); - this.hpBar.setOrigin(0); - this.add(this.hpBar); - - this.hpBarSegmentDividers = []; - - this.levelNumbersContainer = globalScene.add.container(9.5, globalScene.uiTheme ? 0 : -0.5); - this.levelNumbersContainer.setName("container_level"); - this.levelContainer.add(this.levelNumbersContainer); - - if (this.player) { - this.hpNumbersContainer = globalScene.add.container(-15, 10); - this.hpNumbersContainer.setName("container_hp"); - this.add(this.hpNumbersContainer); - - const expBar = globalScene.add.image(-98, 18, "overlay_exp"); - expBar.setName("overlay_exp"); - expBar.setOrigin(0); - this.add(expBar); - - const expMaskRect = globalScene.make.graphics({}); - expMaskRect.setScale(6); - expMaskRect.fillStyle(0xffffff); - expMaskRect.beginPath(); - expMaskRect.fillRect(127, 126, 85, 2); - - const expMask = expMaskRect.createGeometryMask(); - - expBar.setMask(expMask); - - this.expBar = expBar; - this.expMaskRect = expMaskRect; - } - - this.statsContainer = globalScene.add.container(0, 0); - this.statsContainer.setName("container_stats"); - this.statsContainer.setAlpha(0); - this.add(this.statsContainer); - - this.statsBox = globalScene.add.sprite(0, 0, `${this.getTextureName()}_stats`); - this.statsBox.setName("box_stats"); - this.statsBox.setOrigin(1, 0.5); - this.statsContainer.add(this.statsBox); - - const statLabels: Phaser.GameObjects.Sprite[] = []; - this.statNumbers = []; - - this.statValuesContainer = globalScene.add.container(0, 0); - this.statsContainer.add(this.statValuesContainer); - - // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy - // since the player won't have HP to show, it doesn't need to change from the current version - const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5; - const paddingX = this.player ? 4 : 2; - const statOverflow = this.player ? 1 : 0; - this.statOrder = this.player ? this.statOrderPlayer : this.statOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order - - this.statOrder.map((s, i) => { - // we do a check for i > statOverflow to see when the stat labels go onto the next column - // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 - // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 - const statX = - i > statOverflow - ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX - : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 - - const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis - let statY: number; // this will be the y-axis placement for the labels - if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) { - statY = baseY + 5; - } else { - statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us - } - - const statLabel = globalScene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]); - statLabel.setName("icon_stat_label_" + i.toString()); - statLabel.setOrigin(0, 0); - statLabels.push(statLabel); - this.statValuesContainer.add(statLabel); - - const statNumber = globalScene.add.sprite( - statX + statLabel.width, - statY, - "pbinfo_stat_numbers", - this.statOrder[i] !== Stat.HP ? "3" : "empty", - ); - statNumber.setName("icon_stat_number_" + i.toString()); - statNumber.setOrigin(0, 0); - this.statNumbers.push(statNumber); - this.statValuesContainer.add(statNumber); - - if (this.statOrder[i] === Stat.HP) { - statLabel.setVisible(false); - statNumber.setVisible(false); - } - }); - - if (!this.player) { - this.flyoutMenu = new BattleFlyout(this.player); - this.add(this.flyoutMenu); - - this.moveBelow(this.flyoutMenu, this.box); - } - - this.type1Icon = globalScene.add.sprite( - player ? -139 : -15, - player ? -17 : -15.5, - `pbinfo_${player ? "player" : "enemy"}_type1`, - ); - this.type1Icon.setName("icon_type_1"); - this.type1Icon.setOrigin(0, 0); - this.add(this.type1Icon); - - this.type2Icon = globalScene.add.sprite( - player ? -139 : -15, - player ? -1 : -2.5, - `pbinfo_${player ? "player" : "enemy"}_type2`, - ); - this.type2Icon.setName("icon_type_2"); - this.type2Icon.setOrigin(0, 0); - this.add(this.type2Icon); - - this.type3Icon = globalScene.add.sprite( - player ? -154 : 0, - player ? -17 : -15.5, - `pbinfo_${player ? "player" : "enemy"}_type`, - ); - this.type3Icon.setName("icon_type_3"); - this.type3Icon.setOrigin(0, 0); - this.add(this.type3Icon); - - if (!this.player) { - this.effectivenessContainer = globalScene.add.container(0, 0); - this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4); - this.effectivenessContainer.setVisible(false); - this.add(this.effectivenessContainer); - - this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); - this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); - - this.effectivenessContainer.add(this.effectivenessWindow); - this.effectivenessContainer.add(this.effectivenessText); - } - } - - getStatsValueContainer(): Phaser.GameObjects.Container { - return this.statValuesContainer; - } - - initInfo(pokemon: Pokemon) { - this.updateNameText(pokemon); - const nameTextWidth = this.nameText.displayWidth; - - this.name = pokemon.getNameToRender(); - this.box.name = pokemon.getNameToRender(); - - this.flyoutMenu?.initInfo(pokemon); - - this.genderText.setText(getGenderSymbol(pokemon.gender)); - this.genderText.setColor(getGenderColor(pokemon.gender)); - this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0); - - this.lastTeraType = pokemon.getTeraType(); - - this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); - this.teraIcon.setVisible(pokemon.isTerastallized); - this.teraIcon.on("pointerover", () => { - if (pokemon.isTerastallized) { - globalScene.ui.showTooltip( - "", - i18next.t("fightUiHandler:teraHover", { - type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`), - }), - ); - } - }); - this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - - const isFusion = pokemon.isFusion(true); - - this.splicedIcon.setPositionRelative( - this.nameText, - nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), - 2.5, - ); - this.splicedIcon.setVisible(isFusion); - if (this.splicedIcon.visible) { - this.splicedIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`, - ), - ); - this.splicedIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; - - this.shinyIcon.setPositionRelative( - this.nameText, - nameTextWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + - (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), - 2.5, - ); - this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`); - this.shinyIcon.setVisible(pokemon.isShiny()); - this.shinyIcon.setTint(getVariantTint(baseVariant)); - if (this.shinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.shinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - ), - ); - this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - this.fusionShinyIcon.setVisible(doubleShiny); - if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); - } - - if (!this.player) { - if (this.nameText.visible) { - this.nameText.on("pointerover", () => - globalScene.ui.showTooltip( - "", - i18next.t("battleInfo:generation", { - generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`), - }), - ), - ); - this.nameText.on("pointerout", () => globalScene.ui.hideTooltip()); - } - - const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; - this.ownedIcon.setVisible(!!dexEntry.caughtAttr); - const opponentPokemonDexAttr = pokemon.getDexAttr(); - if (globalScene.gameMode.isClassic) { - if ( - globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && - globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0 - ) { - this.championRibbon.setVisible(true); - } - } - - // Check if Player owns all genders and forms of the Pokemon - const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr; - - const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; - - // Check if the player owns ability for the root form - const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); - - if (missingDexAttrs || !playerOwnsThisAbility) { - this.ownedIcon.setTint(0x808080); - } - - if (this.boss) { - this.updateBossSegmentDividers(pokemon as EnemyPokemon); - } - } - - this.hpBar.setScale(pokemon.getHpRatio(true), 1); - this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; - this.hpBar.setFrame(this.lastHpFrame); - if (this.player) { - this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); - } - this.lastHp = pokemon.hp; - this.lastMaxHp = pokemon.getMaxHp(); - - this.setLevel(pokemon.level); - this.lastLevel = pokemon.level; - - this.shinyIcon.setVisible(pokemon.isShiny()); - - const types = pokemon.getTypes(true, false, undefined, true); - this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`); - this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - - if (this.player) { - this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; - this.lastExp = pokemon.exp; - this.lastLevelExp = pokemon.levelExp; - - this.statValuesContainer.setPosition(8, 7); - } - - const stats = this.statOrder.map(() => 0); - - this.lastStats = stats.join(""); - this.updateStats(stats); - } - - getTextureName(): string { - return `pbinfo_${this.player ? "player" : "enemy"}${!this.player && this.boss ? "_boss" : this.mini ? "_mini" : ""}`; - } - - setMini(mini: boolean): void { - if (this.mini === mini) { - return; - } - - this.mini = mini; - - this.box.setTexture(this.getTextureName()); - this.statsBox.setTexture(`${this.getTextureName()}_stats`); - - if (this.player) { - this.y -= 12 * (mini ? 1 : -1); - this.baseY = this.y; - } - - const offsetElements = [ - this.nameText, - this.genderText, - this.teraIcon, - this.splicedIcon, - this.shinyIcon, - this.statusIndicator, - this.levelContainer, - ]; - offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1))); - - [this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => { - el.x += 4 * (mini ? 1 : -1); - el.y += -8 * (mini ? 1 : -1); - }); - - this.statValuesContainer.x += 2 * (mini ? 1 : -1); - this.statValuesContainer.y += -7 * (mini ? 1 : -1); - - const toggledElements = [this.hpNumbersContainer, this.expBar]; - toggledElements.forEach(el => el.setVisible(!mini)); - } - - toggleStats(visible: boolean): void { - globalScene.tweens.add({ - targets: this.statsContainer, - duration: fixedInt(125), - ease: "Sine.easeInOut", - alpha: visible ? 1 : 0, - }); - } - - updateBossSegments(pokemon: EnemyPokemon): void { - const boss = !!pokemon.bossSegments; - - if (boss !== this.boss) { - this.boss = boss; - - [ - this.nameText, - this.genderText, - this.teraIcon, - this.splicedIcon, - this.shinyIcon, - this.ownedIcon, - this.championRibbon, - this.statusIndicator, - this.statValuesContainer, - ].map(e => (e.x += 48 * (boss ? -1 : 1))); - this.hpBar.x += 38 * (boss ? -1 : 1); - this.hpBar.y += 2 * (this.boss ? -1 : 1); - this.levelContainer.x += 2 * (boss ? -1 : 1); - this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`); - this.box.setTexture(this.getTextureName()); - this.statsBox.setTexture(`${this.getTextureName()}_stats`); - } - - this.bossSegments = boss ? pokemon.bossSegments : 0; - this.updateBossSegmentDividers(pokemon); - } - - updateBossSegmentDividers(pokemon: EnemyPokemon): void { - while (this.hpBarSegmentDividers.length) { - this.hpBarSegmentDividers.pop()?.destroy(); - } - - if (this.boss && this.bossSegments > 1) { - const uiTheme = globalScene.uiTheme; - const maxHp = pokemon.getMaxHp(); - for (let s = 1; s < this.bossSegments; s++) { - const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; - const divider = globalScene.add.rectangle( - 0, - 0, - 1, - this.hpBar.height - (uiTheme ? 0 : 1), - pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040, - ); - divider.setOrigin(0.5, 0); - divider.setName("hpBar_divider_" + s.toString()); - this.add(divider); - this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer); - - divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1); - this.hpBarSegmentDividers.push(divider); - } - } - } - - setOffset(offset: boolean): void { - if (this.offset === offset) { - return; - } - - this.offset = offset; - - this.x += 10 * (this.offset === this.player ? 1 : -1); - this.y += 27 * (this.offset ? 1 : -1); - this.baseY = this.y; - } - - updateInfo(pokemon: Pokemon, instant?: boolean): Promise { - return new Promise(resolve => { - if (!globalScene) { - return resolve(); - } - - const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; - - this.genderText.setText(getGenderSymbol(gender)); - this.genderText.setColor(getGenderColor(gender)); - - const nameUpdated = this.lastName !== pokemon.getNameToRender(); - - if (nameUpdated) { - this.updateNameText(pokemon); - this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0); - } - - const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN; - const teraTypeUpdated = this.lastTeraType !== teraType; - - if (teraTypeUpdated) { - this.teraIcon.setVisible(teraType !== PokemonType.UNKNOWN); - this.teraIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + this.genderText.displayWidth + 1, - 2, - ); - this.teraIcon.setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(teraType))); - this.lastTeraType = teraType; - } - - const isFusion = pokemon.isFusion(true); - - if (nameUpdated || teraTypeUpdated) { - this.splicedIcon.setVisible(isFusion); - - this.teraIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + this.genderText.displayWidth + 1, - 2, - ); - this.splicedIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), - 1.5, - ); - this.shinyIcon.setPositionRelative( - this.nameText, - this.nameText.displayWidth + - this.genderText.displayWidth + - 1 + - (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + - (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), - 2.5, - ); - } - - if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) { - this.lastStatus = pokemon.status?.effect || StatusEffect.NONE; - - if (this.lastStatus !== StatusEffect.NONE) { - this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase()); - } - - const offsetX = !this.player ? (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0) : 0; - this.statusIndicator.setPositionRelative(this.nameText, offsetX, 11.5); - - this.statusIndicator.setVisible(!!this.lastStatus); - } - - const types = pokemon.getTypes(true, false, undefined, true); - this.type1Icon.setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`); - this.type1Icon.setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - - const updateHpFrame = () => { - const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; - if (hpFrame !== this.lastHpFrame) { - this.hpBar.setFrame(hpFrame); - this.lastHpFrame = hpFrame; - } - }; - - const updatePokemonHp = () => { - let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; - const speed = globalScene.hpBarSpeed; - if (speed) { - duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); - } - globalScene.tweens.add({ - targets: this.hpBar, - ease: "Sine.easeOut", - scaleX: pokemon.getHpRatio(true), - duration: duration, - onUpdate: () => { - if (this.player && this.lastHp !== pokemon.hp) { - const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp()); - this.setHpNumbers(tweenHp, pokemon.getMaxHp()); - this.lastHp = tweenHp; - } - - updateHpFrame(); - }, - onComplete: () => { - updateHpFrame(); - // If, after tweening, the hp is different from the original (due to rounding), force the hp number display - // to update to the correct value. - if (this.player && this.lastHp !== pokemon.hp) { - this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); - this.lastHp = pokemon.hp; - } - resolve(); - }, - }); - if (!this.player) { - this.lastHp = pokemon.hp; - } - this.lastMaxHp = pokemon.getMaxHp(); - }; - - if (this.player) { - const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); - - if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { - const originalResolve = resolve; - const durationMultipler = Math.max( - Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")( - 1 - Math.min(pokemon.level - this.lastLevel, 10) / 10, - ), - 0.1, - ); - resolve = () => this.updatePokemonExp(pokemon, false, durationMultipler).then(() => originalResolve()); - } else if (isLevelCapped !== this.lastLevelCapped) { - this.setLevel(pokemon.level); - } - - this.lastLevelCapped = isLevelCapped; - } - - if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) { - return updatePokemonHp(); - } - if (!this.player && this.lastLevel !== pokemon.level) { - this.setLevel(pokemon.level); - this.lastLevel = pokemon.level; - } - - const stats = pokemon.getStatStages(); - const statsStr = stats.join(""); - - if (this.lastStats !== statsStr) { - this.updateStats(stats); - this.lastStats = statsStr; - } - - this.shinyIcon.setVisible(pokemon.isShiny(true)); - - const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; - const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; - this.shinyIcon.setTint(getVariantTint(baseVariant)); - - this.fusionShinyIcon.setVisible(doubleShiny); - if (isFusion) { - this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); - } - this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); - - resolve(); - }); - } - - updateNameText(pokemon: Pokemon): void { - let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); - let nameTextWidth: number; - - const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); - nameTextWidth = nameSizeTest.displayWidth; - - const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; - while ( - nameTextWidth > - (this.player || !this.boss ? 60 : 98) - - ((gender !== Gender.GENDERLESS ? 6 : 0) + - (pokemon.fusionSpecies ? 8 : 0) + - (pokemon.isShiny() ? 8 : 0) + - (Math.min(pokemon.level.toString().length, 3) - 3) * 8) - ) { - displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`; - nameSizeTest.setText(displayName); - nameTextWidth = nameSizeTest.displayWidth; - } - - nameSizeTest.destroy(); - - this.nameText.setText(displayName); - this.lastName = pokemon.getNameToRender(); - - if (this.nameText.visible) { - this.nameText.setInteractive( - new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height), - Phaser.Geom.Rectangle.Contains, - ); - } - } - - updatePokemonExp(pokemon: Pokemon, instant?: boolean, levelDurationMultiplier = 1): Promise { - return new Promise(resolve => { - const levelUp = this.lastLevel < pokemon.level; - const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); - const levelExp = levelUp ? relLevelExp : pokemon.levelExp; - let ratio = relLevelExp ? levelExp / relLevelExp : 0; - if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { - if (levelUp) { - ratio = 1; - } else { - ratio = 0; - } - instant = true; - } - const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")( - 1 - Math.max(this.lastLevel - 100, 0) / 150, - ); - let duration = - this.visible && !instant - ? ((levelExp - this.lastLevelExp) / relLevelExp) * - BattleInfo.EXP_GAINS_DURATION_BASE * - durationMultiplier * - levelDurationMultiplier - : 0; - const speed = globalScene.expGainsSpeed; - if (speed && speed >= ExpGainsSpeed.DEFAULT) { - duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); - } - if (ratio === 1) { - this.lastLevelExp = 0; - this.lastLevel++; - } else { - this.lastExp = pokemon.exp; - this.lastLevelExp = pokemon.levelExp; - } - if (duration) { - globalScene.playSound("se/exp"); - } - globalScene.tweens.add({ - targets: this.expMaskRect, - ease: "Sine.easeIn", - x: ratio * 510, - duration: duration, - onComplete: () => { - if (!globalScene) { - return resolve(); - } - if (duration) { - globalScene.sound.stopByKey("se/exp"); - } - if (ratio === 1) { - globalScene.playSound("se/level_up"); - this.setLevel(this.lastLevel); - globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { - this.expMaskRect.x = 0; - this.updateInfo(pokemon, instant).then(() => resolve()); - }); - return; - } - resolve(); - }, - }); - }); - } - - setLevel(level: number): void { - const isCapped = level >= globalScene.getMaxExpLevel(); - this.levelNumbersContainer.removeAll(true); - const levelStr = level.toString(); - for (let i = 0; i < levelStr.length; i++) { - this.levelNumbersContainer.add( - globalScene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i]), - ); - } - this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0)); - } - - setHpNumbers(hp: number, maxHp: number): void { - if (!this.player || !globalScene) { - return; - } - this.hpNumbersContainer.removeAll(true); - const hpStr = hp.toString(); - const maxHpStr = maxHp.toString(); - let offset = 0; - for (let i = maxHpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); - } - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); - for (let i = hpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); - } - } - - updateStats(stats: number[]): void { - this.statOrder.map((s, i) => { - if (s !== Stat.HP) { - this.statNumbers[i].setFrame(stats[s - 1].toString()); - } - }); - } - - /** - * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary - */ - toggleFlyout(visible: boolean): void { - this.flyoutMenu?.toggleFlyout(visible); - - if (visible) { - this.effectivenessContainer?.setVisible(false); - } else { - this.updateEffectiveness(this.currentEffectiveness); - } - } - - /** - * Show or hide the type effectiveness multiplier window - * Passing undefined will hide the window - */ - updateEffectiveness(effectiveness?: string) { - if (this.player) { - return; - } - this.currentEffectiveness = effectiveness; - - if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu?.flyoutVisible) { - this.effectivenessContainer.setVisible(false); - return; - } - - this.effectivenessText.setText(effectiveness); - this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; - this.effectivenessContainer.setVisible(true); - } - - getBaseY(): number { - return this.baseY; - } - - resetY(): void { - this.y = this.baseY; - } -} - -export class PlayerBattleInfo extends BattleInfo { - constructor() { - super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true); - } -} - -export class EnemyBattleInfo extends BattleInfo { - constructor() { - super(140, -141, false); - } - - setMini(_mini: boolean): void {} // Always mini -} diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts new file mode 100644 index 00000000000..71596bf0f43 --- /dev/null +++ b/src/ui/battle-info/battle-info.ts @@ -0,0 +1,688 @@ +import type { default as Pokemon } from "../../field/pokemon"; +import { getLocalizedSpriteKey, fixedInt, getShinyDescriptor } from "#app/utils/common"; +import { addTextObject, TextStyle } from "../text"; +import { getGenderSymbol, getGenderColor, Gender } from "../../data/gender"; +import { StatusEffect } from "#enums/status-effect"; +import { globalScene } from "#app/global-scene"; +import { getTypeRgb } from "#app/data/type"; +import { PokemonType } from "#enums/pokemon-type"; +import { getVariantTint } from "#app/sprites/variant"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; + +/** + * Parameters influencing the position of elements within the battle info container + */ +export type BattleInfoParamList = { + /** X offset for the name text*/ + nameTextX: number; + /** Y offset for the name text */ + nameTextY: number; + /** X offset for the level container */ + levelContainerX: number; + /** Y offset for the level container */ + levelContainerY: number; + /** X offset for the hp bar */ + hpBarX: number; + /** Y offset for the hp bar */ + hpBarY: number; + /** Parameters for the stat box container */ + statBox: { + /** The starting offset from the left of the label for the entries in the stat box */ + xOffset: number; + /** The X padding between each number column */ + paddingX: number; + /** The index of the stat entries at which paddingX is used instead of startingX */ + statOverflow: number; + }; +}; + +export default abstract class BattleInfo extends Phaser.GameObjects.Container { + public static readonly EXP_GAINS_DURATION_BASE = 1650; + + protected baseY: number; + protected baseLvContainerX: number; + + protected player: boolean; + protected mini: boolean; + protected boss: boolean; + protected bossSegments: number; + protected offset: boolean; + protected lastName: string | null; + protected lastTeraType: PokemonType; + protected lastStatus: StatusEffect; + protected lastHp: number; + protected lastMaxHp: number; + protected lastHpFrame: string | null; + protected lastExp: number; + protected lastLevelExp: number; + protected lastLevel: number; + protected lastLevelCapped: boolean; + protected lastStats: string; + + protected box: Phaser.GameObjects.Sprite; + protected nameText: Phaser.GameObjects.Text; + protected genderText: Phaser.GameObjects.Text; + protected teraIcon: Phaser.GameObjects.Sprite; + protected shinyIcon: Phaser.GameObjects.Sprite; + protected fusionShinyIcon: Phaser.GameObjects.Sprite; + protected splicedIcon: Phaser.GameObjects.Sprite; + protected statusIndicator: Phaser.GameObjects.Sprite; + protected levelContainer: Phaser.GameObjects.Container; + protected hpBar: Phaser.GameObjects.Image; + protected levelNumbersContainer: Phaser.GameObjects.Container; + protected type1Icon: Phaser.GameObjects.Sprite; + protected type2Icon: Phaser.GameObjects.Sprite; + protected type3Icon: Phaser.GameObjects.Sprite; + protected expBar: Phaser.GameObjects.Image; + + public expMaskRect: Phaser.GameObjects.Graphics; + + protected statsContainer: Phaser.GameObjects.Container; + protected statsBox: Phaser.GameObjects.Sprite; + protected statValuesContainer: Phaser.GameObjects.Container; + protected statNumbers: Phaser.GameObjects.Sprite[]; + + get statOrder(): Stat[] { + return []; + } + + /** Helper method used by the constructor to create the tera and shiny icons next to the name */ + private constructIcons() { + const hitArea = new Phaser.Geom.Rectangle(0, 0, 12, 15); + const hitCallback = Phaser.Geom.Rectangle.Contains; + + this.teraIcon = globalScene.add + .sprite(0, 0, "icon_tera") + .setName("icon_tera") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.shinyIcon = globalScene.add + .sprite(0, 0, "shiny_star") + .setName("icon_shiny") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.fusionShinyIcon = globalScene.add + .sprite(0, 0, "shiny_star_2") + .setName("icon_fusion_shiny") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .copyPosition(this.shinyIcon); + + this.splicedIcon = globalScene.add + .sprite(0, 0, "icon_spliced") + .setName("icon_spliced") + .setVisible(false) + .setOrigin(0) + .setScale(0.5) + .setInteractive(hitArea, hitCallback) + .setPositionRelative(this.nameText, 0, 2); + + this.add([this.teraIcon, this.shinyIcon, this.fusionShinyIcon, this.splicedIcon]); + } + + /** + * Submethod of the constructor that creates and adds the stats container to the battle info + */ + protected constructStatContainer({ xOffset, paddingX, statOverflow }: BattleInfoParamList["statBox"]): void { + this.statsContainer = globalScene.add.container(0, 0).setName("container_stats").setAlpha(0); + this.add(this.statsContainer); + + this.statsBox = globalScene.add + .sprite(0, 0, `${this.getTextureName()}_stats`) + .setName("box_stats") + .setOrigin(1, 0.5); + this.statsContainer.add(this.statsBox); + + const statLabels: Phaser.GameObjects.Sprite[] = []; + this.statNumbers = []; + + this.statValuesContainer = globalScene.add.container(); + this.statsContainer.add(this.statValuesContainer); + + const startingX = -this.statsBox.width + xOffset; + + // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy + // since the player won't have HP to show, it doesn't need to change from the current version + + for (const [i, s] of this.statOrder.entries()) { + const isHp = s === Stat.HP; + // we do a check for i > statOverflow to see when the stat labels go onto the next column + // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 + // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 + const statX = + i > statOverflow + ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX + : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 + + let statY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis + if (isHp || s === Stat.SPD) { + statY += 5; + } else if (this.player === !!(i % 2)) { + // we compare i % 2 against this.player to tell us where to place the label + // because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players + // this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us + statY += 10; + } + + const statLabel = globalScene.add + .sprite(statX, statY, "pbinfo_stat", Stat[s]) + .setName("icon_stat_label_" + i.toString()) + .setOrigin(0); + statLabels.push(statLabel); + this.statValuesContainer.add(statLabel); + + const statNumber = globalScene.add + .sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", !isHp ? "3" : "empty") + .setName("icon_stat_number_" + i.toString()) + .setOrigin(0); + this.statNumbers.push(statNumber); + this.statValuesContainer.add(statNumber); + + if (isHp) { + statLabel.setVisible(false); + statNumber.setVisible(false); + } + } + } + + /** + * Submethod of the constructor that creates and adds the pokemon type icons to the battle info + */ + protected abstract constructTypeIcons(): void; + + /** + * @param x - The x position of the battle info container + * @param y - The y position of the battle info container + * @param player - Whether this battle info belongs to a player or an enemy + * @param posParams - The parameters influencing the position of elements within the battle info container + */ + constructor(x: number, y: number, player: boolean, posParams: BattleInfoParamList) { + super(globalScene, x, y); + this.baseY = y; + this.player = player; + this.mini = !player; + this.boss = false; + this.offset = false; + this.lastName = null; + this.lastTeraType = PokemonType.UNKNOWN; + this.lastStatus = StatusEffect.NONE; + this.lastHp = -1; + this.lastMaxHp = -1; + this.lastHpFrame = null; + this.lastExp = -1; + this.lastLevelExp = -1; + this.lastLevel = -1; + this.baseLvContainerX = posParams.levelContainerX; + + // Initially invisible and shown via Pokemon.showInfo + this.setVisible(false); + + this.box = globalScene.add.sprite(0, 0, this.getTextureName()).setName("box").setOrigin(1, 0.5); + this.add(this.box); + + this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO) + .setName("text_name") + .setOrigin(0); + this.add(this.nameText); + + this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO) + .setName("text_gender") + .setOrigin(0) + .setPositionRelative(this.nameText, 0, 2); + this.add(this.genderText); + + this.constructIcons(); + + this.statusIndicator = globalScene.add + .sprite(0, 0, getLocalizedSpriteKey("statuses")) + .setName("icon_status") + .setVisible(false) + .setOrigin(0) + .setPositionRelative(this.nameText, 0, 11.5); + this.add(this.statusIndicator); + + this.levelContainer = globalScene.add + .container(posParams.levelContainerX, posParams.levelContainerY) + .setName("container_level"); + this.add(this.levelContainer); + + const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); + this.levelContainer.add(levelOverlay); + + this.hpBar = globalScene.add.image(posParams.hpBarX, posParams.hpBarY, "overlay_hp").setName("hp_bar").setOrigin(0); + this.add(this.hpBar); + + this.levelNumbersContainer = globalScene.add + .container(9.5, globalScene.uiTheme ? 0 : -0.5) + .setName("container_level"); + this.levelContainer.add(this.levelNumbersContainer); + + this.constructStatContainer(posParams.statBox); + + this.constructTypeIcons(); + } + + getStatsValueContainer(): Phaser.GameObjects.Container { + return this.statValuesContainer; + } + + //#region Initialization methods + + initSplicedIcon(pokemon: Pokemon, baseWidth: number) { + this.splicedIcon.setPositionRelative( + this.nameText, + baseWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), + 2.5, + ); + this.splicedIcon.setVisible(pokemon.isFusion(true)); + if (!this.splicedIcon.visible) { + return; + } + this.splicedIcon + .on("pointerover", () => + globalScene.ui.showTooltip( + "", + `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`, + ), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + /** + * Called by {@linkcode initInfo} to initialize the shiny icon + * @param pokemon - The pokemon object attached to this battle info + * @param baseXOffset - The x offset to use for the shiny icon + * @param doubleShiny - Whether the pokemon is shiny and its fusion species is also shiny + */ + protected initShinyIcon(pokemon: Pokemon, xOffset: number, doubleShiny: boolean) { + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; + + this.shinyIcon.setPositionRelative( + this.nameText, + xOffset + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), + 2.5, + ); + this.shinyIcon + .setTexture(`shiny_star${doubleShiny ? "_1" : ""}`) + .setVisible(pokemon.isShiny()) + .setTint(getVariantTint(baseVariant)); + + if (!this.shinyIcon.visible) { + return; + } + + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + + this.shinyIcon + .on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor)) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + initInfo(pokemon: Pokemon) { + this.updateNameText(pokemon); + const nameTextWidth = this.nameText.displayWidth; + + this.name = pokemon.getNameToRender(); + this.box.name = pokemon.getNameToRender(); + + this.genderText + .setText(getGenderSymbol(pokemon.gender)) + .setColor(getGenderColor(pokemon.gender)) + .setPositionRelative(this.nameText, nameTextWidth, 0); + + this.lastTeraType = pokemon.getTeraType(); + + this.teraIcon + .setVisible(pokemon.isTerastallized) + .on("pointerover", () => { + if (pokemon.isTerastallized) { + globalScene.ui.showTooltip( + "", + i18next.t("fightUiHandler:teraHover", { + type: i18next.t(`pokemonInfo:Type.${PokemonType[this.lastTeraType]}`), + }), + ); + } + }) + .on("pointerout", () => globalScene.ui.hideTooltip()) + .setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); + + const isFusion = pokemon.isFusion(true); + this.initSplicedIcon(pokemon, nameTextWidth); + + const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; + this.initShinyIcon(pokemon, nameTextWidth, doubleShiny); + + this.fusionShinyIcon.setVisible(doubleShiny).copyPosition(this.shinyIcon); + if (isFusion) { + this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); + } + + this.hpBar.setScale(pokemon.getHpRatio(true), 1); + this.lastHpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; + this.hpBar.setFrame(this.lastHpFrame); + this.lastHp = pokemon.hp; + this.lastMaxHp = pokemon.getMaxHp(); + + this.setLevel(pokemon.level); + this.lastLevel = pokemon.level; + + this.shinyIcon.setVisible(pokemon.isShiny()); + + this.setTypes(pokemon.getTypes(true, false, undefined, true)); + + const stats = this.statOrder.map(() => 0); + + this.lastStats = stats.join(""); + this.updateStats(stats); + } + //#endregion + + /** + * Return the texture name of the battle info box + */ + abstract getTextureName(): string; + + setMini(_mini: boolean): void {} + + toggleStats(visible: boolean): void { + globalScene.tweens.add({ + targets: this.statsContainer, + duration: fixedInt(125), + ease: "Sine.easeInOut", + alpha: visible ? 1 : 0, + }); + } + + setOffset(offset: boolean): void { + if (this.offset === offset) { + return; + } + + this.offset = offset; + + this.x += 10 * (this.offset === this.player ? 1 : -1); + this.y += 27 * (this.offset ? 1 : -1); + this.baseY = this.y; + } + + //#region Update methods and helpers + + /** + * Update the status icon to match the pokemon's current status + * @param pokemon - The pokemon object attached to this battle info + * @param xOffset - The offset from the name text + */ + updateStatusIcon(pokemon: Pokemon, xOffset = 0) { + if (this.lastStatus !== (pokemon.status?.effect || StatusEffect.NONE)) { + this.lastStatus = pokemon.status?.effect || StatusEffect.NONE; + + if (this.lastStatus !== StatusEffect.NONE) { + this.statusIndicator.setFrame(StatusEffect[this.lastStatus].toLowerCase()); + } + + this.statusIndicator.setVisible(!!this.lastStatus).setPositionRelative(this.nameText, xOffset, 11.5); + } + } + + /** Update the pokemon name inside the container */ + protected updateName(name: string): boolean { + if (this.lastName === name) { + return false; + } + this.nameText.setText(name).setPositionRelative(this.box, -this.nameText.displayWidth, 0); + this.lastName = name; + + return true; + } + + protected updateTeraType(ty: PokemonType): boolean { + if (this.lastTeraType === ty) { + return false; + } + + this.teraIcon + .setVisible(ty !== PokemonType.UNKNOWN) + .setTintFill(Phaser.Display.Color.GetColor(...getTypeRgb(ty))) + .setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2); + this.lastTeraType = ty; + + return true; + } + + /** + * Update the type icons to match the pokemon's types + */ + setTypes(types: PokemonType[]): void { + this.type1Icon + .setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`) + .setFrame(PokemonType[types[0]].toLowerCase()); + this.type2Icon.setVisible(types.length > 1); + this.type3Icon.setVisible(types.length > 2); + if (types.length > 1) { + this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); + } + if (types.length > 2) { + this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); + } + } + + /** + * Called by {@linkcode updateInfo} to update the position of the tera, spliced, and shiny icons + * @param isFusion - Whether the pokemon is a fusion or not + */ + protected updateIconDisplay(isFusion: boolean): void { + this.teraIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2); + this.splicedIcon + .setVisible(isFusion) + .setPositionRelative( + this.nameText, + this.nameText.displayWidth + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), + 1.5, + ); + this.shinyIcon.setPositionRelative( + this.nameText, + this.nameText.displayWidth + + this.genderText.displayWidth + + 1 + + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), + 2.5, + ); + } + + //#region Hp Bar Display handling + /** + * Called every time the hp frame is updated by the tween + * @param pokemon - The pokemon object attached to this battle info + */ + protected updateHpFrame(): void { + const hpFrame = this.hpBar.scaleX > 0.5 ? "high" : this.hpBar.scaleX > 0.25 ? "medium" : "low"; + if (hpFrame !== this.lastHpFrame) { + this.hpBar.setFrame(hpFrame); + this.lastHpFrame = hpFrame; + } + } + + /** + * Called by every frame in the hp animation tween created in {@linkcode updatePokemonHp} + * @param _pokemon - The pokemon the battle-info bar belongs to + */ + protected onHpTweenUpdate(_pokemon: Pokemon): void { + this.updateHpFrame(); + } + + /** Update the pokemonHp bar */ + protected updatePokemonHp(pokemon: Pokemon, resolve: (r: void | PromiseLike) => void, instant?: boolean): void { + let duration = !instant ? Phaser.Math.Clamp(Math.abs(this.lastHp - pokemon.hp) * 5, 250, 5000) : 0; + const speed = globalScene.hpBarSpeed; + if (speed) { + duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); + } + globalScene.tweens.add({ + targets: this.hpBar, + ease: "Sine.easeOut", + scaleX: pokemon.getHpRatio(true), + duration: duration, + onUpdate: () => { + this.onHpTweenUpdate(pokemon); + }, + onComplete: () => { + this.updateHpFrame(); + resolve(); + }, + }); + this.lastMaxHp = pokemon.getMaxHp(); + } + + //#endregion + + async updateInfo(pokemon: Pokemon, instant?: boolean): Promise { + let resolve: (r: void | PromiseLike) => void = () => {}; + const promise = new Promise(r => (resolve = r)); + if (!globalScene) { + return resolve(); + } + + const gender: Gender = pokemon.summonData?.illusion?.gender ?? pokemon.gender; + + this.genderText.setText(getGenderSymbol(gender)).setColor(getGenderColor(gender)); + + const nameUpdated = this.updateName(pokemon.getNameToRender()); + + const teraTypeUpdated = this.updateTeraType(pokemon.isTerastallized ? pokemon.getTeraType() : PokemonType.UNKNOWN); + + const isFusion = pokemon.isFusion(true); + + if (nameUpdated || teraTypeUpdated) { + this.updateIconDisplay(isFusion); + } + + this.updateStatusIcon(pokemon); + + if (this.lastHp !== pokemon.hp || this.lastMaxHp !== pokemon.getMaxHp()) { + return this.updatePokemonHp(pokemon, resolve, instant); + } + if (!this.player && this.lastLevel !== pokemon.level) { + this.setLevel(pokemon.level); + this.lastLevel = pokemon.level; + } + + const stats = pokemon.getStatStages(); + const statsStr = stats.join(""); + + if (this.lastStats !== statsStr) { + this.updateStats(stats); + this.lastStats = statsStr; + } + + this.shinyIcon.setVisible(pokemon.isShiny(true)); + + const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; + const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant; + this.shinyIcon.setTint(getVariantTint(baseVariant)); + + this.fusionShinyIcon.setVisible(doubleShiny).setPosition(this.shinyIcon.x, this.shinyIcon.y); + if (isFusion) { + this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant)); + } + + resolve(); + await promise; + } + //#endregion + + updateNameText(pokemon: Pokemon): void { + let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); + let nameTextWidth: number; + + const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); + nameTextWidth = nameSizeTest.displayWidth; + + const gender = pokemon.summonData.illusion?.gender ?? pokemon.gender; + while ( + nameTextWidth > + (this.player || !this.boss ? 60 : 98) - + ((gender !== Gender.GENDERLESS ? 6 : 0) + + (pokemon.fusionSpecies ? 8 : 0) + + (pokemon.isShiny() ? 8 : 0) + + (Math.min(pokemon.level.toString().length, 3) - 3) * 8) + ) { + displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`; + nameSizeTest.setText(displayName); + nameTextWidth = nameSizeTest.displayWidth; + } + + nameSizeTest.destroy(); + + this.nameText.setText(displayName); + this.lastName = pokemon.getNameToRender(); + + if (this.nameText.visible) { + this.nameText.setInteractive( + new Phaser.Geom.Rectangle(0, 0, this.nameText.width, this.nameText.height), + Phaser.Geom.Rectangle.Contains, + ); + } + } + + /** + * Set the level numbers container to display the provided level + * + * @remarks + * The numbers in the pokemon's level uses images for each number rather than a text object with a special font. + * This method sets the images for each digit of the level number and then positions the level container based + * on the number of digits. + * + * @param level - The level to display + * @param textureKey - The texture key for the level numbers + */ + setLevel(level: number, textureKey: "numbers" | "numbers_red" = "numbers"): void { + this.levelNumbersContainer.removeAll(true); + const levelStr = level.toString(); + for (let i = 0; i < levelStr.length; i++) { + this.levelNumbersContainer.add(globalScene.add.image(i * 8, 0, textureKey, levelStr[i])); + } + this.levelContainer.setX(this.baseLvContainerX - 8 * Math.max(levelStr.length - 3, 0)); + } + + updateStats(stats: number[]): void { + for (const [i, s] of this.statOrder.entries()) { + if (s !== Stat.HP) { + this.statNumbers[i].setFrame(stats[s - 1].toString()); + } + } + } + + getBaseY(): number { + return this.baseY; + } + + resetY(): void { + this.y = this.baseY; + } +} diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts new file mode 100644 index 00000000000..e8f5434dcf2 --- /dev/null +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -0,0 +1,235 @@ +import { globalScene } from "#app/global-scene"; +import BattleFlyout from "../battle-flyout"; +import { addTextObject, TextStyle } from "#app/ui/text"; +import { addWindow, WindowVariant } from "#app/ui/ui-theme"; +import { Stat } from "#enums/stat"; +import i18next from "i18next"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import type { GameObjects } from "phaser"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; + +export class EnemyBattleInfo extends BattleInfo { + protected player: false = false; + protected championRibbon: Phaser.GameObjects.Sprite; + protected ownedIcon: Phaser.GameObjects.Sprite; + protected flyoutMenu: BattleFlyout; + + protected hpBarSegmentDividers: GameObjects.Rectangle[] = []; + + // #region Type effectiveness hint objects + protected effectivenessContainer: Phaser.GameObjects.Container; + protected effectivenessWindow: Phaser.GameObjects.NineSlice; + protected effectivenessText: Phaser.GameObjects.Text; + protected currentEffectiveness?: string; + // #endregion + + override get statOrder(): Stat[] { + return [Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; + } + + override getTextureName(): string { + return this.boss ? "pbinfo_enemy_boss_mini" : "pbinfo_enemy_mini"; + } + + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(0, 15.5, "pbinfo_enemy_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + + constructor() { + const posParams: BattleInfoParamList = { + nameTextX: -124, + nameTextY: -11.2, + levelContainerX: -50, + levelContainerY: -5, + hpBarX: -71, + hpBarY: 4.5, + statBox: { + xOffset: 5, + paddingX: 2, + statOverflow: 0, + }, + }; + + super(140, -141, false, posParams); + + this.ownedIcon = globalScene.add + .sprite(0, 0, "icon_owned") + .setName("icon_owned") + .setVisible(false) + .setOrigin(0, 0) + .setPositionRelative(this.nameText, 0, 11.75); + + this.championRibbon = globalScene.add + .sprite(0, 0, "champion_ribbon") + .setName("icon_champion_ribbon") + .setVisible(false) + .setOrigin(0, 0) + .setPositionRelative(this.nameText, 8, 11.75); + // Ensure these two icons are positioned below the stats container + this.addAt([this.ownedIcon, this.championRibbon], this.getIndex(this.statsContainer)); + + this.flyoutMenu = new BattleFlyout(this.player); + this.add(this.flyoutMenu); + + this.moveBelow(this.flyoutMenu, this.box); + + this.effectivenessContainer = globalScene.add + .container(0, 0) + .setVisible(false) + .setPositionRelative(this.type1Icon, 22, 4); + this.add(this.effectivenessContainer); + + this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); + this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); + + this.effectivenessContainer.add([this.effectivenessWindow, this.effectivenessText]); + } + + override initInfo(pokemon: EnemyPokemon): void { + this.flyoutMenu.initInfo(pokemon); + super.initInfo(pokemon); + + if (this.nameText.visible) { + this.nameText + .on("pointerover", () => + globalScene.ui.showTooltip( + "", + i18next.t("battleInfo:generation", { + generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`), + }), + ), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); + } + + const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; + this.ownedIcon.setVisible(!!dexEntry.caughtAttr); + const opponentPokemonDexAttr = pokemon.getDexAttr(); + if ( + globalScene.gameMode.isClassic && + globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && + globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0 + ) { + this.championRibbon.setVisible(true); + } + + // Check if Player owns all genders and forms of the Pokemon + const missingDexAttrs = (dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr; + + const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; + + // Check if the player owns ability for the root form + const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); + + if (missingDexAttrs || !playerOwnsThisAbility) { + this.ownedIcon.setTint(0x808080); + } + + if (this.boss) { + this.updateBossSegmentDividers(pokemon as EnemyPokemon); + } + } + + /** + * Show or hide the type effectiveness multiplier window + * Passing undefined will hide the window + */ + updateEffectiveness(effectiveness?: string) { + this.currentEffectiveness = effectiveness; + + if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu.flyoutVisible) { + this.effectivenessContainer.setVisible(false); + return; + } + + this.effectivenessText.setText(effectiveness); + this.effectivenessWindow.width = 10 + this.effectivenessText.displayWidth; + this.effectivenessContainer.setVisible(true); + } + + /** + * Request the flyoutMenu to toggle if available and hides or shows the effectiveness window where necessary + */ + toggleFlyout(visible: boolean): void { + this.flyoutMenu.toggleFlyout(visible); + + if (visible) { + this.effectivenessContainer.setVisible(false); + } else { + this.updateEffectiveness(this.currentEffectiveness); + } + } + + updateBossSegments(pokemon: EnemyPokemon): void { + const boss = !!pokemon.bossSegments; + + if (boss !== this.boss) { + this.boss = boss; + + [ + this.nameText, + this.genderText, + this.teraIcon, + this.splicedIcon, + this.shinyIcon, + this.ownedIcon, + this.championRibbon, + this.statusIndicator, + this.levelContainer, + this.statValuesContainer, + ].map(e => (e.x += 48 * (boss ? -1 : 1))); + this.hpBar.x += 38 * (boss ? -1 : 1); + this.hpBar.y += 2 * (this.boss ? -1 : 1); + this.hpBar.setTexture(`overlay_hp${boss ? "_boss" : ""}`); + this.box.setTexture(this.getTextureName()); + this.statsBox.setTexture(`${this.getTextureName()}_stats`); + } + + this.bossSegments = boss ? pokemon.bossSegments : 0; + this.updateBossSegmentDividers(pokemon); + } + + updateBossSegmentDividers(pokemon: EnemyPokemon): void { + while (this.hpBarSegmentDividers.length) { + this.hpBarSegmentDividers.pop()?.destroy(); + } + + if (this.boss && this.bossSegments > 1) { + const uiTheme = globalScene.uiTheme; + const maxHp = pokemon.getMaxHp(); + for (let s = 1; s < this.bossSegments; s++) { + const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; + const divider = globalScene.add.rectangle( + 0, + 0, + 1, + this.hpBar.height - (uiTheme ? 0 : 1), + pokemon.bossSegmentIndex >= s ? 0xffffff : 0x404040, + ); + divider.setOrigin(0.5, 0).setName("hpBar_divider_" + s.toString()); + this.add(divider); + this.moveBelow(divider as Phaser.GameObjects.GameObject, this.statsContainer); + + divider.setPositionRelative(this.hpBar, dividerX, uiTheme ? 0 : 1); + this.hpBarSegmentDividers.push(divider); + } + } + } + + override updateStatusIcon(pokemon: EnemyPokemon): void { + super.updateStatusIcon(pokemon, (this.ownedIcon.visible ? 8 : 0) + (this.championRibbon.visible ? 8 : 0)); + } + + protected override updatePokemonHp( + pokemon: EnemyPokemon, + resolve: (r: void | PromiseLike) => void, + instant?: boolean, + ): void { + super.updatePokemonHp(pokemon, resolve, instant); + this.lastHp = pokemon.hp; + } +} diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts new file mode 100644 index 00000000000..634f89b7922 --- /dev/null +++ b/src/ui/battle-info/player-battle-info.ts @@ -0,0 +1,242 @@ +import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import { ExpGainsSpeed } from "#enums/exp-gains-speed"; +import { Stat } from "#enums/stat"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; + +export class PlayerBattleInfo extends BattleInfo { + protected player: true = true; + protected hpNumbersContainer: Phaser.GameObjects.Container; + + override get statOrder(): Stat[] { + return [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD]; + } + + override getTextureName(): string { + return this.mini ? "pbinfo_player_mini" : "pbinfo_player"; + } + + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + + constructor() { + const posParams: BattleInfoParamList = { + nameTextX: -115, + nameTextY: -15.2, + levelContainerX: -41, + levelContainerY: -10, + hpBarX: -61, + hpBarY: -1, + statBox: { + xOffset: 8, + paddingX: 4, + statOverflow: 1, + }, + }; + super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true, posParams); + + this.hpNumbersContainer = globalScene.add.container(-15, 10).setName("container_hp"); + + // hp number container must be beneath the stat container for overlay to display properly + this.addAt(this.hpNumbersContainer, this.getIndex(this.statsContainer)); + + const expBar = globalScene.add.image(-98, 18, "overlay_exp").setName("overlay_exp").setOrigin(0); + this.add(expBar); + + const expMaskRect = globalScene.make + .graphics({}) + .setScale(6) + .fillStyle(0xffffff) + .beginPath() + .fillRect(127, 126, 85, 2); + + const expMask = expMaskRect.createGeometryMask(); + + expBar.setMask(expMask); + + this.expBar = expBar; + this.expMaskRect = expMaskRect; + } + + override initInfo(pokemon: PlayerPokemon): void { + super.initInfo(pokemon); + this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); + this.expMaskRect.x = (pokemon.levelExp / getLevelTotalExp(pokemon.level, pokemon.species.growthRate)) * 510; + this.lastExp = pokemon.exp; + this.lastLevelExp = pokemon.levelExp; + + this.statValuesContainer.setPosition(8, 7); + } + + override setMini(mini: boolean): void { + if (this.mini === mini) { + return; + } + + this.mini = mini; + + this.box.setTexture(this.getTextureName()); + this.statsBox.setTexture(`${this.getTextureName()}_stats`); + + if (this.player) { + this.y -= 12 * (mini ? 1 : -1); + this.baseY = this.y; + } + + const offsetElements = [ + this.nameText, + this.genderText, + this.teraIcon, + this.splicedIcon, + this.shinyIcon, + this.statusIndicator, + this.levelContainer, + ]; + offsetElements.forEach(el => (el.y += 1.5 * (mini ? -1 : 1))); + + [this.type1Icon, this.type2Icon, this.type3Icon].forEach(el => { + el.x += 4 * (mini ? 1 : -1); + el.y += -8 * (mini ? 1 : -1); + }); + + this.statValuesContainer.x += 2 * (mini ? 1 : -1); + this.statValuesContainer.y += -7 * (mini ? 1 : -1); + + const toggledElements = [this.hpNumbersContainer, this.expBar]; + toggledElements.forEach(el => el.setVisible(!mini)); + } + + /** + * Updates the Hp Number text (that is the "HP/Max HP" text that appears below the player's health bar) + * while the health bar is tweening. + * @param pokemon - The Pokemon the health bar belongs to. + */ + protected override onHpTweenUpdate(pokemon: PlayerPokemon): void { + const tweenHp = Math.ceil(this.hpBar.scaleX * pokemon.getMaxHp()); + this.setHpNumbers(tweenHp, pokemon.getMaxHp()); + this.lastHp = tweenHp; + this.updateHpFrame(); + } + + updatePokemonExp(pokemon: PlayerPokemon, instant?: boolean, levelDurationMultiplier = 1): Promise { + const levelUp = this.lastLevel < pokemon.level; + const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); + const levelExp = levelUp ? relLevelExp : pokemon.levelExp; + let ratio = relLevelExp ? levelExp / relLevelExp : 0; + if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { + ratio = levelUp ? 1 : 0; + instant = true; + } + const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")( + 1 - Math.max(this.lastLevel - 100, 0) / 150, + ); + let duration = + this.visible && !instant + ? ((levelExp - this.lastLevelExp) / relLevelExp) * + BattleInfo.EXP_GAINS_DURATION_BASE * + durationMultiplier * + levelDurationMultiplier + : 0; + const speed = globalScene.expGainsSpeed; + if (speed && speed >= ExpGainsSpeed.DEFAULT) { + duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); + } + if (ratio === 1) { + this.lastLevelExp = 0; + this.lastLevel++; + } else { + this.lastExp = pokemon.exp; + this.lastLevelExp = pokemon.levelExp; + } + if (duration) { + globalScene.playSound("se/exp"); + } + return new Promise(resolve => { + globalScene.tweens.add({ + targets: this.expMaskRect, + ease: "Sine.easeIn", + x: ratio * 510, + duration: duration, + onComplete: () => { + if (!globalScene) { + return resolve(); + } + if (duration) { + globalScene.sound.stopByKey("se/exp"); + } + if (ratio === 1) { + globalScene.playSound("se/level_up"); + this.setLevel(this.lastLevel); + globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { + this.expMaskRect.x = 0; + this.updateInfo(pokemon, instant).then(() => resolve()); + }); + return; + } + resolve(); + }, + }); + }); + } + + /** + * Updates the info on the info bar. + * + * In addition to performing all the steps of {@linkcode BattleInfo.updateInfo}, + * it also updates the EXP Bar + */ + override async updateInfo(pokemon: PlayerPokemon, instant?: boolean): Promise { + await super.updateInfo(pokemon, instant); + const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); + const oldLevelCapped = this.lastLevelCapped; + this.lastLevelCapped = isLevelCapped; + + if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { + const durationMultipler = Math.max( + Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(1 - Math.min(pokemon.level - this.lastLevel, 10) / 10), + 0.1, + ); + await this.updatePokemonExp(pokemon, false, durationMultipler); + } else if (isLevelCapped !== oldLevelCapped) { + this.setLevel(pokemon.level); + } + } + + /** + * Set the HP numbers text, that is the "HP/Max HP" text that appears below the player's health bar. + * @param hp - The current HP of the player. + * @param maxHp - The maximum HP of the player. + */ + setHpNumbers(hp: number, maxHp: number): void { + if (!globalScene) { + return; + } + this.hpNumbersContainer.removeAll(true); + const hpStr = hp.toString(); + const maxHpStr = maxHp.toString(); + let offset = 0; + for (let i = maxHpStr.length - 1; i >= 0; i--) { + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); + } + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); + for (let i = hpStr.length - 1; i >= 0; i--) { + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); + } + } + + /** + * Set the level numbers container to display the provided level + * + * Overrides the default implementation to handle displaying level capped numbers in red. + * @param level - The level to display + */ + override setLevel(level: number): void { + super.setLevel(level, level >= globalScene.getMaxExpLevel() ? "numbers_red" : "numbers"); + } +} diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 5a0978a934d..6dbbcd47300 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -10,7 +10,7 @@ import { getLocalizedSpriteKey, fixedInt, padInt } from "#app/utils/common"; import { MoveCategory } from "#enums/MoveCategory"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import type { PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; @@ -279,7 +279,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { this.moveInfoOverlay.show(pokemonMove.getMove()); pokemon.getOpponents().forEach(opponent => { - opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); + (opponent as EnemyPokemon).updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); }); } @@ -391,7 +391,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); opponents.forEach(opponent => { - opponent.updateEffectiveness(undefined); + (opponent as EnemyPokemon).updateEffectiveness(); }); } diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 18b5d2384ef..d8012a58875 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -8,7 +8,7 @@ import type Pokemon from "../field/pokemon"; import i18next from "i18next"; import type { DexEntry, StarterDataEntry } from "../system/game-data"; import { DexAttr } from "../system/game-data"; -import { fixedInt } from "#app/utils/common"; +import { fixedInt, getShinyDescriptor } from "#app/utils/common"; import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; @@ -343,18 +343,19 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.setVisible(pokemon.isShiny()); this.pokemonShinyIcon.setTint(getVariantTint(baseVariant)); if (this.pokemonShinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.pokemonShinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - true, - ), - ); - this.pokemonShinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + this.pokemonShinyIcon + .on("pointerover", () => + globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0)); const newVariant = BigInt(1 << (pokemon.variant + 4)); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index f93a1826b3e..24e790f7c61 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -11,6 +11,7 @@ import { isNullOrUndefined, toReadableString, formatStat, + getShinyDescriptor, } from "#app/utils/common"; import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; @@ -444,18 +445,19 @@ export default class SummaryUiHandler extends UiHandler { this.shinyIcon.setVisible(this.pokemon.isShiny(false)); this.shinyIcon.setTint(getVariantTint(baseVariant)); if (this.shinyIcon.visible) { - const shinyDescriptor = - doubleShiny || baseVariant - ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${this.pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : this.pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` - : ""; - this.shinyIcon.on("pointerover", () => - globalScene.ui.showTooltip( - "", - `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, - true, - ), - ); - this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); + let shinyDescriptor = ""; + if (doubleShiny || baseVariant) { + shinyDescriptor = " (" + getShinyDescriptor(baseVariant); + if (doubleShiny) { + shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant); + } + shinyDescriptor += ")"; + } + this.shinyIcon + .on("pointerover", () => + globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor, true), + ) + .on("pointerout", () => globalScene.ui.hideTooltip()); } this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); diff --git a/src/utils/common.ts b/src/utils/common.ts index b9111578e2f..a018b49da3c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -2,6 +2,7 @@ import { MoneyFormat } from "#enums/money-format"; import { Moves } from "#enums/moves"; import i18next from "i18next"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import type { Variant } from "#app/sprites/variant"; export type nil = null | undefined; @@ -576,3 +577,18 @@ export function animationFileName(move: Moves): string { export function camelCaseToKebabCase(str: string): string { return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (s, o) => (o ? "-" : "") + s.toLowerCase()); } + +/** Get the localized shiny descriptor for the provided variant + * @param variant - The variant to get the shiny descriptor for + * @returns The localized shiny descriptor + */ +export function getShinyDescriptor(variant: Variant): string { + switch (variant) { + case 2: + return i18next.t("common:epicShiny"); + case 1: + return i18next.t("common:rareShiny"); + case 0: + return i18next.t("common:commonShiny"); + } +} diff --git a/test/testUtils/mocks/mockGameObject.ts b/test/testUtils/mocks/mockGameObject.ts index 4c243ec9ca1..0dff7c48c73 100644 --- a/test/testUtils/mocks/mockGameObject.ts +++ b/test/testUtils/mocks/mockGameObject.ts @@ -1,3 +1,4 @@ export interface MockGameObject { name: string; + destroy?(): void; } diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index 5e739fbe3cc..e116e7d151a 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -2,199 +2,253 @@ import type MockTextureManager from "#test/testUtils/mocks/mockTextureManager"; import type { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { - protected x; - protected y; + protected x: number; + protected y: number; protected scene; - protected width; - protected height; - protected visible; - private alpha; + protected width: number; + protected height: number; + protected visible: boolean; + private alpha: number; private style; public frame; protected textureManager; public list: MockGameObject[] = []; public name: string; - constructor(textureManager: MockTextureManager, x, y) { + constructor(textureManager: MockTextureManager, x: number, y: number) { this.x = x; this.y = y; this.frame = {}; this.textureManager = textureManager; } - setVisible(visible) { + setVisible(visible: boolean): this { this.visible = visible; + return this; } - once(_event, _callback, _source) {} + once(_event, _callback, _source): this { + return this; + } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } - removeBetween(_startIndex, _endIndex, _destroyChild) { + removeBetween(_startIndex, _endIndex, _destroyChild): this { // Removes multiple children across an index range + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setSize(_width, _height) { + setSize(_width: number, _height: number): this { // Sets the size of this Game Object. + return this; } - setMask() { + setMask(): this { /// Sets the mask that this Game Object will use to render with. + return this; } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } - setInteractive = () => null; + setInteractive(): this { + return this; + } - setOrigin(x, y) { + setOrigin(x = 0.5, y = x): this { this.x = x; this.y = y; + return this; } - setAlpha(alpha) { + setAlpha(alpha = 1): this { this.alpha = alpha; + return this; } - setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean) { + setFrame(_frame, _updateSize?: boolean, _updateOrigin?: boolean): this { // Sets the frame this Game Object will use to render with. + return this; } - setScale(_scale) { + setScale(_x = 1, _y = _x): this { // Sets the scale of this Game Object. + return this; } - setPosition(x, y) { + setPosition(x = 0, y = x, _z = 0, _w = 0): this { this.x = x; this.y = y; + return this; } - setX(x) { + setX(x = 0): this { this.x = x; + return this; } - setY(y) { + setY(y = 0): this { this.y = y; + return this; } destroy() { this.list = []; } - setShadow(_shadowXpos, _shadowYpos, _shadowColor) { + setShadow(_shadowXpos, _shadowYpos, _shadowColor): this { // Sets the shadow settings for this Game Object. + return this; } - setLineSpacing(_lineSpacing) { + setLineSpacing(_lineSpacing): this { // Sets the line spacing value of this Game Object. + return this; } - setText(_text) { + setText(_text): this { // Sets the text this Game Object will display. + return this; } - setAngle(_angle) { + setAngle(_angle): this { // Sets the angle of this Game Object. + return this; } - setShadowOffset(_offsetX, _offsetY) { + setShadowOffset(_offsetX, _offsetY): this { // Sets the shadow offset values. + return this; } setWordWrapWidth(_width) { // Sets the width (in pixels) to use for wrapping lines. } - setFontSize(_fontSize) { + setFontSize(_fontSize): this { // Sets the font size of this Game Object. + return this; } getBounds() { return { width: this.width, height: this.height }; } - setColor(_color) { + setColor(_color): this { // Sets the tint of this Game Object. + return this; } - setShadowColor(_color) { + setShadowColor(_color): this { // Sets the shadow color. + return this; } - setTint(_color) { + setTint(_color: this) { // Sets the tint of this Game Object. + return this; } - setStrokeStyle(_thickness, _color) { + setStrokeStyle(_thickness, _color): this { // Sets the stroke style for the graphics. return this; } - setDepth(_depth) { - // Sets the depth of this Game Object. + setDepth(_depth): this { + // Sets the depth of this Game Object.\ + return this; } - setTexture(_texture) { - // Sets the texture this Game Object will use to render with. + setTexture(_texture): this { + // Sets the texture this Game Object will use to render with.\ + return this; } - clearTint() { - // Clears any previously set tint. + clearTint(): this { + // Clears any previously set tint.\ + return this; } - sendToBack() { - // Sends this Game Object to the back of its parent's display list. + sendToBack(): this { + // Sends this Game Object to the back of its parent's display list.\ + return this; } - moveTo(_obj) { - // Moves this Game Object to the given index in the list. + moveTo(_obj): this { + // Moves this Game Object to the given index in the list.\ + return this; } - moveAbove(_obj) { + moveAbove(_obj): this { // Moves this Game Object to be above the given Game Object in the display list. + return this; } - moveBelow(_obj) { + moveBelow(_obj): this { // Moves this Game Object to be below the given Game Object in the display list. + return this; } - setName(name: string) { + setName(name: string): this { this.name = name; + return this; } - bringToTop(_obj) { + bringToTop(_obj): this { // Brings this Game Object to the top of its parents display list. + return this; } - on(_event, _callback, _source) {} + on(_event, _callback, _source): this { + return this; + } - add(obj) { + add(...obj: MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + this.list.push(...obj); + return this; } - removeAll() { + removeAll(): this { // Removes all Game Objects from this Container. this.list = []; + return this; } - addAt(obj, index) { + addAt(obj: MockGameObject | MockGameObject[], index = 0): this { // Adds a Game Object to this Container at the given index. - this.list.splice(index, 0, obj); + if (!Array.isArray(obj)) { + obj = [obj]; + } + this.list.splice(index, 0, ...obj); + return this; } - remove(obj) { - const index = this.list.indexOf(obj); - if (index !== -1) { - this.list.splice(index, 1); + remove(obj: MockGameObject | MockGameObject[], destroyChild = false): this { + if (!Array.isArray(obj)) { + obj = [obj]; } + for (const item of obj) { + const index = this.list.indexOf(item); + if (index !== -1) { + this.list.splice(index, 1); + } + if (destroyChild) { + item.destroy?.(); + } + } + return this; } getIndex(obj) { @@ -210,15 +264,28 @@ export default class MockContainer implements MockGameObject { return this.list; } - getByName(key: string) { + getByName(key: string): MockGameObject | null { return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0); } - disableInteractive = () => null; + disableInteractive(): this { + return this; + } - each(method) { + each(method): this { for (const item of this.list) { method(item); } + return this; + } + + copyPosition(source: { x?: number; y?: number }): this { + if (source.x !== undefined) { + this.x = source.x; + } + if (source.y !== undefined) { + this.y = source.y; + } + return this; } } diff --git a/test/testUtils/mocks/mocksContainer/mockGraphics.ts b/test/testUtils/mocks/mocksContainer/mockGraphics.ts index ebf84e935e3..1650d2f9f60 100644 --- a/test/testUtils/mocks/mocksContainer/mockGraphics.ts +++ b/test/testUtils/mocks/mocksContainer/mockGraphics.ts @@ -8,57 +8,76 @@ export default class MockGraphics implements MockGameObject { this.scene = textureManager.scene; } - fillStyle(_color) { + fillStyle(_color): this { // Sets the fill style to be used by the fill methods. + return this; } - beginPath() { + beginPath(): this { // Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path. + return this; } - fillRect(_x, _y, _width, _height) { + fillRect(_x, _y, _width, _height): this { // Adds a rectangle shape to the path which is filled when you call fill(). + return this; } - createGeometryMask() { + createGeometryMask(): this { // Creates a geometry mask. + return this; } - setOrigin(_x, _y) {} + setOrigin(_x, _y): this { + return this; + } - setAlpha(_alpha) {} + setAlpha(_alpha): this { + return this; + } - setVisible(_visible) {} + setVisible(_visible): this { + return this; + } - setName(_name) {} + setName(_name) { + return this; + } - once(_event, _callback, _source) {} + once(_event, _callback, _source) { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } destroy() { this.list = []; } - setScale(_scale) { - // Sets the scale of this Game Object. + setScale(_scale): this { + return this; } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -90,4 +109,8 @@ export default class MockGraphics implements MockGameObject { getAll() { return this.list; } + + copyPosition(_source): this { + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index 7bdf343759d..f9a92c41904 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -10,34 +10,47 @@ export default class MockRectangle implements MockGameObject { this.fillColor = fillColor; this.scene = textureManager.scene; } - setOrigin(_x, _y) {} + setOrigin(_x, _y): this { + return this; + } - setAlpha(_alpha) {} - setVisible(_visible) {} + setAlpha(_alpha): this { + return this; + } + setVisible(_visible): this { + return this; + } - setName(_name) {} + setName(_name): this { + return this; + } - once(_event, _callback, _source) {} + once(_event, _callback, _source): this { + return this; + } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy + return this; } addedToScene() { // This callback is invoked when this Game Object is added to a Scene. } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. + return this; } destroy() { this.list = []; } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -45,16 +58,18 @@ export default class MockRectangle implements MockGameObject { this.list = []; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } getIndex(obj) { @@ -69,9 +84,12 @@ export default class MockRectangle implements MockGameObject { getAll() { return this.list; } - setScale(_scale) { + setScale(_scale): this { // return this.phaserText.setScale(scale); + return this; } - off() {} + off(): this { + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index dcc3588f127..b8ccfcced1f 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -1,6 +1,5 @@ import Phaser from "phaser"; import type { MockGameObject } from "../mockGameObject"; -import Sprite = Phaser.GameObjects.Sprite; import Frame = Phaser.Textures.Frame; export default class MockSprite implements MockGameObject { @@ -21,7 +20,9 @@ export default class MockSprite implements MockGameObject { Phaser.GameObjects.Sprite.prototype.setInteractive = this.setInteractive; // @ts-ignore Phaser.GameObjects.Sprite.prototype.setTexture = this.setTexture; + // @ts-ignore Phaser.GameObjects.Sprite.prototype.setSizeToFrame = this.setSizeToFrame; + // @ts-ignore Phaser.GameObjects.Sprite.prototype.setFrame = this.setFrame; // Phaser.GameObjects.Sprite.prototype.disable = this.disable; @@ -37,46 +38,55 @@ export default class MockSprite implements MockGameObject { }; } - setTexture(_key: string, _frame?: string | number) { + setTexture(_key: string, _frame?: string | number): this { return this; } - setSizeToFrame(_frame?: boolean | Frame): Sprite { - return {} as Sprite; + setSizeToFrame(_frame?: boolean | Frame): this { + return this; } - setPipeline(obj) { + setPipeline(obj): this { // Sets the pipeline of this Game Object. - return this.phaserSprite.setPipeline(obj); + this.phaserSprite.setPipeline(obj); + return this; } - off(_event, _callback, _source) {} + off(_event, _callback, _source): this { + return this; + } - setTintFill(color) { + setTintFill(color): this { // Sets the tint fill color. - return this.phaserSprite.setTintFill(color); + this.phaserSprite.setTintFill(color); + return this; } - setScale(scale) { - return this.phaserSprite.setScale(scale); + setScale(scale = 1): this { + this.phaserSprite.setScale(scale); + return this; } - setOrigin(x, y) { - return this.phaserSprite.setOrigin(x, y); + setOrigin(x = 0.5, y = x): this { + this.phaserSprite.setOrigin(x, y); + return this; } - setSize(width, height) { + setSize(width, height): this { // Sets the size of this Game Object. - return this.phaserSprite.setSize(width, height); + this.phaserSprite.setSize(width, height); + return this; } - once(event, callback, source) { - return this.phaserSprite.once(event, callback, source); + once(event, callback, source): this { + this.phaserSprite.once(event, callback, source); + return this; } - removeFromDisplayList() { + removeFromDisplayList(): this { // same as remove or destroy - return this.phaserSprite.removeFromDisplayList(); + this.phaserSprite.removeFromDisplayList(); + return this; } addedToScene() { @@ -84,97 +94,117 @@ export default class MockSprite implements MockGameObject { return this.phaserSprite.addedToScene(); } - setVisible(visible) { - return this.phaserSprite.setVisible(visible); + setVisible(visible): this { + this.phaserSprite.setVisible(visible); + return this; } - setPosition(x, y) { - return this.phaserSprite.setPosition(x, y); + setPosition(x, y): this { + this.phaserSprite.setPosition(x, y); + return this; } - setRotation(radians) { - return this.phaserSprite.setRotation(radians); + setRotation(radians): this { + this.phaserSprite.setRotation(radians); + return this; } - stop() { - return this.phaserSprite.stop(); + stop(): this { + this.phaserSprite.stop(); + return this; } - setInteractive = () => null; - - on(event, callback, source) { - return this.phaserSprite.on(event, callback, source); + setInteractive(): this { + return this; } - setAlpha(alpha) { - return this.phaserSprite.setAlpha(alpha); + on(event, callback, source): this { + this.phaserSprite.on(event, callback, source); + return this; } - setTint(color) { + setAlpha(alpha): this { + this.phaserSprite.setAlpha(alpha); + return this; + } + + setTint(color): this { // Sets the tint of this Game Object. - return this.phaserSprite.setTint(color); + this.phaserSprite.setTint(color); + return this; } - setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean) { + setFrame(frame, _updateSize?: boolean, _updateOrigin?: boolean): this { // Sets the frame this Game Object will use to render with. this.frame = frame; - return frame; + return this; } - setPositionRelative(source, x, y) { + setPositionRelative(source, x, y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. - return this.phaserSprite.setPositionRelative(source, x, y); + this.phaserSprite.setPositionRelative(source, x, y); + return this; } - setY(y) { - return this.phaserSprite.setY(y); + setY(y: number): this { + this.phaserSprite.setY(y); + return this; } - setCrop(x, y, width, height) { + setCrop(x: number, y: number, width: number, height: number): this { // Sets the crop size of this Game Object. - return this.phaserSprite.setCrop(x, y, width, height); + this.phaserSprite.setCrop(x, y, width, height); + return this; } - clearTint() { + clearTint(): this { // Clears any previously set tint. - return this.phaserSprite.clearTint(); + this.phaserSprite.clearTint(); + return this; } - disableInteractive() { + disableInteractive(): this { // Disables Interactive features of this Game Object. - return null; + return this; } apply() { - return this.phaserSprite.apply(); + this.phaserSprite.apply(); + return this; } - play() { + play(): this { // return this.phaserSprite.play(); return this; } - setPipelineData(key, value) { + setPipelineData(key: string, value: any): this { this.pipelineData[key] = value; + return this; } destroy() { return this.phaserSprite.destroy(); } - setName(name) { - return this.phaserSprite.setName(name); + setName(name: string): this { + this.phaserSprite.setName(name); + return this; } - setAngle(angle) { - return this.phaserSprite.setAngle(angle); + setAngle(angle): this { + this.phaserSprite.setAngle(angle); + return this; } - setMask() {} + setMask(): this { + return this; + } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } removeAll() { @@ -182,16 +212,18 @@ export default class MockSprite implements MockGameObject { this.list = []; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } getIndex(obj) { @@ -206,4 +238,9 @@ export default class MockSprite implements MockGameObject { getAll() { return this.list; } + + copyPosition(obj): this { + this.phaserSprite.copyPosition(obj); + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockText.ts b/test/testUtils/mocks/mocksContainer/mockText.ts index 1f3f0ad792f..dc648616fe6 100644 --- a/test/testUtils/mocks/mocksContainer/mockText.ts +++ b/test/testUtils/mocks/mocksContainer/mockText.ts @@ -107,42 +107,51 @@ export default class MockText implements MockGameObject { } } - setScale(_scale) { + setScale(_scale): this { // return this.phaserText.setScale(scale); + return this; } - setShadow(_shadowXpos, _shadowYpos, _shadowColor) { + setShadow(_shadowXpos, _shadowYpos, _shadowColor): this { // Sets the shadow settings for this Game Object. // return this.phaserText.setShadow(shadowXpos, shadowYpos, shadowColor); + return this; } - setLineSpacing(_lineSpacing) { + setLineSpacing(_lineSpacing): this { // Sets the line spacing value of this Game Object. // return this.phaserText.setLineSpacing(lineSpacing); + return this; } - setOrigin(_x, _y) { + setOrigin(_x, _y): this { // return this.phaserText.setOrigin(x, y); + return this; } - once(_event, _callback, _source) { + once(_event, _callback, _source): this { // return this.phaserText.once(event, callback, source); + return this; } off(_event, _callback, _obj) {} removedFromScene() {} - addToDisplayList() {} - - setStroke(_color, _thickness) { - // Sets the stroke color and thickness. - // return this.phaserText.setStroke(color, thickness); + addToDisplayList(): this { + return this; } - removeFromDisplayList() { + setStroke(_color, _thickness): this { + // Sets the stroke color and thickness. + // return this.phaserText.setStroke(color, thickness); + return this; + } + + removeFromDisplayList(): this { // same as remove or destroy // return this.phaserText.removeFromDisplayList(); + return this; } addedToScene() { @@ -154,12 +163,14 @@ export default class MockText implements MockGameObject { // return this.phaserText.setVisible(visible); } - setY(_y) { + setY(_y): this { // return this.phaserText.setY(y); + return this; } - setX(_x) { + setX(_x): this { // return this.phaserText.setX(x); + return this; } /** @@ -169,27 +180,33 @@ export default class MockText implements MockGameObject { * @param z The z position of this Game Object. Default 0. * @param w The w position of this Game Object. Default 0. */ - setPosition(_x?: number, _y?: number, _z?: number, _w?: number) {} + setPosition(_x?: number, _y?: number, _z?: number, _w?: number): this { + return this; + } - setText(text) { + setText(text): this { // Sets the text this Game Object will display. // return this.phaserText.setText\(text); this.text = text; + return this; } - setAngle(_angle) { + setAngle(_angle): this { // Sets the angle of this Game Object. // return this.phaserText.setAngle(angle); + return this; } - setPositionRelative(_source, _x, _y) { + setPositionRelative(_source, _x, _y): this { /// Sets the position of this Game Object to be a relative position from the source Game Object. // return this.phaserText.setPositionRelative(source, x, y); + return this; } - setShadowOffset(_offsetX, _offsetY) { + setShadowOffset(_offsetX, _offsetY): this { // Sets the shadow offset values. // return this.phaserText.setShadowOffset(offsetX, offsetY); + return this; } setWordWrapWidth(width) { @@ -197,9 +214,10 @@ export default class MockText implements MockGameObject { this.wordWrapWidth = width; } - setFontSize(_fontSize) { + setFontSize(_fontSize): this { // Sets the font size of this Game Object. // return this.phaserText.setFontSize(fontSize); + return this; } getBounds() { @@ -209,25 +227,31 @@ export default class MockText implements MockGameObject { }; } - setColor(color: string) { + setColor(color: string): this { this.color = color; + return this; } - setInteractive = () => null; + setInteractive(): this { + return this; + } - setShadowColor(_color) { + setShadowColor(_color): this { // Sets the shadow color. // return this.phaserText.setShadowColor(color); + return this; } - setTint(_color) { + setTint(_color): this { // Sets the tint of this Game Object. // return this.phaserText.setTint(color); + return this; } - setStrokeStyle(_thickness, _color) { + setStrokeStyle(_thickness, _color): this { // Sets the stroke style for the graphics. // return this.phaserText.setStrokeStyle(thickness, color); + return this; } destroy() { @@ -235,20 +259,24 @@ export default class MockText implements MockGameObject { this.list = []; } - setAlpha(_alpha) { + setAlpha(_alpha): this { // return this.phaserText.setAlpha(alpha); + return this; } - setName(name: string) { + setName(name: string): this { this.name = name; + return this; } - setAlign(_align) { + setAlign(_align): this { // return this.phaserText.setAlign(align); + return this; } - setMask() { + setMask(): this { /// Sets the mask that this Game Object will use to render with. + return this; } getBottomLeft() { @@ -265,37 +293,43 @@ export default class MockText implements MockGameObject { }; } - disableInteractive() { + disableInteractive(): this { // Disables interaction with this Game Object. + return this; } - clearTint() { + clearTint(): this { // Clears tint on this Game Object. + return this; } - add(obj) { + add(obj): this { // Adds a child to this Game Object. this.list.push(obj); + return this; } - removeAll() { + removeAll(): this { // Removes all Game Objects from this Container. this.list = []; + return this; } - addAt(obj, index) { + addAt(obj, index): this { // Adds a Game Object to this Container at the given index. this.list.splice(index, 0, obj); + return this; } - remove(obj) { + remove(obj): this { const index = this.list.indexOf(obj); if (index !== -1) { this.list.splice(index, 1); } + return this; } - getIndex(obj) { + getIndex(obj): number { const index = this.list.indexOf(obj); return index || -1; } diff --git a/test/testUtils/testFileInitialization.ts b/test/testUtils/testFileInitialization.ts index 15635289e6f..ba90cba7d5b 100644 --- a/test/testUtils/testFileInitialization.ts +++ b/test/testUtils/testFileInitialization.ts @@ -70,10 +70,10 @@ export function initTestFile() { * @param x The relative x position * @param y The relative y position */ - const setPositionRelative = function (guideObject: any, x: number, y: number) { + const setPositionRelative = function (guideObject: any, x: number, y: number): any { const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); }; Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; From d8c00616fc155fdf5a6b90a48a2cc3bb65c80819 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 20:00:54 -0500 Subject: [PATCH 07/20] [Test] Add iterate, fix each method in mock container (#5882) --- .../mocks/mocksContainer/mockContainer.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index e116e7d151a..b392e61f9f7 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -272,9 +272,24 @@ export default class MockContainer implements MockGameObject { return this; } - each(method): this { + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks + each(callback: Function, context?: object, ...args: any[]): this { + if (context !== undefined) { + callback = callback.bind(context); + } + for (const item of this.list.slice()) { + callback(item, ...args); + } + return this; + } + + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the method it mocks + iterate(callback: Function, context?: object, ...args: any[]): this { + if (context !== undefined) { + callback = callback.bind(context); + } for (const item of this.list) { - method(item); + callback(item, ...args); } return this; } From 86fa3198fd897b3aafbcba7abf5d4dc6e972738e Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 21:47:27 -0500 Subject: [PATCH 08/20] [Test] Fix mock text to make it compatible with method chaining (#5884) --- test/testUtils/mocks/mocksContainer/mockText.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/testUtils/mocks/mocksContainer/mockText.ts b/test/testUtils/mocks/mocksContainer/mockText.ts index dc648616fe6..8f72cd0d34f 100644 --- a/test/testUtils/mocks/mocksContainer/mockText.ts +++ b/test/testUtils/mocks/mocksContainer/mockText.ts @@ -159,8 +159,8 @@ export default class MockText implements MockGameObject { // return this.phaserText.addedToScene(); } - setVisible(_visible) { - // return this.phaserText.setVisible(visible); + setVisible(_visible): this { + return this; } setY(_y): this { @@ -209,9 +209,10 @@ export default class MockText implements MockGameObject { return this; } - setWordWrapWidth(width) { + setWordWrapWidth(width): this { // Sets the width (in pixels) to use for wrapping lines. this.wordWrapWidth = width; + return this; } setFontSize(_fontSize): this { @@ -351,5 +352,6 @@ export default class MockText implements MockGameObject { return this.runWordWrap(this.text).split("\n"); } + // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the class this mocks on(_event: string | symbol, _fn: Function, _context?: any) {} } From f9e6785c351d07e7f2a7e11299831aff476c6db4 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Wed, 28 May 2025 22:18:06 -0500 Subject: [PATCH 09/20] [Test] Fix mocks' add method to match phaser signature (#5885) --- test/testUtils/mocks/mocksContainer/mockContainer.ts | 9 ++++++--- test/testUtils/mocks/mocksContainer/mockRectangle.ts | 8 ++++++-- test/testUtils/mocks/mocksContainer/mockSprite.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index b392e61f9f7..883c3856c26 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -214,9 +214,12 @@ export default class MockContainer implements MockGameObject { return this; } - add(...obj: MockGameObject[]): this { - // Adds a child to this Game Object. - this.list.push(...obj); + add(obj: MockGameObject | MockGameObject[]): this { + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } return this; } diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index f9a92c41904..c11af824c56 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -47,9 +47,13 @@ export default class MockRectangle implements MockGameObject { this.list = []; } - add(obj): this { + add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } return this; } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index b8ccfcced1f..ad70b6ced07 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -201,9 +201,13 @@ export default class MockSprite implements MockGameObject { return this; } - add(obj): this { + add(obj: MockGameObject | MockGameObject[]): this { // Adds a child to this Game Object. - this.list.push(obj); + if (Array.isArray(obj)) { + this.list.push(...obj); + } else { + this.list.push(obj); + } return this; } From 8fcb33d11aab3586e452ed02a668774677f65239 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 29 May 2025 19:11:53 -0500 Subject: [PATCH 10/20] [Bug] Fix improperly named sprite key for enemy boss' battle UI (#5888) --- src/ui/battle-info/battle-info.ts | 2 +- src/ui/battle-info/enemy-battle-info.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts index 71596bf0f43..7e6cc12bd3d 100644 --- a/src/ui/battle-info/battle-info.ts +++ b/src/ui/battle-info/battle-info.ts @@ -230,7 +230,7 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { this.box = globalScene.add.sprite(0, 0, this.getTextureName()).setName("box").setOrigin(1, 0.5); this.add(this.box); - this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO) + this.nameText = addTextObject(posParams.nameTextX, posParams.nameTextY, "", TextStyle.BATTLE_INFO) .setName("text_name") .setOrigin(0); this.add(this.nameText); diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts index e8f5434dcf2..7c16f01ac38 100644 --- a/src/ui/battle-info/enemy-battle-info.ts +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -29,7 +29,7 @@ export class EnemyBattleInfo extends BattleInfo { } override getTextureName(): string { - return this.boss ? "pbinfo_enemy_boss_mini" : "pbinfo_enemy_mini"; + return this.boss ? "pbinfo_enemy_boss" : "pbinfo_enemy_mini"; } override constructTypeIcons(): void { From 14e01c3da12e1fa6cabdb87259eaef0bec9c8cbb Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 29 May 2025 22:27:39 -0500 Subject: [PATCH 11/20] [Test] Add missing methods to many mocks (#5891) --- test/testUtils/mocks/mockGameObject.ts | 3 +++ test/testUtils/mocks/mockVideoGameObject.ts | 17 +++++++++++++---- .../mocks/mocksContainer/mockContainer.ts | 6 ++++++ .../mocks/mocksContainer/mockGraphics.ts | 6 ++++++ .../mocks/mocksContainer/mockRectangle.ts | 6 ++++++ .../mocks/mocksContainer/mockSprite.ts | 6 ++++++ test/testUtils/mocks/mocksContainer/mockText.ts | 5 +++++ .../mocks/mocksContainer/mockTexture.ts | 11 +++++++++++ 8 files changed, 56 insertions(+), 4 deletions(-) diff --git a/test/testUtils/mocks/mockGameObject.ts b/test/testUtils/mocks/mockGameObject.ts index 0dff7c48c73..1e156b99d21 100644 --- a/test/testUtils/mocks/mockGameObject.ts +++ b/test/testUtils/mocks/mockGameObject.ts @@ -1,4 +1,7 @@ export interface MockGameObject { name: string; + active: boolean; destroy?(): void; + setActive(active: boolean): this; + setName(name: string): this; } diff --git a/test/testUtils/mocks/mockVideoGameObject.ts b/test/testUtils/mocks/mockVideoGameObject.ts index 65a5c37b244..1789229b1c7 100644 --- a/test/testUtils/mocks/mockVideoGameObject.ts +++ b/test/testUtils/mocks/mockVideoGameObject.ts @@ -3,11 +3,20 @@ import type { MockGameObject } from "./mockGameObject"; /** Mocks video-related stuff */ export class MockVideoGameObject implements MockGameObject { public name: string; + public active = true; public play = () => null; public stop = () => this; - public setOrigin = () => null; - public setScale = () => null; - public setVisible = () => null; - public setLoop = () => null; + public setOrigin = () => this; + public setScale = () => this; + public setVisible = () => this; + public setLoop = () => this; + public setName = (name: string) => { + this.name = name; + return this; + }; + public setActive = (active: boolean) => { + this.active = active; + return this; + }; } diff --git a/test/testUtils/mocks/mocksContainer/mockContainer.ts b/test/testUtils/mocks/mocksContainer/mockContainer.ts index 883c3856c26..f1371643ce3 100644 --- a/test/testUtils/mocks/mocksContainer/mockContainer.ts +++ b/test/testUtils/mocks/mocksContainer/mockContainer.ts @@ -14,6 +14,7 @@ export default class MockContainer implements MockGameObject { protected textureManager; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager: MockTextureManager, x: number, y: number) { this.x = x; @@ -306,4 +307,9 @@ export default class MockContainer implements MockGameObject { } return this; } + + setActive(active: boolean): this { + this.active = active; + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockGraphics.ts b/test/testUtils/mocks/mocksContainer/mockGraphics.ts index 1650d2f9f60..cd43bb3a877 100644 --- a/test/testUtils/mocks/mocksContainer/mockGraphics.ts +++ b/test/testUtils/mocks/mocksContainer/mockGraphics.ts @@ -4,6 +4,7 @@ export default class MockGraphics implements MockGameObject { private scene; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager, _config) { this.scene = textureManager.scene; } @@ -113,4 +114,9 @@ export default class MockGraphics implements MockGameObject { copyPosition(_source): this { return this; } + + setActive(active: boolean): this { + this.active = active; + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockRectangle.ts b/test/testUtils/mocks/mocksContainer/mockRectangle.ts index c11af824c56..7f54a0e255f 100644 --- a/test/testUtils/mocks/mocksContainer/mockRectangle.ts +++ b/test/testUtils/mocks/mocksContainer/mockRectangle.ts @@ -5,6 +5,7 @@ export default class MockRectangle implements MockGameObject { private scene; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager, _x, _y, _width, _height, fillColor) { this.fillColor = fillColor; @@ -96,4 +97,9 @@ export default class MockRectangle implements MockGameObject { off(): this { return this; } + + setActive(active: boolean): this { + this.active = active; + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockSprite.ts b/test/testUtils/mocks/mocksContainer/mockSprite.ts index ad70b6ced07..df36b3a29fd 100644 --- a/test/testUtils/mocks/mocksContainer/mockSprite.ts +++ b/test/testUtils/mocks/mocksContainer/mockSprite.ts @@ -13,6 +13,7 @@ export default class MockSprite implements MockGameObject { public anims; public list: MockGameObject[] = []; public name: string; + public active = true; constructor(textureManager, x, y, texture) { this.textureManager = textureManager; this.scene = textureManager.scene; @@ -247,4 +248,9 @@ export default class MockSprite implements MockGameObject { this.phaserSprite.copyPosition(obj); return this; } + + setActive(active: boolean): this { + this.phaserSprite.setActive(active); + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockText.ts b/test/testUtils/mocks/mocksContainer/mockText.ts index 8f72cd0d34f..2345ff70c0d 100644 --- a/test/testUtils/mocks/mocksContainer/mockText.ts +++ b/test/testUtils/mocks/mocksContainer/mockText.ts @@ -12,6 +12,7 @@ export default class MockText implements MockGameObject { public text = ""; public name: string; public color?: string; + public active = true; constructor(textureManager, _x, _y, _content, _styleOptions) { this.scene = textureManager.scene; @@ -354,4 +355,8 @@ export default class MockText implements MockGameObject { // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the class this mocks on(_event: string | symbol, _fn: Function, _context?: any) {} + + setActive(_active: boolean): this { + return this; + } } diff --git a/test/testUtils/mocks/mocksContainer/mockTexture.ts b/test/testUtils/mocks/mocksContainer/mockTexture.ts index eb8b70902fa..3b01f9ab4ea 100644 --- a/test/testUtils/mocks/mocksContainer/mockTexture.ts +++ b/test/testUtils/mocks/mocksContainer/mockTexture.ts @@ -12,6 +12,7 @@ export default class MockTexture implements MockGameObject { public frames: object; public firstFrame: string; public name: string; + public active: boolean; constructor(manager, key: string, source) { this.manager = manager; @@ -39,4 +40,14 @@ export default class MockTexture implements MockGameObject { getSourceImage() { return null; } + + setActive(active: boolean): this { + this.active = active; + return this; + } + + setName(name: string): this { + this.name = name; + return this; + } } From ff6f9131ae50da6002a7114a721bea1d727a8dde Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Fri, 30 May 2025 05:39:55 +0200 Subject: [PATCH 12/20] [Dev] Fixed biome override not working for Daily Mode (#5776) --- src/game-mode.ts | 10 +++++++--- src/overrides.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/game-mode.ts b/src/game-mode.ts index ec7171b0024..97398d2b0a9 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -7,7 +7,7 @@ import type PokemonSpecies from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species"; import type { Arena } from "./field/arena"; import Overrides from "#app/overrides"; -import { randSeedInt, randSeedItem } from "#app/utils/common"; +import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils/common"; import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; import { Challenges } from "./enums/challenges"; @@ -124,16 +124,20 @@ export class GameMode implements GameModeConfig { /** * @returns either: - * - random biome for Daily mode * - override from overrides.ts + * - random biome for Daily mode * - Town */ getStartingBiome(): Biome { + if (!isNullOrUndefined(Overrides.STARTING_BIOME_OVERRIDE)) { + return Overrides.STARTING_BIOME_OVERRIDE; + } + switch (this.modeId) { case GameModes.DAILY: return getDailyStartingBiome(); default: - return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; + return Biome.TOWN; } } diff --git a/src/overrides.ts b/src/overrides.ts index 5bbd29b355f..95c758d7c43 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -73,7 +73,7 @@ class DefaultOverrides { */ readonly BATTLE_STYLE_OVERRIDE: BattleStyle | null = null; readonly STARTING_WAVE_OVERRIDE: number = 0; - readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; + readonly STARTING_BIOME_OVERRIDE: Biome | null = null; readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; /** Multiplies XP gained by this value including 0. Set to null to ignore the override. */ readonly XP_MULTIPLIER_OVERRIDE: number | null = null; From 901ed7324770e7a8d94cd13f78b5d20796b36ed4 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 29 May 2025 22:51:36 -0500 Subject: [PATCH 13/20] Cleanup evolution scene handler --- src/ui/evolution-scene-handler.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts index cea91ce4e2c..7372fc6f2b5 100644 --- a/src/ui/evolution-scene-handler.ts +++ b/src/ui/evolution-scene-handler.ts @@ -22,18 +22,12 @@ export default class EvolutionSceneHandler extends MessageUiHandler { const ui = this.getUi(); this.evolutionContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); - ui.add(this.evolutionContainer); - const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType); - messageBg.setOrigin(0, 1); - messageBg.setVisible(false); - ui.add(messageBg); + const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType).setOrigin(0, 1).setVisible(false); this.messageBg = messageBg; - this.messageContainer = globalScene.add.container(12, -39); - this.messageContainer.setVisible(false); - ui.add(this.messageContainer); + this.messageContainer = globalScene.add.container(12, -39).setVisible(false); const message = addTextObject(0, 0, "", TextStyle.MESSAGE, { maxLines: 2, @@ -43,6 +37,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler { }); this.messageContainer.add(message); + ui.add([this.evolutionContainer, this.messageBg, this.messageContainer]); + this.message = message; this.initPromptSprite(this.messageContainer); @@ -52,10 +48,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler { super.show(_args); globalScene.ui.bringToTop(this.evolutionContainer); - globalScene.ui.bringToTop(this.messageBg); - globalScene.ui.bringToTop(this.messageContainer); - this.messageBg.setVisible(true); - this.messageContainer.setVisible(true); + globalScene.ui.bringToTop(this.messageBg.setVisible(true)); + globalScene.ui.bringToTop(this.messageContainer.setVisible(true)); return true; } From d5f7665b15ef923d102b18c2e812edd73604c372 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 30 May 2025 12:24:05 -0700 Subject: [PATCH 14/20] [Test] Update test utils (#5848) * [Test] Add/update test utils - Add `FieldHelper` which has methods to mock a pokemon's ability or force a pokemon to be Terastallized - Add `MoveHelper#use` which can be used to remove the need for setting pokemon move overrides by modifying the moveset of the pokemon - Add `MoveHelper#selectEnemyMove` to make an enemy pokemon select a specific move - Add `MoveHelper#forceEnemyMove` which modifies the pokemon's moveset and then uses `selectEnemyMove` - Fix `GameManager#toNextTurn` to work correctly in double battles - Add `GameManager#toEndOfTurn` which advances to the end of the turn * Update some tests - Disable broken Good As Gold test and add `.edgeCase` to Good As Gold - Fix Powder test - Update some tests to demonstrate new methods --- src/data/abilities/ability.ts | 7 +- test/abilities/good_as_gold.test.ts | 32 ++++---- test/moves/alluring_voice.test.ts | 10 +-- test/moves/chloroblast.test.ts | 15 ++-- test/moves/powder.test.ts | 17 ++-- test/testUtils/gameManager.ts | 30 ++++--- test/testUtils/helpers/field-helper.ts | 87 +++++++++++++++++++++ test/testUtils/helpers/moveHelper.ts | 104 ++++++++++++++++++++++++- 8 files changed, 252 insertions(+), 50 deletions(-) create mode 100644 test/testUtils/helpers/field-helper.ts diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index b677dd2bd11..f49863639f0 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -7425,7 +7425,12 @@ export function initAbilities() { .uncopiable() .attr(NoTransformAbilityAbAttr), new Ability(Abilities.GOOD_AS_GOLD, 9) - .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS && ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget)) + .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => + pokemon !== attacker + && move.category === MoveCategory.STATUS + && ![ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES, MoveTarget.USER_SIDE ].includes(move.moveTarget) + ) + .edgeCase() // Heal Bell should not cure the status of a Pokemon with Good As Gold .ignorable(), new Ability(Abilities.VESSEL_OF_RUIN, 9) .attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75) diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index 09bdaafb11f..9b1d582f64c 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; -import { allAbilities } from "#app/data/data-lists"; import { ArenaTagSide } from "#app/data/arena-tag"; +import { allAbilities } from "#app/data/data-lists"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#app/enums/stat"; @@ -107,35 +107,33 @@ describe("Abilities - Good As Gold", () => { expect(game.scene.getPlayerField()[1].getTag(BattlerTagType.HELPING_HAND)).toBeUndefined(); }); - it("should block the ally's heal bell, but only if the good as gold user is on the field", async () => { - game.override.battleStyle("double"); - game.override.moveset([Moves.HEAL_BELL, Moves.SPLASH]); - game.override.statusEffect(StatusEffect.BURN); - await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS, Species.ABRA]); - const [good_as_gold, ball_fetch] = game.scene.getPlayerField(); - - // Force second pokemon to have ball fetch to isolate to a single mon. - vi.spyOn(ball_fetch, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]); + // TODO: re-enable when heal bell is fixed + it.todo("should block the ally's heal bell, but only if the good as gold user is on the field", async () => { + game.override.battleStyle("double").statusEffect(StatusEffect.BURN); + await game.classicMode.startBattle([Species.MILOTIC, Species.FEEBAS, Species.ABRA]); + const [milotic, feebas, abra] = game.scene.getPlayerParty(); + game.field.mockAbility(milotic, Abilities.GOOD_AS_GOLD); + game.field.mockAbility(feebas, Abilities.BALL_FETCH); + game.field.mockAbility(abra, Abilities.BALL_FETCH); // turn 1 - game.move.select(Moves.SPLASH, 0); - game.move.select(Moves.HEAL_BELL, 1); + game.move.use(Moves.SPLASH, 0); + game.move.use(Moves.HEAL_BELL, 1); await game.toNextTurn(); - expect(good_as_gold.status?.effect).toBe(StatusEffect.BURN); + expect(milotic.status?.effect).toBe(StatusEffect.BURN); game.doSwitchPokemon(2); - game.move.select(Moves.HEAL_BELL, 0); + game.move.use(Moves.HEAL_BELL, 1); await game.toNextTurn(); - expect(good_as_gold.status?.effect).toBeUndefined(); + expect(milotic.status?.effect).toBeUndefined(); }); it("should not block field targeted effects like rain dance", async () => { game.override.battleStyle("single"); game.override.enemyMoveset([Moves.RAIN_DANCE]); - game.override.weather(WeatherType.NONE); await game.classicMode.startBattle([Species.MAGIKARP]); - game.move.select(Moves.SPLASH, 0); + game.move.use(Moves.SPLASH, 0); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN); diff --git a/test/moves/alluring_voice.test.ts b/test/moves/alluring_voice.test.ts index 240e008f311..9265c5f970d 100644 --- a/test/moves/alluring_voice.test.ts +++ b/test/moves/alluring_voice.test.ts @@ -29,20 +29,18 @@ describe("Moves - Alluring Voice", () => { .disableCrits() .enemySpecies(Species.MAGIKARP) .enemyAbility(Abilities.ICE_SCALES) - .enemyMoveset([Moves.HOWL]) + .enemyMoveset(Moves.HOWL) .startingLevel(10) .enemyLevel(10) - .starterSpecies(Species.FEEBAS) - .ability(Abilities.BALL_FETCH) - .moveset([Moves.ALLURING_VOICE]); + .ability(Abilities.BALL_FETCH); }); it("should confuse the opponent if their stat stages were raised", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([Species.FEEBAS]); const enemy = game.scene.getEnemyPokemon()!; - game.move.select(Moves.ALLURING_VOICE); + game.move.use(Moves.ALLURING_VOICE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to(BerryPhase); diff --git a/test/moves/chloroblast.test.ts b/test/moves/chloroblast.test.ts index 175227bbd5e..b2130da83eb 100644 --- a/test/moves/chloroblast.test.ts +++ b/test/moves/chloroblast.test.ts @@ -1,3 +1,4 @@ +import { MoveResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -22,21 +23,23 @@ describe("Moves - Chloroblast", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([Moves.CHLOROBLAST]) .ability(Abilities.BALL_FETCH) .battleStyle("single") .disableCrits() .enemySpecies(Species.MAGIKARP) - .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset(Moves.PROTECT); + .enemyAbility(Abilities.BALL_FETCH); }); it("should not deal recoil damage if the opponent uses protect", async () => { await game.classicMode.startBattle([Species.FEEBAS]); - game.move.select(Moves.CHLOROBLAST); - await game.phaseInterceptor.to("BerryPhase"); + game.move.use(Moves.CHLOROBLAST); + await game.move.forceEnemyMove(Moves.PROTECT); + await game.toEndOfTurn(); - expect(game.scene.getPlayerPokemon()!.isFullHp()).toBe(true); + const player = game.field.getPlayerPokemon(); + + expect(player.isFullHp()).toBe(true); + expect(player.getLastXMoves()[0]).toMatchObject({ result: MoveResult.MISS, move: Moves.CHLOROBLAST }); }); }); diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index 457beb60f91..c6114db3ff1 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -1,15 +1,15 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import Phaser from "phaser"; -import GameManager from "#test/testUtils/gameManager"; +import { BattlerIndex } from "#app/battle"; +import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { BerryPhase } from "#app/phases/berry-phase"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import { BerryPhase } from "#app/phases/berry-phase"; -import { MoveResult, PokemonMove } from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { Species } from "#enums/species"; import { StatusEffect } from "#enums/status-effect"; -import { BattlerIndex } from "#app/battle"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - Powder", () => { let phaserGame: Phaser.Game; @@ -161,7 +161,6 @@ describe("Moves - Powder", () => { game.move.select(Moves.ROAR, 0, BattlerIndex.ENEMY_2); game.move.select(Moves.SPLASH, 1); await game.toNextTurn(); - await game.toNextTurn(); // Requires game.toNextTurn() twice due to double battle // Turn 2: Enemy should activate Powder twice: From using Ember, and from copying Fiery Dance via Dancer playerPokemon.hp = playerPokemon.getMaxHp(); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 8dd90decf1a..07cea3b1328 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -5,6 +5,7 @@ import { getMoveTargets } from "#app/data/moves/move"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; import { GameModes, getGameMode } from "#app/game-mode"; +import { globalScene } from "#app/global-scene"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import overrides from "#app/overrides"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; @@ -22,15 +23,13 @@ import { TitlePhase } from "#app/phases/title-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; -import ErrorInterceptor from "#test/testUtils/errorInterceptor"; -import type InputsHandler from "#test/testUtils/inputsHandler"; import type BallUiHandler from "#app/ui/ball-ui-handler"; import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; import type CommandUiHandler from "#app/ui/command-ui-handler"; import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import type PartyUiHandler from "#app/ui/party-ui-handler"; +import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; import type TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; -import { UiMode } from "#enums/ui-mode"; import { isNullOrUndefined } from "#app/utils/common"; import { BattleStyle } from "#enums/battle-style"; import { Button } from "#enums/buttons"; @@ -40,24 +39,26 @@ import type { Moves } from "#enums/moves"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PlayerGender } from "#enums/player-gender"; import type { Species } from "#enums/species"; +import { UiMode } from "#enums/ui-mode"; +import ErrorInterceptor from "#test/testUtils/errorInterceptor"; import { generateStarter, waitUntil } from "#test/testUtils/gameManagerUtils"; import GameWrapper from "#test/testUtils/gameWrapper"; import { ChallengeModeHelper } from "#test/testUtils/helpers/challengeModeHelper"; import { ClassicModeHelper } from "#test/testUtils/helpers/classicModeHelper"; import { DailyModeHelper } from "#test/testUtils/helpers/dailyModeHelper"; +import { FieldHelper } from "#test/testUtils/helpers/field-helper"; import { ModifierHelper } from "#test/testUtils/helpers/modifiersHelper"; import { MoveHelper } from "#test/testUtils/helpers/moveHelper"; import { OverridesHelper } from "#test/testUtils/helpers/overridesHelper"; import { ReloadHelper } from "#test/testUtils/helpers/reloadHelper"; import { SettingsHelper } from "#test/testUtils/helpers/settingsHelper"; +import type InputsHandler from "#test/testUtils/inputsHandler"; +import { MockFetch } from "#test/testUtils/mocks/mockFetch"; import PhaseInterceptor from "#test/testUtils/phaseInterceptor"; import TextInterceptor from "#test/testUtils/TextInterceptor"; import { AES, enc } from "crypto-js"; import fs from "node:fs"; import { expect, vi } from "vitest"; -import { globalScene } from "#app/global-scene"; -import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; -import { MockFetch } from "#test/testUtils/mocks/mockFetch"; /** * Class to manage the game state and transitions between phases. @@ -76,6 +77,7 @@ export default class GameManager { public readonly settings: SettingsHelper; public readonly reload: ReloadHelper; public readonly modifiers: ModifierHelper; + public readonly field: FieldHelper; /** * Creates an instance of GameManager. @@ -123,6 +125,7 @@ export default class GameManager { this.settings = new SettingsHelper(this); this.reload = new ReloadHelper(this); this.modifiers = new ModifierHelper(this); + this.field = new FieldHelper(this); this.override.sanitizeOverrides(); // Disables Mystery Encounters on all tests (can be overridden at test level) @@ -383,6 +386,7 @@ export default class GameManager { * @param moveId - The {@linkcode Moves | move} the enemy will use * @param target - The {@linkcode BattlerIndex} of the target against which the enemy will use the given move; * will use normal target selection priorities if omitted. + * @deprecated Use {@linkcode MoveHelper.forceEnemyMove} or {@linkcode MoveHelper.selectEnemyMove} */ async forceEnemyMove(moveId: Moves, target?: BattlerIndex) { // Wait for the next EnemyCommandPhase to start @@ -417,9 +421,15 @@ export default class GameManager { }; } - /** Transition to the next upcoming {@linkcode CommandPhase} */ + /** Transition to the first {@linkcode CommandPhase} of the next turn. */ async toNextTurn() { - await this.phaseInterceptor.to(CommandPhase); + await this.phaseInterceptor.to("TurnInitPhase"); + await this.phaseInterceptor.to("CommandPhase"); + } + + /** Transition to the {@linkcode TurnEndPhase | end of the current turn}. */ + async toEndOfTurn() { + await this.phaseInterceptor.to("TurnEndPhase"); } /** @@ -541,8 +551,8 @@ export default class GameManager { } /** - * Select a pokemon from the party menu during the given phase. - * Only really handles the basic case of "navigate to party slot and press Action twice" - + * Select a pokemon from the party menu during the given phase. + * Only really handles the basic case of "navigate to party slot and press Action twice" - * any menus that come up afterwards are ignored and must be handled separately by the caller. * @param slot - The 0-indexed position of the pokemon in your party to switch to * @param inPhase - Which phase to expect the selection to occur in. Defaults to `SwitchPhase` diff --git a/test/testUtils/helpers/field-helper.ts b/test/testUtils/helpers/field-helper.ts new file mode 100644 index 00000000000..6d762853cad --- /dev/null +++ b/test/testUtils/helpers/field-helper.ts @@ -0,0 +1,87 @@ +// -- start tsdoc imports -- +// biome-ignore lint/correctness/noUnusedImports: TSDoc import +import type { globalScene } from "#app/global-scene"; +// -- end tsdoc imports -- + +import type { BattlerIndex } from "#app/battle"; +import type { Ability } from "#app/data/abilities/ability-class"; +import { allAbilities } from "#app/data/data-lists"; +import type Pokemon from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { Abilities } from "#enums/abilities"; +import type { PokemonType } from "#enums/pokemon-type"; +import { Stat } from "#enums/stat"; +import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; +import { expect, type MockInstance, vi } from "vitest"; + +/** Helper to manage pokemon */ +export class FieldHelper extends GameManagerHelper { + /** + * Passthrough for {@linkcode globalScene.getPlayerPokemon} that adds an `undefined` check for + * the Pokemon so that the return type for the function doesn't have `undefined`. + * This removes the need to add a `!` like when calling `game.scene.getPlayerPokemon()!`. + * @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true` + * @returns The first {@linkcode PlayerPokemon} that is {@linkcode globalScene.getPlayerField on the field} + * and {@linkcode PlayerPokemon.isActive is active} + * (aka {@linkcode PlayerPokemon.isAllowedInBattle is allowed in battle}). + */ + public getPlayerPokemon(includeSwitching = true): PlayerPokemon { + const pokemon = this.game.scene.getPlayerPokemon(includeSwitching); + expect(pokemon).toBeDefined(); + return pokemon!; + } + + /** + * Passthrough for {@linkcode globalScene.getEnemyPokemon} that adds an `undefined` check for + * the Pokemon so that the return type for the function doesn't have `undefined`. + * This removes the need to add a `!` like when calling `game.scene.getEnemyPokemon()!`. + * @param includeSwitching Whether a pokemon that is currently switching out is valid, default `true` + * @returns The first {@linkcode EnemyPokemon} that is {@linkcode globalScene.getEnemyField on the field} + * and {@linkcode EnemyPokemon.isActive is active} + * (aka {@linkcode EnemyPokemon.isAllowedInBattle is allowed in battle}). + */ + public getEnemyPokemon(includeSwitching = true): EnemyPokemon { + const pokemon = this.game.scene.getEnemyPokemon(includeSwitching); + expect(pokemon).toBeDefined(); + return pokemon!; + } + + /** + * @returns The {@linkcode BattlerIndex | indexes} of Pokemon on the field in order of decreasing Speed. + * Speed ties are returned in increasing order of index. + * + * Note: Trick Room does not modify the speed of Pokemon on the field. + */ + public getSpeedOrder(): BattlerIndex[] { + return this.game.scene + .getField(true) + .sort((pA, pB) => pB.getEffectiveStat(Stat.SPD) - pA.getEffectiveStat(Stat.SPD)) + .map(p => p.getBattlerIndex()); + } + + /** + * Mocks a pokemon's ability, overriding its existing ability (takes precedence over global overrides) + * @param pokemon - The pokemon to mock the ability of + * @param ability - The ability to be mocked + * @returns A {@linkcode MockInstance} object + * @see {@linkcode vi.spyOn} + * @see https://vitest.dev/api/mock#mockreturnvalue + */ + public mockAbility(pokemon: Pokemon, ability: Abilities): MockInstance<(baseOnly?: boolean) => Ability> { + return vi.spyOn(pokemon, "getAbility").mockReturnValue(allAbilities[ability]); + } + + /** + * Forces a pokemon to be terastallized. Defaults to the pokemon's primary type if not specified. + * + * This function only mocks the Pokemon's tera-related variables; it does NOT activate any tera-related abilities. + * + * @param pokemon - The pokemon to terastallize. + * @param teraType - (optional) The {@linkcode PokemonType} to terastallize it as. + */ + public forceTera(pokemon: Pokemon, teraType?: PokemonType): void { + vi.spyOn(pokemon, "isTerastallized", "get").mockReturnValue(true); + teraType ??= pokemon.getSpeciesForm(true).type1; + vi.spyOn(pokemon, "teraType", "get").mockReturnValue(teraType); + } +} diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index 269cf65ea56..ab10486867d 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -1,14 +1,16 @@ import type { BattlerIndex } from "#app/battle"; +import { getMoveTargets } from "#app/data/moves/move"; import { Button } from "#app/enums/buttons"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; import Overrides from "#app/overrides"; import type { CommandPhase } from "#app/phases/command-phase"; +import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Command } from "#app/ui/command-ui-handler"; -import { UiMode } from "#enums/ui-mode"; import { Moves } from "#enums/moves"; +import { UiMode } from "#enums/ui-mode"; import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { vi } from "vitest"; @@ -92,6 +94,35 @@ export class MoveHelper extends GameManagerHelper { } } + /** + * Modifies a player pokemon's moveset to contain only the selected move and then + * selects it to be used during the next {@linkcode CommandPhase}. + * + * Warning: Will disable the player moveset override if it is enabled! + * + * Note: If you need to check for changes in the player's moveset as part of the test, it may be + * best to use {@linkcode changeMoveset} and {@linkcode select} instead. + * @param moveId - the move to use + * @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0) + * @param targetIndex - (optional) The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required + * @param useTera - If `true`, the Pokemon also chooses to Terastallize. This does not require a Tera Orb. Default: `false`. + */ + public use(moveId: Moves, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null, useTera = false): void { + if ([Overrides.MOVESET_OVERRIDE].flat().length > 0) { + vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]); + console.warn("Warning: `use` overwrites the Pokemon's moveset and disables the player moveset override!"); + } + + const pokemon = this.game.scene.getPlayerField()[pkmIndex]; + pokemon.moveset = [new PokemonMove(moveId)]; + + if (useTera) { + this.selectWithTera(moveId, pkmIndex, targetIndex); + return; + } + this.select(moveId, pkmIndex, targetIndex); + } + /** * Forces the Paralysis or Freeze status to activate on the next move by temporarily mocking {@linkcode Overrides.STATUS_ACTIVATION_OVERRIDE}, * advancing to the next `MovePhase`, and then resetting the override to `null` @@ -132,6 +163,77 @@ export class MoveHelper extends GameManagerHelper { console.log(`Pokemon ${pokemon.species.name}'s moveset manually set to ${movesetStr} (=[${moveset.join(", ")}])!`); } + /** + * Forces the next enemy selecting a move to use the given move in its moveset + * against the given target (if applicable). + * @param moveId The {@linkcode MoveId | move} the enemy will use + * @param target (Optional) the {@linkcode BattlerIndex | target} which the enemy will use the given move against + */ + public async selectEnemyMove(moveId: Moves, target?: BattlerIndex) { + // Wait for the next EnemyCommandPhase to start + await this.game.phaseInterceptor.to("EnemyCommandPhase", false); + const enemy = + this.game.scene.getEnemyField()[(this.game.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()]; + const legalTargets = getMoveTargets(enemy, moveId); + + vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({ + move: moveId, + targets: + target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target) + ? [target] + : enemy.getNextTargets(moveId), + }); + + /** + * Run the EnemyCommandPhase to completion. + * This allows this function to be called consecutively to + * force a move for each enemy in a double battle. + */ + await this.game.phaseInterceptor.to("EnemyCommandPhase"); + } + + /** + * Forces the next enemy selecting a move to use the given move against the given target (if applicable). + * + * Warning: Overwrites the pokemon's moveset and disables the moveset override! + * + * Note: If you need to check for changes in the enemy's moveset as part of the test, it may be + * best to use {@linkcode changeMoveset} and {@linkcode selectEnemyMove} instead. + * @param moveId The {@linkcode MoveId | move} the enemy will use + * @param target (Optional) the {@linkcode BattlerIndex | target} which the enemy will use the given move against + */ + public async forceEnemyMove(moveId: Moves, target?: BattlerIndex) { + // Wait for the next EnemyCommandPhase to start + await this.game.phaseInterceptor.to("EnemyCommandPhase", false); + + const enemy = + this.game.scene.getEnemyField()[(this.game.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()]; + + if ([Overrides.OPP_MOVESET_OVERRIDE].flat().length > 0) { + vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([]); + console.warn( + "Warning: `forceEnemyMove` overwrites the Pokemon's moveset and disables the enemy moveset override!", + ); + } + enemy.moveset = [new PokemonMove(moveId)]; + const legalTargets = getMoveTargets(enemy, moveId); + + vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({ + move: moveId, + targets: + target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target) + ? [target] + : enemy.getNextTargets(moveId), + }); + + /** + * Run the EnemyCommandPhase to completion. + * This allows this function to be called consecutively to + * force a move for each enemy in a double battle. + */ + await this.game.phaseInterceptor.to("EnemyCommandPhase"); + } + /** * Simulates learning a move for a player pokemon. * @param move The {@linkcode Moves} being learnt From 5eeff184071cd28bc522355e6c330c9ffe116a22 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 30 May 2025 14:35:38 -0500 Subject: [PATCH 15/20] [GitHub] Fix gh action path filter (#5904) * Fix test workflow syntax to use negation properly * Add missing id and comparison check in tests.yml * Fix missing curly brace --- .github/test-filters.yml | 14 +++++--------- .github/workflows/tests.yml | 3 ++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/test-filters.yml b/.github/test-filters.yml index 89f4322adea..fc52e85082c 100644 --- a/.github/test-filters.yml +++ b/.github/test-filters.yml @@ -1,7 +1,8 @@ all: - - "src/**" - - "test/**" - - "public/**" + # Negations syntax from https://github.com/dorny/paths-filter/issues/184#issuecomment-2786521554 + - "src/**/!(*.{md,py,sh,gitkeep,gitignore})" + - "test/**/!(*.{md,py,sh,gitkeep,gitignore})" + - "public/**/!(*.{md,py,sh,gitkeep,gitignore})" # Workflows that can impact tests - ".github/workflows/test*.yml" - ".github/test-filters.yml" @@ -11,9 +12,4 @@ all: - "vite*" # vite.config.ts, vite.vitest.config.ts, vitest.workspace.ts - "tsconfig*.json" # tsconfig.json tweaking can impact compilation - "global.d.ts" - - ".env*" - # Blanket negations for files that cannot impact tests - - "!**/*.py" # No .py files - - "!**/*.sh" # No .sh files - - "!**/*.md" # No .md files - - "!**/.git*" # .gitkeep and family + - ".env*" \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f04a1987eff..f9d26b5568a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,6 +25,7 @@ jobs: - name: checkout uses: actions/checkout@v4 - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 + id: filter with: filters: .github/test-filters.yml @@ -39,4 +40,4 @@ jobs: project: main shard: ${{ matrix.shard }} totalShards: 10 - skip: ${{ needs.check-path-change-filter.outputs.all == 'false'}} + skip: ${{ needs.check-path-change-filter.outputs.all != 'true'}} From e1be360e74467fa3ec7804580228891fb8c16b25 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 30 May 2025 16:42:12 -0500 Subject: [PATCH 16/20] [GitHub] Change total shard numbers and change job name (#5905) --- .github/workflows/test-shard-template.yml | 9 +++++---- .github/workflows/tests.yml | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-shard-template.yml b/.github/workflows/test-shard-template.yml index a1146cb3497..98836bd335a 100644 --- a/.github/workflows/test-shard-template.yml +++ b/.github/workflows/test-shard-template.yml @@ -19,19 +19,20 @@ on: jobs: test: - name: Shard ${{ inputs.shard }} of ${{ inputs.totalShards }} + # We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented + name: Shard runs-on: ubuntu-latest if: ${{ !inputs.skip }} steps: - name: Check out Git repository uses: actions/checkout@v4.2.2 with: - submodules: 'recursive' + submodules: "recursive" - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version-file: '.nvmrc' - cache: 'npm' + node-version-file: ".nvmrc" + cache: "npm" - name: Install Node.js dependencies run: npm ci - name: Run tests diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f9d26b5568a..c3b9666caa9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,10 +34,10 @@ jobs: needs: check-path-change-filter strategy: matrix: - shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + shard: [1, 2, 3, 4, 5] uses: ./.github/workflows/test-shard-template.yml with: project: main shard: ${{ matrix.shard }} - totalShards: 10 + totalShards: 5 skip: ${{ needs.check-path-change-filter.outputs.all != 'true'}} From ed0251ea9055cd0c321cbe2d1865cc22f6993100 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 30 May 2025 18:08:52 -0500 Subject: [PATCH 17/20] [Refactor] Cleanup egg-counter-container (#5892) --- src/ui/egg-counter-container.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ui/egg-counter-container.ts b/src/ui/egg-counter-container.ts index 7bec7c97480..7bb32189681 100644 --- a/src/ui/egg-counter-container.ts +++ b/src/ui/egg-counter-container.ts @@ -43,14 +43,13 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container { this.add(this.eggCountWindow); - const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0"); - eggSprite.setScale(0.32); + const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0").setScale(0.32); - this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }); - this.eggCountText.setName("text-egg-count"); + this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }).setName( + "text-egg-count", + ); - this.add(eggSprite); - this.add(this.eggCountText); + this.add([eggSprite, this.eggCountText]); } /** From 9a60cc9c716b043764270f6b160d5302663f521e Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Fri, 30 May 2025 18:12:35 -0500 Subject: [PATCH 18/20] [Refactor] Cleanup filter bar (#5896) * Cleanup filter bar * Move DropDownColumn to its own file --------- Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> --- src/enums/drop-down-column.ts | 9 +++++++++ src/ui/filter-bar.ts | 19 +++---------------- src/ui/pokedex-ui-handler.ts | 3 ++- src/ui/starter-select-ui-handler.ts | 3 ++- test/ui/pokedex.test.ts | 2 +- 5 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 src/enums/drop-down-column.ts diff --git a/src/enums/drop-down-column.ts b/src/enums/drop-down-column.ts new file mode 100644 index 00000000000..b413d1f0bf4 --- /dev/null +++ b/src/enums/drop-down-column.ts @@ -0,0 +1,9 @@ +export enum DropDownColumn { + GEN, + TYPES, + BIOME, + CAUGHT, + UNLOCKS, + MISC, + SORT +} diff --git a/src/ui/filter-bar.ts b/src/ui/filter-bar.ts index 2aade130ed1..622488c04cd 100644 --- a/src/ui/filter-bar.ts +++ b/src/ui/filter-bar.ts @@ -5,16 +5,7 @@ import { addTextObject, getTextColor, TextStyle } from "./text"; import type { UiTheme } from "#enums/ui-theme"; import { addWindow, WindowVariant } from "./ui-theme"; import { globalScene } from "#app/global-scene"; - -export enum DropDownColumn { - GEN, - TYPES, - BIOME, - CAUGHT, - UNLOCKS, - MISC, - SORT, -} +import type { DropDownColumn } from "#enums/drop-down-column"; export class FilterBar extends Phaser.GameObjects.Container { private window: Phaser.GameObjects.NineSlice; @@ -49,13 +40,9 @@ export class FilterBar extends Phaser.GameObjects.Container { this.cursorOffset = cursorOffset; this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN); - this.add(this.window); - this.cursorObj = globalScene.add.image(1, 1, "cursor"); - this.cursorObj.setScale(0.5); - this.cursorObj.setVisible(false); - this.cursorObj.setOrigin(0, 0); - this.add(this.cursorObj); + this.cursorObj = globalScene.add.image(1, 1, "cursor").setScale(0.5).setVisible(false).setOrigin(0); + this.add([this.window, this.cursorObj]); } /** diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index 08fe5d7442f..179d5b4664b 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -23,7 +23,8 @@ import type { Species } from "#enums/species"; import { Button } from "#enums/buttons"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown"; import { PokedexMonContainer } from "#app/ui/pokedex-mon-container"; -import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; +import { FilterBar } from "#app/ui/filter-bar"; +import { DropDownColumn } from "#enums/drop-down-column"; import { ScrollBar } from "#app/ui/scroll-bar"; import { Abilities } from "#enums/abilities"; import { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index a971ba86479..ff2298f268d 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -53,7 +53,8 @@ import { Button } from "#enums/buttons"; import { EggSourceType } from "#enums/egg-source-types"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown"; import { StarterContainer } from "#app/ui/starter-container"; -import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; +import { FilterBar } from "#app/ui/filter-bar"; +import { DropDownColumn } from "#enums/drop-down-column"; import { ScrollBar } from "#app/ui/scroll-bar"; import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index ff5ca116ba8..007fc43c3b9 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -8,7 +8,7 @@ import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import { allSpecies, getPokemonSpecies, type PokemonForm } from "#app/data/pokemon-species"; import { Button } from "#enums/buttons"; -import { DropDownColumn } from "#app/ui/filter-bar"; +import { DropDownColumn } from "#enums/drop-down-column"; import type PokemonSpecies from "#app/data/pokemon-species"; import { PokemonType } from "#enums/pokemon-type"; import { UiMode } from "#enums/ui-mode"; From a33638a7a36b01935e40ea876532c770b53f0994 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 30 May 2025 19:50:25 -0400 Subject: [PATCH 19/20] [Test] Remove deprecated test funcs (#5906) * Removed `game.startBattle` * Removed `game.forceEnemyMove` * Removed near-unused learn move macro --- test/abilities/aroma_veil.test.ts | 6 +-- test/abilities/battery.test.ts | 6 +-- test/abilities/commander.test.ts | 12 ++--- test/abilities/costar.test.ts | 4 +- test/abilities/cud_chew.test.ts | 4 +- test/abilities/dancer.test.ts | 4 +- test/abilities/desolate-land.test.ts | 12 ++--- test/abilities/flower_gift.test.ts | 8 +-- test/abilities/flower_veil.test.ts | 14 ++--- test/abilities/forecast.test.ts | 14 ++--- test/abilities/friend_guard.test.ts | 24 ++++----- test/abilities/good_as_gold.test.ts | 4 +- test/abilities/gorilla_tactics.test.ts | 6 +-- test/abilities/gulp_missile.test.ts | 4 +- test/abilities/harvest.test.ts | 24 ++++----- test/abilities/heatproof.test.ts | 4 +- test/abilities/hyper_cutter.test.ts | 2 +- test/abilities/libero.test.ts | 30 +++++------ test/abilities/magic_bounce.test.ts | 12 ++--- test/abilities/magic_guard.test.ts | 38 +++++++------- test/abilities/mimicry.test.ts | 8 +-- test/abilities/mirror_armor.test.ts | 42 +++++++-------- test/abilities/moxie.test.ts | 4 +- test/abilities/mycelium_might.test.ts | 6 +-- test/abilities/neutralizing_gas.test.ts | 8 +-- test/abilities/pastel_veil.test.ts | 4 +- test/abilities/perish_body.test.ts | 10 ++-- test/abilities/power_spot.test.ts | 6 +-- test/abilities/protean.test.ts | 30 +++++------ test/abilities/quick_draw.test.ts | 6 +-- test/abilities/sand_veil.test.ts | 2 +- test/abilities/schooling.test.ts | 2 +- test/abilities/screen_cleaner.test.ts | 6 +-- test/abilities/shields_down.test.ts | 12 ++--- test/abilities/simple.test.ts | 2 +- test/abilities/stakeout.test.ts | 8 +-- test/abilities/stall.test.ts | 6 +-- test/abilities/sturdy.test.ts | 8 +-- test/abilities/supreme_overlord.test.ts | 2 +- test/abilities/sweet_veil.test.ts | 8 +-- test/abilities/unburden.test.ts | 16 +++--- test/abilities/wimp_out.test.ts | 4 +- test/abilities/wind_power.test.ts | 8 +-- test/abilities/wonder_skin.test.ts | 4 +- test/abilities/zero_to_hero.test.ts | 8 +-- test/arena/weather_fog.test.ts | 2 +- test/battle/battle-order.test.ts | 10 ++-- test/battle/battle.test.ts | 22 ++++---- test/battle/double_battle.test.ts | 2 +- test/battle/special_battle.test.ts | 18 +++---- test/daily_mode.test.ts | 2 +- test/evolution.test.ts | 6 +-- test/items/dire_hit.test.ts | 4 +- test/items/exp_booster.test.ts | 2 +- test/items/leek.test.ts | 12 ++--- test/items/leftovers.test.ts | 2 +- test/items/light_ball.test.ts | 4 +- test/items/metal_powder.test.ts | 8 +-- test/items/scope_lens.test.ts | 2 +- test/moves/astonish.test.ts | 2 +- test/moves/beat_up.test.ts | 4 +- test/moves/belly_drum.test.ts | 8 +-- test/moves/chilly_reception.test.ts | 2 +- test/moves/clangorous_soul.test.ts | 8 +-- test/moves/crafty_shield.test.ts | 8 +-- test/moves/defog.test.ts | 6 +-- test/moves/disable.test.ts | 4 +- test/moves/double_team.test.ts | 2 +- test/moves/dragon_tail.test.ts | 10 ++-- test/moves/dynamax_cannon.test.ts | 16 +++--- test/moves/encore.test.ts | 2 +- test/moves/fairy_lock.test.ts | 32 +++++------ test/moves/fillet_away.test.ts | 8 +-- test/moves/flame_burst.test.ts | 8 +-- test/moves/flower_shield.test.ts | 8 +-- test/moves/fly.test.ts | 4 +- test/moves/follow_me.test.ts | 16 +++--- test/moves/foresight.test.ts | 4 +- test/moves/fusion_bolt.test.ts | 2 +- test/moves/fusion_flare.test.ts | 2 +- test/moves/fusion_flare_bolt.test.ts | 2 +- test/moves/growth.test.ts | 2 +- test/moves/grudge.test.ts | 8 +-- test/moves/guard_split.test.ts | 4 +- test/moves/hard_press.test.ts | 8 +-- test/moves/haze.test.ts | 2 +- test/moves/hyper_beam.test.ts | 2 +- test/moves/imprison.test.ts | 12 ++--- test/moves/instruct.test.ts | 60 ++++++++++----------- test/moves/jaw_lock.test.ts | 10 ++-- test/moves/last-resort.test.ts | 8 +-- test/moves/lucky_chant.test.ts | 6 +-- test/moves/lunar_blessing.test.ts | 4 +- test/moves/magic_coat.test.ts | 18 +++---- test/moves/magnet_rise.test.ts | 4 +- test/moves/make_it_rain.test.ts | 8 +-- test/moves/mat_block.test.ts | 6 +-- test/moves/metal_burst.test.ts | 8 +-- test/moves/mirror_move.test.ts | 4 +- test/moves/multi_target.test.ts | 4 +- test/moves/parting_shot.test.ts | 20 ++++--- test/moves/plasma_fists.test.ts | 4 +- test/moves/pledge_moves.test.ts | 8 +-- test/moves/powder.test.ts | 12 ++--- test/moves/power_split.test.ts | 4 +- test/moves/purify.test.ts | 4 +- test/moves/quash.test.ts | 16 +++--- test/moves/rage_fist.test.ts | 8 +-- test/moves/rage_powder.test.ts | 8 +-- test/moves/reflect_type.test.ts | 6 +-- test/moves/retaliate.test.ts | 2 +- test/moves/revival_blessing.test.ts | 4 +- test/moves/rollout.test.ts | 2 +- test/moves/round.test.ts | 4 +- test/moves/secret_power.test.ts | 4 +- test/moves/shell_trap.test.ts | 10 ++-- test/moves/speed_swap.test.ts | 2 +- test/moves/spikes.test.ts | 6 +-- test/moves/spit_up.test.ts | 12 ++--- test/moves/spotlight.test.ts | 8 +-- test/moves/stockpile.test.ts | 4 +- test/moves/swallow.test.ts | 12 ++--- test/moves/tackle.test.ts | 4 +- test/moves/tail_whip.test.ts | 2 +- test/moves/taunt.test.ts | 4 +- test/moves/telekinesis.test.ts | 4 +- test/moves/thousand_arrows.test.ts | 6 +-- test/moves/thunder_wave.test.ts | 10 ++-- test/moves/torment.test.ts | 6 +-- test/moves/whirlwind.test.ts | 26 ++++----- test/moves/wide_guard.test.ts | 8 +-- test/phases/learn-move-phase.test.ts | 68 +----------------------- test/testUtils/gameManager.ts | 70 ------------------------- test/testUtils/helpers/moveHelper.ts | 37 ------------- test/ui/type-hints.test.ts | 4 +- 135 files changed, 563 insertions(+), 730 deletions(-) diff --git a/test/abilities/aroma_veil.test.ts b/test/abilities/aroma_veil.test.ts index 38683bcb1e3..aeec33eccf7 100644 --- a/test/abilities/aroma_veil.test.ts +++ b/test/abilities/aroma_veil.test.ts @@ -40,7 +40,7 @@ describe("Moves - Aroma Veil", () => { game.move.select(Moves.GROWL); game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.HEAL_BLOCK); + await game.move.selectEnemyMove(Moves.HEAL_BLOCK); await game.toNextTurn(); party.forEach(p => { expect(p.getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined(); @@ -54,8 +54,8 @@ describe("Moves - Aroma Veil", () => { game.move.select(Moves.GROWL); game.move.select(Moves.GROWL, 1); - await game.forceEnemyMove(Moves.IMPRISON, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.IMPRISON, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); party.forEach(p => { diff --git a/test/abilities/battery.test.ts b/test/abilities/battery.test.ts index 251ca6ccf16..123889c24af 100644 --- a/test/abilities/battery.test.ts +++ b/test/abilities/battery.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Battery", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.PIKACHU, Species.CHARJABUG]); + await game.classicMode.startBattle([Species.PIKACHU, Species.CHARJABUG]); game.move.select(Moves.DAZZLING_GLEAM); game.move.select(Moves.SPLASH, 1); @@ -54,7 +54,7 @@ describe("Abilities - Battery", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.PIKACHU, Species.CHARJABUG]); + await game.classicMode.startBattle([Species.PIKACHU, Species.CHARJABUG]); game.move.select(Moves.BREAKING_SWIPE); game.move.select(Moves.SPLASH, 1); @@ -69,7 +69,7 @@ describe("Abilities - Battery", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.CHARJABUG, Species.PIKACHU]); + await game.classicMode.startBattle([Species.CHARJABUG, Species.PIKACHU]); game.move.select(Moves.DAZZLING_GLEAM); game.move.select(Moves.SPLASH, 1); diff --git a/test/abilities/commander.test.ts b/test/abilities/commander.test.ts index 0e6cb1b9208..0fddb8e9bf6 100644 --- a/test/abilities/commander.test.ts +++ b/test/abilities/commander.test.ts @@ -59,8 +59,8 @@ describe("Abilities - Commander", () => { expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); // Force both enemies to target the Tatsugiri - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); await game.phaseInterceptor.to("BerryPhase", false); game.scene.getEnemyField().forEach(enemy => expect(enemy.getLastXMoves(1)[0].result).toBe(MoveResult.MISS)); @@ -100,8 +100,8 @@ describe("Abilities - Commander", () => { expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER]); @@ -183,8 +183,8 @@ describe("Abilities - Commander", () => { expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); - await game.forceEnemyMove(Moves.WHIRLWIND, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.WHIRLWIND, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); // Test may time out here if Whirlwind forced out a Pokemon await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/abilities/costar.test.ts b/test/abilities/costar.test.ts index 7b1e362689d..02d607c2e9f 100644 --- a/test/abilities/costar.test.ts +++ b/test/abilities/costar.test.ts @@ -33,7 +33,7 @@ describe("Abilities - COSTAR", () => { test("ability copies positive stat stages", async () => { game.override.enemyAbility(Abilities.BALL_FETCH); - await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); @@ -58,7 +58,7 @@ describe("Abilities - COSTAR", () => { test("ability copies negative stat stages", async () => { game.override.enemyAbility(Abilities.INTIMIDATE); - await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); diff --git a/test/abilities/cud_chew.test.ts b/test/abilities/cud_chew.test.ts index 2f65ac5fd97..60205b62b70 100644 --- a/test/abilities/cud_chew.test.ts +++ b/test/abilities/cud_chew.test.ts @@ -76,7 +76,7 @@ describe("Abilities - Cud Chew", () => { farigiraf.hp = farigiraf.getMaxHp() / 2 - 1; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); // doesn't trigger since cud chew hasn't eaten berry yet @@ -86,7 +86,7 @@ describe("Abilities - Cud Chew", () => { // get heal pulsed back to full before the cud chew proc game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.HEAL_PULSE); + await game.move.selectEnemyMove(Moves.HEAL_PULSE); await game.phaseInterceptor.to("TurnEndPhase"); // globalScene.queueAbilityDisplay should be called twice: diff --git a/test/abilities/dancer.test.ts b/test/abilities/dancer.test.ts index cdd1e3221e9..b85fc7bdcd4 100644 --- a/test/abilities/dancer.test.ts +++ b/test/abilities/dancer.test.ts @@ -77,8 +77,8 @@ describe("Abilities - Dancer", () => { game.move.select(Moves.REVELATION_DANCE, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); game.move.select(Moves.FIERY_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.INSTRUCT, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.MIRROR_MOVE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.INSTRUCT, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.MIRROR_MOVE, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("MovePhase"); // Oricorio rev dance await game.phaseInterceptor.to("MovePhase"); // Feebas fiery dance diff --git a/test/abilities/desolate-land.test.ts b/test/abilities/desolate-land.test.ts index 697bd0a4c48..da2c285e38f 100644 --- a/test/abilities/desolate-land.test.ts +++ b/test/abilities/desolate-land.test.ts @@ -50,8 +50,8 @@ describe("Abilities - Desolate Land", () => { game.move.select(Moves.SPLASH, 0, 2); game.move.select(Moves.SPLASH, 1, 2); - await game.forceEnemyMove(Moves.ROAR, 0); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.ROAR, 0); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("TurnEndPhase"); @@ -66,8 +66,8 @@ describe("Abilities - Desolate Land", () => { game.move.select(Moves.SPLASH, 0, 2); game.move.select(Moves.SPLASH, 1, 2); - await game.forceEnemyMove(Moves.ROAR, 1); - await game.forceEnemyMove(Moves.SPLASH, 0); + await game.move.selectEnemyMove(Moves.ROAR, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 0); await game.phaseInterceptor.to("TurnEndPhase"); @@ -103,8 +103,8 @@ describe("Abilities - Desolate Land", () => { game.move.select(Moves.SPLASH, 0, 2); game.move.select(Moves.SPLASH, 1, 2); - await game.forceEnemyMove(Moves.MEMENTO, 0); - await game.forceEnemyMove(Moves.MEMENTO, 1); + await game.move.selectEnemyMove(Moves.MEMENTO, 0); + await game.move.selectEnemyMove(Moves.MEMENTO, 1); await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/abilities/flower_gift.test.ts b/test/abilities/flower_gift.test.ts index f2b32dc4c80..df8830bca6d 100644 --- a/test/abilities/flower_gift.test.ts +++ b/test/abilities/flower_gift.test.ts @@ -67,8 +67,8 @@ describe("Abilities - Flower Gift", () => { // turn 1 game.move.select(Moves.SUNNY_DAY, 0); game.move.select(ally_move, 1, ally_target); - await game.forceEnemyMove(enemy_move, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(enemy_move, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); // Ensure sunny day is used last. await game.setTurnOrder([attacker_index, target_index, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER]); await game.phaseInterceptor.to(TurnEndPhase); @@ -79,8 +79,8 @@ describe("Abilities - Flower Gift", () => { // turn 2. Make target use recover to reset hp calculation. game.move.select(Moves.SPLASH, 0, target_index); game.move.select(ally_move, 1, ally_target); - await game.forceEnemyMove(enemy_move, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(enemy_move, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, target_index, attacker_index]); await game.phaseInterceptor.to(TurnEndPhase); const damageWithGift = initialHp - target.hp; diff --git a/test/abilities/flower_veil.test.ts b/test/abilities/flower_veil.test.ts index ce45906c4a9..7f51414d8a5 100644 --- a/test/abilities/flower_veil.test.ts +++ b/test/abilities/flower_veil.test.ts @@ -49,7 +49,7 @@ describe("Abilities - Flower Veil", () => { await game.classicMode.startBattle([Species.BULBASAUR]); const user = game.scene.getPlayerPokemon()!; game.move.select(Moves.REST); - await game.forceEnemyMove(Moves.TACKLE); + await game.move.selectEnemyMove(Moves.TACKLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); expect(user.status?.effect).toBe(StatusEffect.SLEEP); @@ -57,7 +57,7 @@ describe("Abilities - Flower Veil", () => { // remove sleep status so we can get burn from the orb user.resetStatus(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(user.status?.effect).toBe(StatusEffect.BURN); }); @@ -71,8 +71,8 @@ describe("Abilities - Flower Veil", () => { vi.spyOn(ally, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.YAWN, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.YAWN, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.YAWN, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.YAWN, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to("BerryPhase"); const user = game.scene.getPlayerPokemon()!; @@ -86,7 +86,7 @@ describe("Abilities - Flower Veil", () => { await game.classicMode.startBattle([Species.BULBASAUR]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.THUNDER_WAVE); + await game.move.selectEnemyMove(Moves.THUNDER_WAVE); await game.toNextTurn(); expect(game.scene.getPlayerPokemon()!.status).toBeUndefined(); vi.spyOn(allMoves[Moves.THUNDER_WAVE], "accuracy", "get").mockClear(); @@ -101,8 +101,8 @@ describe("Abilities - Flower Veil", () => { vi.spyOn(ally, "getAbility").mockReturnValue(allAbilities[Abilities.BALL_FETCH]); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.THUNDER_WAVE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.THUNDER_WAVE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.THUNDER_WAVE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.THUNDER_WAVE, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to("BerryPhase"); expect(user.status?.effect).toBe(StatusEffect.PARALYSIS); expect(ally.status?.effect).toBe(StatusEffect.PARALYSIS); diff --git a/test/abilities/forecast.test.ts b/test/abilities/forecast.test.ts index 03b5d993a54..f2aa350ef37 100644 --- a/test/abilities/forecast.test.ts +++ b/test/abilities/forecast.test.ts @@ -30,7 +30,7 @@ describe("Abilities - Forecast", () => { */ const testWeatherFormChange = async (game: GameManager, weather: WeatherType, form: number, initialForm?: number) => { game.override.weather(weather).starterForms({ [Species.CASTFORM]: initialForm }); - await game.startBattle([Species.CASTFORM]); + await game.classicMode.startBattle([Species.CASTFORM]); game.move.select(Moves.SPLASH); @@ -44,7 +44,7 @@ describe("Abilities - Forecast", () => { */ const testRevertFormAgainstAbility = async (game: GameManager, ability: Abilities) => { game.override.starterForms({ [Species.CASTFORM]: SUNNY_FORM }).enemyAbility(ability); - await game.startBattle([Species.CASTFORM]); + await game.classicMode.startBattle([Species.CASTFORM]); game.move.select(Moves.SPLASH); @@ -81,7 +81,7 @@ describe("Abilities - Forecast", () => { [Species.GROUDON]: 1, [Species.RAYQUAZA]: 1, }); - await game.startBattle([ + await game.classicMode.startBattle([ Species.CASTFORM, Species.FEEBAS, Species.KYOGRE, @@ -201,7 +201,7 @@ describe("Abilities - Forecast", () => { it("has no effect on Pokémon other than Castform", async () => { game.override.enemyAbility(Abilities.FORECAST).enemySpecies(Species.SHUCKLE); - await game.startBattle([Species.CASTFORM]); + await game.classicMode.startBattle([Species.CASTFORM]); game.move.select(Moves.RAIN_DANCE); await game.phaseInterceptor.to(TurnEndPhase); @@ -212,7 +212,7 @@ describe("Abilities - Forecast", () => { it("reverts to Normal Form when Forecast is suppressed, changes form to match the weather when it regains it", async () => { game.override.enemyMoveset([Moves.GASTRO_ACID]).weather(WeatherType.RAIN); - await game.startBattle([Species.CASTFORM, Species.PIKACHU]); + await game.classicMode.startBattle([Species.CASTFORM, Species.PIKACHU]); const castform = game.scene.getPlayerPokemon()!; expect(castform.formIndex).toBe(RAINY_FORM); @@ -243,7 +243,7 @@ describe("Abilities - Forecast", () => { it("does not change Castform's form until after Stealth Rock deals damage", async () => { game.override.weather(WeatherType.RAIN).enemyMoveset([Moves.STEALTH_ROCK]); - await game.startBattle([Species.PIKACHU, Species.CASTFORM]); + await game.classicMode.startBattle([Species.PIKACHU, Species.CASTFORM]); // First turn - set up stealth rock game.move.select(Moves.SPLASH); @@ -267,7 +267,7 @@ describe("Abilities - Forecast", () => { it("should be in Normal Form after the user is switched out", async () => { game.override.weather(WeatherType.RAIN); - await game.startBattle([Species.CASTFORM, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.CASTFORM, Species.MAGIKARP]); const castform = game.scene.getPlayerPokemon()!; expect(castform.formIndex).toBe(RAINY_FORM); diff --git a/test/abilities/friend_guard.test.ts b/test/abilities/friend_guard.test.ts index 0afe678b175..350ff737c58 100644 --- a/test/abilities/friend_guard.test.ts +++ b/test/abilities/friend_guard.test.ts @@ -43,8 +43,8 @@ describe("Moves - Friend Guard", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Get the last return value from `getAttackDamage` @@ -60,8 +60,8 @@ describe("Moves - Friend Guard", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Get the last return value from `getAttackDamage` @@ -83,8 +83,8 @@ describe("Moves - Friend Guard", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const turn1Damage = spy.mock.results[spy.mock.results.length - 1].value.damage; @@ -93,8 +93,8 @@ describe("Moves - Friend Guard", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const turn2Damage = spy.mock.results[spy.mock.results.length - 1].value.damage; @@ -109,8 +109,8 @@ describe("Moves - Friend Guard", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.DRAGON_RAGE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.DRAGON_RAGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const turn1Damage = spy.mock.results[spy.mock.results.length - 1].value.damage; @@ -120,8 +120,8 @@ describe("Moves - Friend Guard", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.DRAGON_RAGE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.DRAGON_RAGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const turn2Damage = spy.mock.results[spy.mock.results.length - 1].value.damage; diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index 9b1d582f64c..adb54810381 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -74,8 +74,8 @@ describe("Abilities - Good As Gold", () => { game.move.select(Moves.SWORDS_DANCE, 0); game.move.select(Moves.SAFEGUARD, 1); - await game.forceEnemyMove(Moves.STEALTH_ROCK); - await game.forceEnemyMove(Moves.HAZE); + await game.move.selectEnemyMove(Moves.STEALTH_ROCK); + await game.move.selectEnemyMove(Moves.HAZE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); expect(good_as_gold.getAbility().id).toBe(Abilities.GOOD_AS_GOLD); diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index edaf1669809..06f0c1d0e3d 100644 --- a/test/abilities/gorilla_tactics.test.ts +++ b/test/abilities/gorilla_tactics.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Gorilla Tactics", () => { const initialAtkStat = darmanitan.getStat(Stat.ATK); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); @@ -57,13 +57,13 @@ describe("Abilities - Gorilla Tactics", () => { // First turn, lock move to Growl game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); // Second turn, Growl is interrupted by Disable await game.toNextTurn(); game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.DISABLE); + await game.move.selectEnemyMove(Moves.DISABLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/abilities/gulp_missile.test.ts b/test/abilities/gulp_missile.test.ts index 4db2ae4190d..64cd106cc5e 100644 --- a/test/abilities/gulp_missile.test.ts +++ b/test/abilities/gulp_missile.test.ts @@ -241,13 +241,13 @@ describe("Abilities - Gulp Missile", () => { await game.classicMode.startBattle([Species.CRAMORANT]); game.move.select(Moves.SURF); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM); game.move.select(Moves.SUBSTITUTE); - await game.forceEnemyMove(Moves.POWER_TRIP); + await game.move.selectEnemyMove(Moves.POWER_TRIP); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); diff --git a/test/abilities/harvest.test.ts b/test/abilities/harvest.test.ts index 23c0ed9088c..36b1b879598 100644 --- a/test/abilities/harvest.test.ts +++ b/test/abilities/harvest.test.ts @@ -61,7 +61,7 @@ describe("Abilities - Harvest", () => { await game.classicMode.startBattle([Species.FEEBAS]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.NUZZLE); + await game.move.selectEnemyMove(Moves.NUZZLE); await game.phaseInterceptor.to("BerryPhase"); expect(getPlayerBerries()).toHaveLength(0); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toHaveLength(1); @@ -87,7 +87,7 @@ describe("Abilities - Harvest", () => { // Chug a few berries without harvest (should get tracked) game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.NUZZLE); + await game.move.selectEnemyMove(Moves.NUZZLE); await game.toNextTurn(); expect(milotic.battleData.berriesEaten).toEqual(expect.arrayContaining([BerryType.ENIGMA, BerryType.LUM])); @@ -98,7 +98,7 @@ describe("Abilities - Harvest", () => { vi.spyOn(PostTurnRestoreBerryAbAttr.prototype, "canApplyPostTurn").mockReturnValueOnce(false); game.override.ability(Abilities.HARVEST); game.move.select(Moves.GASTRO_ACID); - await game.forceEnemyMove(Moves.NUZZLE); + await game.move.selectEnemyMove(Moves.NUZZLE); await game.toNextTurn(); @@ -109,7 +109,7 @@ describe("Abilities - Harvest", () => { // proc a high roll and we _should_ get a berry back! game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(milotic.battleData.berriesEaten).toHaveLength(3); @@ -126,7 +126,7 @@ describe("Abilities - Harvest", () => { regieleki.hp = 1; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to("TurnEndPhase"); @@ -154,7 +154,7 @@ describe("Abilities - Harvest", () => { regieleki.hp = regieleki.getMaxHp() / 4 + 1; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SUPER_FANG); + await game.move.selectEnemyMove(Moves.SUPER_FANG); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); @@ -165,7 +165,7 @@ describe("Abilities - Harvest", () => { // heal up so harvest doesn't proc and kill enemy game.move.select(Moves.EARTHQUAKE); - await game.forceEnemyMove(Moves.HEAL_PULSE); + await game.move.selectEnemyMove(Moves.HEAL_PULSE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextWave(); @@ -191,7 +191,7 @@ describe("Abilities - Harvest", () => { feebas.battleData.berriesEaten = [BerryType.LUM, BerryType.STARF]; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("BerryPhase"); // Force RNG roll to hit the first berry we find that matches. @@ -219,7 +219,7 @@ describe("Abilities - Harvest", () => { player.battleData.berriesEaten = [BerryType.LUM, BerryType.STARF]; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); expectBerriesContaining(...initBerries); @@ -231,7 +231,7 @@ describe("Abilities - Harvest", () => { await game.classicMode.startBattle([Species.FEEBAS]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.INCINERATE); + await game.move.selectEnemyMove(Moves.INCINERATE); await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); @@ -242,7 +242,7 @@ describe("Abilities - Harvest", () => { await game.classicMode.startBattle([Species.FEEBAS]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.KNOCK_OFF); + await game.move.selectEnemyMove(Moves.KNOCK_OFF); await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.getPlayerPokemon()?.battleData.berriesEaten).toEqual([]); @@ -308,7 +308,7 @@ describe("Abilities - Harvest", () => { // steal a sitrus and immediately consume it game.move.select(Moves.FALSE_SWIPE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("BerryPhase"); expect(player.battleData.berriesEaten).toEqual([BerryType.SITRUS]); diff --git a/test/abilities/heatproof.test.ts b/test/abilities/heatproof.test.ts index 016237bb02f..0bec7e4a3db 100644 --- a/test/abilities/heatproof.test.ts +++ b/test/abilities/heatproof.test.ts @@ -38,7 +38,7 @@ describe("Abilities - Heatproof", () => { }); it("reduces Fire type damage by half", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const enemy = game.scene.getEnemyPokemon()!; const initialHP = 1000; @@ -61,7 +61,7 @@ describe("Abilities - Heatproof", () => { it("reduces Burn damage by half", async () => { game.override.enemyStatusEffect(StatusEffect.BURN).enemySpecies(Species.ABRA); - await game.startBattle(); + await game.classicMode.startBattle(); const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/hyper_cutter.test.ts b/test/abilities/hyper_cutter.test.ts index 99a9db28025..76b66c2990e 100644 --- a/test/abilities/hyper_cutter.test.ts +++ b/test/abilities/hyper_cutter.test.ts @@ -34,7 +34,7 @@ describe("Abilities - Hyper Cutter", () => { // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(Ability) it("only prevents ATK drops", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/libero.test.ts b/test/abilities/libero.test.ts index a6942b18aad..ea4e288bdb0 100644 --- a/test/abilities/libero.test.ts +++ b/test/abilities/libero.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Libero", () => { test("ability applies and changes a pokemon's type", async () => { game.override.moveset([Moves.SPLASH]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -54,7 +54,7 @@ describe("Abilities - Libero", () => { test.skip("ability applies only once per switch in", async () => { game.override.moveset([Moves.SPLASH, Moves.AGILITY]); - await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.BULBASAUR]); let leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -90,7 +90,7 @@ describe("Abilities - Libero", () => { test("ability applies correctly even if the pokemon's move has a variable type", async () => { game.override.moveset([Moves.WEATHER_BALL]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -110,7 +110,7 @@ describe("Abilities - Libero", () => { game.override.moveset([Moves.TACKLE]); game.override.passiveAbility(Abilities.REFRIGERATE); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -128,7 +128,7 @@ describe("Abilities - Libero", () => { test("ability applies correctly even if the pokemon's move calls another move", async () => { game.override.moveset([Moves.NATURE_POWER]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -143,7 +143,7 @@ describe("Abilities - Libero", () => { test("ability applies correctly even if the pokemon's move is delayed / charging", async () => { game.override.moveset([Moves.DIG]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -158,7 +158,7 @@ describe("Abilities - Libero", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -176,7 +176,7 @@ describe("Abilities - Libero", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyMoveset([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -191,7 +191,7 @@ describe("Abilities - Libero", () => { game.override.moveset([Moves.TACKLE]); game.override.enemySpecies(Species.GASTLY); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -205,7 +205,7 @@ describe("Abilities - Libero", () => { test("ability is not applied if pokemon's type is the same as the move's type", async () => { game.override.moveset([Moves.SPLASH]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -220,7 +220,7 @@ describe("Abilities - Libero", () => { test("ability is not applied if pokemon is terastallized", async () => { game.override.moveset([Moves.SPLASH]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -236,7 +236,7 @@ describe("Abilities - Libero", () => { test("ability is not applied if pokemon uses struggle", async () => { game.override.moveset([Moves.STRUGGLE]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -250,7 +250,7 @@ describe("Abilities - Libero", () => { test("ability is not applied if the pokemon's move fails", async () => { game.override.moveset([Moves.BURN_UP]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -265,7 +265,7 @@ describe("Abilities - Libero", () => { game.override.moveset([Moves.TRICK_OR_TREAT]); game.override.enemySpecies(Species.GASTLY); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -279,7 +279,7 @@ describe("Abilities - Libero", () => { test("ability applies correctly and the pokemon curses itself", async () => { game.override.moveset([Moves.CURSE]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); diff --git a/test/abilities/magic_bounce.test.ts b/test/abilities/magic_bounce.test.ts index 78e4114724c..be1ad2b413a 100644 --- a/test/abilities/magic_bounce.test.ts +++ b/test/abilities/magic_bounce.test.ts @@ -52,7 +52,7 @@ describe("Abilities - Magic Bounce", () => { game.override.enemyMoveset([Moves.FLY]); game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.FLY); + await game.move.selectEnemyMove(Moves.FLY); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); @@ -183,7 +183,7 @@ describe("Abilities - Magic Bounce", () => { // turn 1 game.move.select(Moves.ENCORE); - await game.forceEnemyMove(Moves.TACKLE); + await game.move.selectEnemyMove(Moves.TACKLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(Moves.TACKLE); @@ -209,7 +209,7 @@ describe("Abilities - Magic Bounce", () => { // turn 1 game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.TACKLE); + await game.move.selectEnemyMove(Moves.TACKLE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); @@ -218,7 +218,7 @@ describe("Abilities - Magic Bounce", () => { // turn 2 game.move.select(Moves.ENCORE); - await game.forceEnemyMove(Moves.TACKLE); + await game.move.selectEnemyMove(Moves.TACKLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(Moves.TACKLE); @@ -254,7 +254,7 @@ describe("Abilities - Magic Bounce", () => { vi.spyOn(stomping_tantrum, "calculateBattlePower"); game.move.select(Moves.SPORE); - await game.forceEnemyMove(Moves.CHARM); + await game.move.selectEnemyMove(Moves.CHARM); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.getLastXMoves(1)[0].result).toBe("success"); @@ -346,7 +346,7 @@ describe("Abilities - Magic Bounce", () => { game.override.moveset([Moves.TOXIC, Moves.CHARM]); await game.classicMode.startBattle([Species.BULBASAUR]); game.move.select(Moves.TOXIC); - await game.forceEnemyMove(Moves.FLY); + await game.move.selectEnemyMove(Moves.FLY); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.TOXIC); diff --git a/test/abilities/magic_guard.test.ts b/test/abilities/magic_guard.test.ts index 96a9f4dab74..61d06a74c58 100644 --- a/test/abilities/magic_guard.test.ts +++ b/test/abilities/magic_guard.test.ts @@ -46,7 +46,7 @@ describe("Abilities - Magic Guard", () => { it("ability should prevent damage caused by weather", async () => { game.override.weather(WeatherType.SANDSTORM); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -70,7 +70,7 @@ describe("Abilities - Magic Guard", () => { //Toxic keeps track of the turn counters -> important that Magic Guard keeps track of post-Toxic turns game.override.statusEffect(StatusEffect.POISON); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -91,7 +91,7 @@ describe("Abilities - Magic Guard", () => { game.override.enemyMoveset([Moves.WORRY_SEED, Moves.WORRY_SEED, Moves.WORRY_SEED, Moves.WORRY_SEED]); game.override.statusEffect(StatusEffect.POISON); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -110,7 +110,7 @@ describe("Abilities - Magic Guard", () => { game.override.enemyStatusEffect(StatusEffect.BURN); game.override.enemyAbility(Abilities.MAGIC_GUARD); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); game.move.select(Moves.SPLASH); @@ -132,7 +132,7 @@ describe("Abilities - Magic Guard", () => { game.override.enemyStatusEffect(StatusEffect.TOXIC); game.override.enemyAbility(Abilities.MAGIC_GUARD); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); game.move.select(Moves.SPLASH); @@ -159,7 +159,7 @@ describe("Abilities - Magic Guard", () => { const newTag = getArenaTag(ArenaTagType.SPIKES, 5, Moves.SPIKES, 0, 0, ArenaTagSide.BOTH)!; game.scene.arena.tags.push(newTag); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); @@ -184,7 +184,7 @@ describe("Abilities - Magic Guard", () => { game.scene.arena.tags.push(playerTag); game.scene.arena.tags.push(enemyTag); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); @@ -206,7 +206,7 @@ describe("Abilities - Magic Guard", () => { }); it("Magic Guard prevents against damage from volatile status effects", async () => { - await game.startBattle([Species.DUSKULL]); + await game.classicMode.startBattle([Species.DUSKULL]); game.override.moveset([Moves.CURSE]); game.override.enemyAbility(Abilities.MAGIC_GUARD); @@ -231,7 +231,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard prevents crash damage", async () => { game.override.moveset([Moves.HIGH_JUMP_KICK]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -249,7 +249,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard prevents damage from recoil", async () => { game.override.moveset([Moves.TAKE_DOWN]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -266,7 +266,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard does not prevent damage from Struggle's recoil", async () => { game.override.moveset([Moves.STRUGGLE]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -284,7 +284,7 @@ describe("Abilities - Magic Guard", () => { //This tests different move attributes than the recoil tests above it("Magic Guard prevents self-damage from attacking moves", async () => { game.override.moveset([Moves.STEEL_BEAM]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -301,7 +301,7 @@ describe("Abilities - Magic Guard", () => { /* it("Magic Guard does not prevent self-damage from confusion", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); game.move.select(Moves.CHARM); @@ -311,7 +311,7 @@ describe("Abilities - Magic Guard", () => { it("Magic Guard does not prevent self-damage from non-attacking moves", async () => { game.override.moveset([Moves.BELLY_DRUM]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -333,7 +333,7 @@ describe("Abilities - Magic Guard", () => { game.override.enemyMoveset([Moves.SPORE, Moves.SPORE, Moves.SPORE, Moves.SPORE]); game.override.enemyAbility(Abilities.BAD_DREAMS); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -355,7 +355,7 @@ describe("Abilities - Magic Guard", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyAbility(Abilities.AFTERMATH); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -379,7 +379,7 @@ describe("Abilities - Magic Guard", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyAbility(Abilities.IRON_BARBS); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -402,7 +402,7 @@ describe("Abilities - Magic Guard", () => { game.override.moveset([Moves.ABSORB]); game.override.enemyAbility(Abilities.LIQUID_OOZE); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -425,7 +425,7 @@ describe("Abilities - Magic Guard", () => { game.override.passiveAbility(Abilities.SOLAR_POWER); game.override.weather(WeatherType.SUNNY); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/abilities/mimicry.test.ts b/test/abilities/mimicry.test.ts index 598f5790aa8..de196ffc939 100644 --- a/test/abilities/mimicry.test.ts +++ b/test/abilities/mimicry.test.ts @@ -55,7 +55,7 @@ describe("Abilities - Mimicry", () => { const playerPokemon = game.scene.getPlayerPokemon(); game.move.select(Moves.TRANSFORM); - await game.forceEnemyMove(Moves.PSYCHIC_TERRAIN); + await game.move.selectEnemyMove(Moves.PSYCHIC_TERRAIN); await game.toNextTurn(); expect(playerPokemon?.getTypes().includes(PokemonType.PSYCHIC)).toBe(true); @@ -64,7 +64,7 @@ describe("Abilities - Mimicry", () => { } game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(playerPokemon?.getTypes().includes(PokemonType.ELECTRIC)).toBe(true); }); @@ -75,13 +75,13 @@ describe("Abilities - Mimicry", () => { const playerPokemon = game.scene.getPlayerPokemon(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.FORESTS_CURSE); + await game.move.selectEnemyMove(Moves.FORESTS_CURSE); await game.toNextTurn(); expect(playerPokemon?.summonData.addedType).toBe(PokemonType.GRASS); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.GRASSY_TERRAIN); + await game.move.selectEnemyMove(Moves.GRASSY_TERRAIN); await game.phaseInterceptor.to("TurnEndPhase"); expect(playerPokemon?.summonData.addedType).toBeNull(); diff --git a/test/abilities/mirror_armor.test.ts b/test/abilities/mirror_armor.test.ts index bd61f39ba75..5f9c63531d4 100644 --- a/test/abilities/mirror_armor.test.ts +++ b/test/abilities/mirror_armor.test.ts @@ -46,7 +46,7 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate, enemy should lose -1 atk game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); @@ -63,7 +63,7 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate, enemy should lose -1 atk game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(userPokemon.getStatStage(Stat.ATK)).toBe(-1); @@ -82,8 +82,8 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate, enemy should lose -2 atk each game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); await game.toNextTurn(); expect(enemy1.getStatStage(Stat.ATK)).toBe(-2); @@ -104,8 +104,8 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate, enemy should lose -1 atk game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); await game.toNextTurn(); expect(enemy1.getStatStage(Stat.ATK)).toBe(0); @@ -124,7 +124,7 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate and uses tickle, enemy receives -2 atk and -1 defense game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1); @@ -144,8 +144,8 @@ describe("Ability - Mirror Armor", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER_2); await game.toNextTurn(); expect(player1.getStatStage(Stat.ATK)).toBe(0); @@ -168,7 +168,7 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate and uses tickle, enemy receives -2 atk and -1 defense game.move.select(Moves.TICKLE); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(userPokemon.getStatStage(Stat.DEF)).toBe(-1); @@ -187,7 +187,7 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate and uses tickle, enemy has white smoke, no one loses stats game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TICKLE, BattlerIndex.PLAYER); await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); @@ -206,7 +206,7 @@ describe("Ability - Mirror Armor", () => { // Enemy has intimidate and uses tickle, enemy has white smoke, no one loses stats game.move.select(Moves.TICKLE); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); @@ -224,7 +224,7 @@ describe("Ability - Mirror Armor", () => { // Enemy uses octolock, player loses stats at end of turn game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.OCTOLOCK, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.OCTOLOCK, BattlerIndex.PLAYER); await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); @@ -242,7 +242,7 @@ describe("Ability - Mirror Armor", () => { // Player uses octolock, enemy loses stats at end of turn game.move.select(Moves.OCTOLOCK); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(userPokemon.getStatStage(Stat.DEF)).toBe(0); @@ -261,7 +261,7 @@ describe("Ability - Mirror Armor", () => { const userPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(userPokemon.getStatStage(Stat.ATK)).toBe(-1); @@ -276,11 +276,11 @@ describe("Ability - Mirror Armor", () => { const userPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.STICKY_WEB, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.STICKY_WEB, BattlerIndex.PLAYER); await game.toNextTurn(); game.doSwitchPokemon(1); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); expect(userPokemon.getStatStage(Stat.SPD)).toBe(0); @@ -297,14 +297,14 @@ describe("Ability - Mirror Armor", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.STICKY_WEB, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.STICKY_WEB, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); await game.toNextTurn(); game.doSwitchPokemon(2); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER_2); await game.toNextTurn(); expect(enemy1.getStatStage(Stat.SPD)).toBe(-1); diff --git a/test/abilities/moxie.test.ts b/test/abilities/moxie.test.ts index bccdeda2b93..d4e1927e077 100644 --- a/test/abilities/moxie.test.ts +++ b/test/abilities/moxie.test.ts @@ -38,7 +38,7 @@ describe("Abilities - Moxie", () => { it("should raise ATK stat stage by 1 when winning a battle", async () => { const moveToUse = Moves.AERIAL_ACE; - await game.startBattle([Species.MIGHTYENA, Species.MIGHTYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.MIGHTYENA]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -56,7 +56,7 @@ describe("Abilities - Moxie", () => { async () => { game.override.battleStyle("double"); const moveToUse = Moves.AERIAL_ACE; - await game.startBattle([Species.MIGHTYENA, Species.MIGHTYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.MIGHTYENA]); const [firstPokemon, secondPokemon] = game.scene.getPlayerField(); diff --git a/test/abilities/mycelium_might.test.ts b/test/abilities/mycelium_might.test.ts index 4a5700045fa..18465f9b64e 100644 --- a/test/abilities/mycelium_might.test.ts +++ b/test/abilities/mycelium_might.test.ts @@ -41,7 +41,7 @@ describe("Abilities - Mycelium Might", () => { **/ it("will move last in its priority bracket and ignore protective abilities", async () => { - await game.startBattle([Species.REGIELEKI]); + await game.classicMode.startBattle([Species.REGIELEKI]); const enemyPokemon = game.scene.getEnemyPokemon(); const playerIndex = game.scene.getPlayerPokemon()?.getBattlerIndex(); @@ -65,7 +65,7 @@ describe("Abilities - Mycelium Might", () => { it("will still go first if a status move that is in a higher priority bracket than the opponent's move is used", async () => { game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); - await game.startBattle([Species.REGIELEKI]); + await game.classicMode.startBattle([Species.REGIELEKI]); const enemyPokemon = game.scene.getEnemyPokemon(); const playerIndex = game.scene.getPlayerPokemon()?.getBattlerIndex(); @@ -87,7 +87,7 @@ describe("Abilities - Mycelium Might", () => { }, 20000); it("will not affect non-status moves", async () => { - await game.startBattle([Species.REGIELEKI]); + await game.classicMode.startBattle([Species.REGIELEKI]); const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex(); const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex(); diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts index 979583b7d97..cf19f36c9d0 100644 --- a/test/abilities/neutralizing_gas.test.ts +++ b/test/abilities/neutralizing_gas.test.ts @@ -110,16 +110,16 @@ describe("Abilities - Neutralizing Gas", () => { await game.classicMode.startBattle([Species.ACCELGOR, Species.ACCELGOR]); game.move.select(Moves.SPLASH, 0); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); // Now one neut gas user is left game.move.select(Moves.SPLASH, 0); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left diff --git a/test/abilities/pastel_veil.test.ts b/test/abilities/pastel_veil.test.ts index 4ae9763c4a6..21da1d1353d 100644 --- a/test/abilities/pastel_veil.test.ts +++ b/test/abilities/pastel_veil.test.ts @@ -34,7 +34,7 @@ describe("Abilities - Pastel Veil", () => { }); it("prevents the user and its allies from being afflicted by poison", async () => { - await game.startBattle([Species.MAGIKARP, Species.GALAR_PONYTA]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.GALAR_PONYTA]); const ponyta = game.scene.getPlayerField()[1]; const magikarp = game.scene.getPlayerField()[0]; ponyta.abilityIndex = 1; @@ -50,7 +50,7 @@ describe("Abilities - Pastel Veil", () => { }); it("it heals the poisoned status condition of allies if user is sent out into battle", async () => { - await game.startBattle([Species.MAGIKARP, Species.FEEBAS, Species.GALAR_PONYTA]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS, Species.GALAR_PONYTA]); const ponyta = game.scene.getPlayerParty()[2]; const magikarp = game.scene.getPlayerField()[0]; ponyta.abilityIndex = 1; diff --git a/test/abilities/perish_body.test.ts b/test/abilities/perish_body.test.ts index 27e76cb52ad..140e087843c 100644 --- a/test/abilities/perish_body.test.ts +++ b/test/abilities/perish_body.test.ts @@ -64,14 +64,14 @@ describe("Abilities - Perish Song", () => { const magikarp = game.scene.getEnemyPokemon(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.PERISH_SONG); + await game.move.selectEnemyMove(Moves.PERISH_SONG); await game.toNextTurn(); expect(feebas?.summonData.tags[0].turnCount).toBe(3); expect(magikarp?.summonData.tags[0].turnCount).toBe(3); game.doSwitchPokemon(1); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const cursola = game.scene.getPlayerPokemon(); @@ -79,7 +79,7 @@ describe("Abilities - Perish Song", () => { expect(magikarp?.summonData.tags[0].turnCount).toBe(2); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.AQUA_JET); + await game.move.selectEnemyMove(Moves.AQUA_JET); await game.toNextTurn(); expect(cursola?.summonData.tags.length).toBe(0); @@ -94,7 +94,7 @@ describe("Abilities - Perish Song", () => { const cursola = game.scene.getPlayerPokemon(); game.move.select(Moves.WHIRLWIND); - await game.forceEnemyMove(Moves.PERISH_SONG); + await game.move.selectEnemyMove(Moves.PERISH_SONG); await game.toNextTurn(); const magikarp = game.scene.getEnemyPokemon(); @@ -102,7 +102,7 @@ describe("Abilities - Perish Song", () => { expect(magikarp?.summonData.tags.length).toBe(0); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.AQUA_JET); + await game.move.selectEnemyMove(Moves.AQUA_JET); await game.toNextTurn(); expect(cursola?.summonData.tags[0].turnCount).toBe(2); diff --git a/test/abilities/power_spot.test.ts b/test/abilities/power_spot.test.ts index c3accdb04f8..0a062537202 100644 --- a/test/abilities/power_spot.test.ts +++ b/test/abilities/power_spot.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Power Spot", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.REGIELEKI, Species.STONJOURNER]); + await game.classicMode.startBattle([Species.REGIELEKI, Species.STONJOURNER]); game.move.select(Moves.DAZZLING_GLEAM); game.move.select(Moves.SPLASH, 1); await game.phaseInterceptor.to(MoveEffectPhase); @@ -53,7 +53,7 @@ describe("Abilities - Power Spot", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.REGIELEKI, Species.STONJOURNER]); + await game.classicMode.startBattle([Species.REGIELEKI, Species.STONJOURNER]); game.move.select(Moves.BREAKING_SWIPE); game.move.select(Moves.SPLASH, 1); await game.phaseInterceptor.to(MoveEffectPhase); @@ -67,7 +67,7 @@ describe("Abilities - Power Spot", () => { vi.spyOn(moveToCheck, "calculateBattlePower"); - await game.startBattle([Species.STONJOURNER, Species.REGIELEKI]); + await game.classicMode.startBattle([Species.STONJOURNER, Species.REGIELEKI]); game.move.select(Moves.BREAKING_SWIPE); game.move.select(Moves.SPLASH, 1); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/abilities/protean.test.ts b/test/abilities/protean.test.ts index 8e6b69dabd6..bfe6dd32282 100644 --- a/test/abilities/protean.test.ts +++ b/test/abilities/protean.test.ts @@ -39,7 +39,7 @@ describe("Abilities - Protean", () => { test("ability applies and changes a pokemon's type", async () => { game.override.moveset([Moves.SPLASH]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -54,7 +54,7 @@ describe("Abilities - Protean", () => { test.skip("ability applies only once per switch in", async () => { game.override.moveset([Moves.SPLASH, Moves.AGILITY]); - await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.BULBASAUR]); let leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -90,7 +90,7 @@ describe("Abilities - Protean", () => { test("ability applies correctly even if the pokemon's move has a variable type", async () => { game.override.moveset([Moves.WEATHER_BALL]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -110,7 +110,7 @@ describe("Abilities - Protean", () => { game.override.moveset([Moves.TACKLE]); game.override.passiveAbility(Abilities.REFRIGERATE); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -128,7 +128,7 @@ describe("Abilities - Protean", () => { test("ability applies correctly even if the pokemon's move calls another move", async () => { game.override.moveset([Moves.NATURE_POWER]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -143,7 +143,7 @@ describe("Abilities - Protean", () => { test("ability applies correctly even if the pokemon's move is delayed / charging", async () => { game.override.moveset([Moves.DIG]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -158,7 +158,7 @@ describe("Abilities - Protean", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -176,7 +176,7 @@ describe("Abilities - Protean", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyMoveset([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -191,7 +191,7 @@ describe("Abilities - Protean", () => { game.override.moveset([Moves.TACKLE]); game.override.enemySpecies(Species.GASTLY); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -205,7 +205,7 @@ describe("Abilities - Protean", () => { test("ability is not applied if pokemon's type is the same as the move's type", async () => { game.override.moveset([Moves.SPLASH]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -220,7 +220,7 @@ describe("Abilities - Protean", () => { test("ability is not applied if pokemon is terastallized", async () => { game.override.moveset([Moves.SPLASH]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -236,7 +236,7 @@ describe("Abilities - Protean", () => { test("ability is not applied if pokemon uses struggle", async () => { game.override.moveset([Moves.STRUGGLE]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -250,7 +250,7 @@ describe("Abilities - Protean", () => { test("ability is not applied if the pokemon's move fails", async () => { game.override.moveset([Moves.BURN_UP]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -265,7 +265,7 @@ describe("Abilities - Protean", () => { game.override.moveset([Moves.TRICK_OR_TREAT]); game.override.enemySpecies(Species.GASTLY); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); @@ -279,7 +279,7 @@ describe("Abilities - Protean", () => { test("ability applies correctly and the pokemon curses itself", async () => { game.override.moveset([Moves.CURSE]); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; expect(leadPokemon).not.toBe(undefined); diff --git a/test/abilities/quick_draw.test.ts b/test/abilities/quick_draw.test.ts index 79a29b0ce77..e761d236a93 100644 --- a/test/abilities/quick_draw.test.ts +++ b/test/abilities/quick_draw.test.ts @@ -41,7 +41,7 @@ describe("Abilities - Quick Draw", () => { }); test("makes pokemon going first in its priority bracket", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const pokemon = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -63,7 +63,7 @@ describe("Abilities - Quick Draw", () => { retry: 5, }, async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const pokemon = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -83,7 +83,7 @@ describe("Abilities - Quick Draw", () => { test("does not increase priority", async () => { game.override.enemyMoveset([Moves.EXTREME_SPEED]); - await game.startBattle(); + await game.classicMode.startBattle(); const pokemon = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/sand_veil.test.ts b/test/abilities/sand_veil.test.ts index b82c79c681b..116850e1e5a 100644 --- a/test/abilities/sand_veil.test.ts +++ b/test/abilities/sand_veil.test.ts @@ -38,7 +38,7 @@ describe("Abilities - Sand Veil", () => { }); test("ability should increase the evasiveness of the source", async () => { - await game.startBattle([Species.SNORLAX, Species.BLISSEY]); + await game.classicMode.startBattle([Species.SNORLAX, Species.BLISSEY]); const leadPokemon = game.scene.getPlayerField(); diff --git a/test/abilities/schooling.test.ts b/test/abilities/schooling.test.ts index 803b4d2062a..ac4e3c837c5 100644 --- a/test/abilities/schooling.test.ts +++ b/test/abilities/schooling.test.ts @@ -39,7 +39,7 @@ describe("Abilities - SCHOOLING", () => { [Species.WISHIWASHI]: schoolForm, }); - await game.startBattle([Species.MAGIKARP, Species.WISHIWASHI]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.WISHIWASHI]); const wishiwashi = game.scene.getPlayerParty().find(p => p.species.speciesId === Species.WISHIWASHI)!; expect(wishiwashi).not.toBe(undefined); diff --git a/test/abilities/screen_cleaner.test.ts b/test/abilities/screen_cleaner.test.ts index 840291f6420..ea99ba00f87 100644 --- a/test/abilities/screen_cleaner.test.ts +++ b/test/abilities/screen_cleaner.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Screen Cleaner", () => { game.override.moveset([Moves.HAIL]); game.override.enemyMoveset([Moves.AURORA_VEIL, Moves.AURORA_VEIL, Moves.AURORA_VEIL, Moves.AURORA_VEIL]); - await game.startBattle([Species.MAGIKARP, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]); game.move.select(Moves.HAIL); await game.phaseInterceptor.to(TurnEndPhase); @@ -50,7 +50,7 @@ describe("Abilities - Screen Cleaner", () => { it("removes Light Screen", async () => { game.override.enemyMoveset([Moves.LIGHT_SCREEN, Moves.LIGHT_SCREEN, Moves.LIGHT_SCREEN, Moves.LIGHT_SCREEN]); - await game.startBattle([Species.MAGIKARP, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]); game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); @@ -67,7 +67,7 @@ describe("Abilities - Screen Cleaner", () => { it("removes Reflect", async () => { game.override.enemyMoveset([Moves.REFLECT, Moves.REFLECT, Moves.REFLECT, Moves.REFLECT]); - await game.startBattle([Species.MAGIKARP, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]); game.move.select(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/abilities/shields_down.test.ts b/test/abilities/shields_down.test.ts index 2f9d2fb1f97..444b1fabf73 100644 --- a/test/abilities/shields_down.test.ts +++ b/test/abilities/shields_down.test.ts @@ -76,7 +76,7 @@ describe("Abilities - SHIELDS DOWN", () => { await game.classicMode.startBattle([Species.MINIOR]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPORE); + await game.move.selectEnemyMove(Moves.SPORE); await game.phaseInterceptor.to(TurnEndPhase); expect(game.scene.getPlayerPokemon()!.status).toBe(undefined); @@ -115,12 +115,12 @@ describe("Abilities - SHIELDS DOWN", () => { // turn 1 game.move.select(Moves.GRAVITY); - await game.forceEnemyMove(Moves.TOXIC_SPIKES); + await game.move.selectEnemyMove(Moves.TOXIC_SPIKES); await game.toNextTurn(); // turn 2 game.doSwitchPokemon(1); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.MINIOR); @@ -134,7 +134,7 @@ describe("Abilities - SHIELDS DOWN", () => { await game.classicMode.startBattle([Species.MAGIKARP, Species.MINIOR]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.YAWN); + await game.move.selectEnemyMove(Moves.YAWN); await game.phaseInterceptor.to(TurnEndPhase); expect(game.scene.getPlayerPokemon()!.findTag(tag => tag.tagType === BattlerTagType.DROWSY)).toBe(undefined); @@ -146,7 +146,7 @@ describe("Abilities - SHIELDS DOWN", () => { await game.classicMode.startBattle([Species.MINIOR]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.CONFUSE_RAY); + await game.move.selectEnemyMove(Moves.CONFUSE_RAY); await game.phaseInterceptor.to(TurnEndPhase); @@ -162,7 +162,7 @@ describe("Abilities - SHIELDS DOWN", () => { await game.classicMode.startBattle([Species.MINIOR]); game.move.select(Moves.SPORE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.SLEEP); diff --git a/test/abilities/simple.test.ts b/test/abilities/simple.test.ts index 1f084b1bf4c..cf3a692a7b0 100644 --- a/test/abilities/simple.test.ts +++ b/test/abilities/simple.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Simple", () => { }); it("should double stat changes when applied", async () => { - await game.startBattle([Species.SLOWBRO]); + await game.classicMode.startBattle([Species.SLOWBRO]); const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/stakeout.test.ts b/test/abilities/stakeout.test.ts index 8a2231bba0b..d986046a7e1 100644 --- a/test/abilities/stakeout.test.ts +++ b/test/abilities/stakeout.test.ts @@ -42,7 +42,7 @@ describe("Abilities - Stakeout", () => { const [enemy1] = game.scene.getEnemyParty(); game.move.select(Moves.SURF); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const damage1 = enemy1.getInverseHp(); enemy1.hp = enemy1.getMaxHp(); @@ -65,17 +65,17 @@ describe("Abilities - Stakeout", () => { const [enemy1] = game.scene.getEnemyParty(); game.move.select(Moves.SURF); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const damage1 = enemy1.getInverseHp(); enemy1.hp = enemy1.getMaxHp(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.FLIP_TURN); + await game.move.selectEnemyMove(Moves.FLIP_TURN); await game.toNextTurn(); game.move.select(Moves.SURF); - await game.forceEnemyMove(Moves.FLIP_TURN); + await game.move.selectEnemyMove(Moves.FLIP_TURN); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); diff --git a/test/abilities/stall.test.ts b/test/abilities/stall.test.ts index 68b3fdedcd8..5c17f71c48d 100644 --- a/test/abilities/stall.test.ts +++ b/test/abilities/stall.test.ts @@ -37,7 +37,7 @@ describe("Abilities - Stall", () => { **/ it("Pokemon with Stall should move last in its priority bracket regardless of speed", async () => { - await game.startBattle([Species.SHUCKLE]); + await game.classicMode.startBattle([Species.SHUCKLE]); const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex(); const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex(); @@ -55,7 +55,7 @@ describe("Abilities - Stall", () => { }, 20000); it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async () => { - await game.startBattle([Species.SHUCKLE]); + await game.classicMode.startBattle([Species.SHUCKLE]); const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex(); const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex(); @@ -74,7 +74,7 @@ describe("Abilities - Stall", () => { it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async () => { game.override.ability(Abilities.STALL); - await game.startBattle([Species.SHUCKLE]); + await game.classicMode.startBattle([Species.SHUCKLE]); const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex(); const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex(); diff --git a/test/abilities/sturdy.test.ts b/test/abilities/sturdy.test.ts index bda8c6d1e35..dbdd1b4570e 100644 --- a/test/abilities/sturdy.test.ts +++ b/test/abilities/sturdy.test.ts @@ -36,14 +36,14 @@ describe("Abilities - Sturdy", () => { }); test("Sturdy activates when user is at full HP", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.CLOSE_COMBAT); await game.phaseInterceptor.to(MoveEndPhase); expect(game.scene.getEnemyParty()[0].hp).toBe(1); }); test("Sturdy doesn't activate when user is not at full HP", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0]; enemyPokemon.hp = enemyPokemon.getMaxHp() - 1; @@ -56,7 +56,7 @@ describe("Abilities - Sturdy", () => { }); test("Sturdy pokemon should be immune to OHKO moves", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.FISSURE); await game.phaseInterceptor.to(MoveEndPhase); @@ -67,7 +67,7 @@ describe("Abilities - Sturdy", () => { test("Sturdy is ignored by pokemon with `Abilities.MOLD_BREAKER`", async () => { game.override.ability(Abilities.MOLD_BREAKER); - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.CLOSE_COMBAT); await game.phaseInterceptor.to(DamageAnimPhase); diff --git a/test/abilities/supreme_overlord.test.ts b/test/abilities/supreme_overlord.test.ts index 6cc52de64bf..4c0be80daea 100644 --- a/test/abilities/supreme_overlord.test.ts +++ b/test/abilities/supreme_overlord.test.ts @@ -44,7 +44,7 @@ describe("Abilities - Supreme Overlord", () => { }); it("should increase Power by 20% if 2 Pokemon are fainted in the party", async () => { - await game.startBattle([Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE]); + await game.classicMode.startBattle([Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE]); game.move.select(Moves.EXPLOSION); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); diff --git a/test/abilities/sweet_veil.test.ts b/test/abilities/sweet_veil.test.ts index e609aa6e7d2..80d7165619f 100644 --- a/test/abilities/sweet_veil.test.ts +++ b/test/abilities/sweet_veil.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Sweet Veil", () => { }); it("prevents the user and its allies from falling asleep", async () => { - await game.startBattle([Species.SWIRLIX, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.SWIRLIX, Species.MAGIKARP]); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); @@ -45,7 +45,7 @@ describe("Abilities - Sweet Veil", () => { it("causes Rest to fail when used by the user or its allies", async () => { game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.SWIRLIX, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.SWIRLIX, Species.MAGIKARP]); game.move.select(Moves.SPLASH); game.move.select(Moves.REST, 1); @@ -57,7 +57,7 @@ describe("Abilities - Sweet Veil", () => { it("causes Yawn to fail if used on the user or its allies", async () => { game.override.enemyMoveset([Moves.YAWN, Moves.YAWN, Moves.YAWN, Moves.YAWN]); - await game.startBattle([Species.SWIRLIX, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.SWIRLIX, Species.MAGIKARP]); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); @@ -73,7 +73,7 @@ describe("Abilities - Sweet Veil", () => { game.override.startingLevel(5); game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.SHUCKLE, Species.SHUCKLE, Species.SWIRLIX]); + await game.classicMode.startBattle([Species.SHUCKLE, Species.SHUCKLE, Species.SWIRLIX]); game.move.select(Moves.SPLASH); game.move.select(Moves.YAWN, 1, BattlerIndex.PLAYER); diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index ff28c3b6a09..ea4f84545aa 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -244,8 +244,8 @@ describe("Abilities - Unburden", () => { // Turn 1: Treecko gets hit by False Swipe and eats Sitrus Berry, activating Unburden game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.FALSE_SWIPE, 0); - await game.forceEnemyMove(Moves.FALSE_SWIPE, 0); + await game.move.selectEnemyMove(Moves.FALSE_SWIPE, 0); + await game.move.selectEnemyMove(Moves.FALSE_SWIPE, 0); await game.phaseInterceptor.to("TurnEndPhase"); expect(getHeldItemCount(treecko)).toBeLessThan(playerHeldItems); @@ -302,7 +302,7 @@ describe("Abilities - Unburden", () => { // Turn 1: Get hit by False Swipe and eat Sitrus Berry, activating Unburden game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.FALSE_SWIPE); + await game.move.selectEnemyMove(Moves.FALSE_SWIPE); await game.toNextTurn(); expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); @@ -310,7 +310,7 @@ describe("Abilities - Unburden", () => { // Turn 2: Get hit by Worry Seed, deactivating Unburden game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WORRY_SEED); + await game.move.selectEnemyMove(Moves.WORRY_SEED); await game.toNextTurn(); expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); @@ -343,13 +343,13 @@ describe("Abilities - Unburden", () => { const initialSpeed = treecko.getStat(Stat.SPD); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.THIEF); + await game.move.selectEnemyMove(Moves.THIEF); game.doSelectPartyPokemon(1); await game.toNextTurn(); game.doRevivePokemon(1); game.doSwitchPokemon(1); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(game.scene.getPlayerPokemon()!).toBe(treecko); @@ -372,8 +372,8 @@ describe("Abilities - Unburden", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.REVIVAL_BLESSING, 1); - await game.forceEnemyMove(Moves.THIEF, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.THIEF, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]); game.doSelectPartyPokemon(0, "RevivalBlessingPhase"); await game.toNextTurn(); diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 67885a82163..32a627f20f9 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -544,8 +544,8 @@ describe("Abilities - Wimp Out", () => { // turn 1 game.move.select(Moves.DRAGON_ENERGY, 0); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.ENDURE); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.ENDURE); await game.phaseInterceptor.to("SelectModifierPhase"); expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); diff --git a/test/abilities/wind_power.test.ts b/test/abilities/wind_power.test.ts index 66c72d454ab..11585520c73 100644 --- a/test/abilities/wind_power.test.ts +++ b/test/abilities/wind_power.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Wind Power", () => { }); it("it becomes charged when hit by wind moves", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const shiftry = game.scene.getEnemyPokemon()!; expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); @@ -46,7 +46,7 @@ describe("Abilities - Wind Power", () => { game.override.ability(Abilities.WIND_POWER); game.override.enemySpecies(Species.MAGIKARP); - await game.startBattle([Species.SHIFTRY]); + await game.classicMode.startBattle([Species.SHIFTRY]); const shiftry = game.scene.getPlayerPokemon()!; expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); @@ -61,7 +61,7 @@ describe("Abilities - Wind Power", () => { game.override.enemySpecies(Species.MAGIKARP); game.override.ability(Abilities.WIND_POWER); - await game.startBattle([Species.SHIFTRY]); + await game.classicMode.startBattle([Species.SHIFTRY]); const magikarp = game.scene.getEnemyPokemon()!; const shiftry = game.scene.getPlayerPokemon()!; @@ -79,7 +79,7 @@ describe("Abilities - Wind Power", () => { it("does not interact with Sandstorm", async () => { game.override.enemySpecies(Species.MAGIKARP); - await game.startBattle([Species.SHIFTRY]); + await game.classicMode.startBattle([Species.SHIFTRY]); const shiftry = game.scene.getPlayerPokemon()!; expect(shiftry.getTag(BattlerTagType.CHARGED)).toBeUndefined(); diff --git a/test/abilities/wonder_skin.test.ts b/test/abilities/wonder_skin.test.ts index fd4cc77bd1c..cb5dd4e117f 100644 --- a/test/abilities/wonder_skin.test.ts +++ b/test/abilities/wonder_skin.test.ts @@ -36,7 +36,7 @@ describe("Abilities - Wonder Skin", () => { vi.spyOn(moveToCheck, "calculateBattleAccuracy"); - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); game.move.select(Moves.CHARM); await game.phaseInterceptor.to(MoveEffectPhase); @@ -48,7 +48,7 @@ describe("Abilities - Wonder Skin", () => { vi.spyOn(moveToCheck, "calculateBattleAccuracy"); - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); game.move.select(Moves.TACKLE); await game.phaseInterceptor.to(MoveEffectPhase); diff --git a/test/abilities/zero_to_hero.test.ts b/test/abilities/zero_to_hero.test.ts index 2cdc516dc6b..c159d007765 100644 --- a/test/abilities/zero_to_hero.test.ts +++ b/test/abilities/zero_to_hero.test.ts @@ -39,7 +39,7 @@ describe("Abilities - ZERO TO HERO", () => { [Species.PALAFIN]: heroForm, }); - await game.startBattle([Species.FEEBAS, Species.PALAFIN, Species.PALAFIN]); + await game.classicMode.startBattle([Species.FEEBAS, Species.PALAFIN, Species.PALAFIN]); const palafin1 = game.scene.getPlayerParty()[1]; const palafin2 = game.scene.getPlayerParty()[2]; @@ -61,7 +61,7 @@ describe("Abilities - ZERO TO HERO", () => { }); it("should swap to Hero form when switching out during a battle", async () => { - await game.startBattle([Species.PALAFIN, Species.FEEBAS]); + await game.classicMode.startBattle([Species.PALAFIN, Species.FEEBAS]); const palafin = game.scene.getPlayerPokemon()!; expect(palafin.formIndex).toBe(baseForm); @@ -72,7 +72,7 @@ describe("Abilities - ZERO TO HERO", () => { }); it("should not swap to Hero form if switching due to faint", async () => { - await game.startBattle([Species.PALAFIN, Species.FEEBAS]); + await game.classicMode.startBattle([Species.PALAFIN, Species.FEEBAS]); const palafin = game.scene.getPlayerPokemon()!; expect(palafin.formIndex).toBe(baseForm); @@ -89,7 +89,7 @@ describe("Abilities - ZERO TO HERO", () => { [Species.PALAFIN]: heroForm, }); - await game.startBattle([Species.PALAFIN, Species.FEEBAS]); + await game.classicMode.startBattle([Species.PALAFIN, Species.FEEBAS]); const palafin = game.scene.getPlayerPokemon()!; expect(palafin.formIndex).toBe(heroForm); diff --git a/test/arena/weather_fog.test.ts b/test/arena/weather_fog.test.ts index ae41c9d14e8..69f167b8cf1 100644 --- a/test/arena/weather_fog.test.ts +++ b/test/arena/weather_fog.test.ts @@ -37,7 +37,7 @@ describe("Weather - Fog", () => { vi.spyOn(moveToCheck, "calculateBattleAccuracy"); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); game.move.select(Moves.TACKLE); await game.phaseInterceptor.to(MoveEffectPhase); diff --git a/test/battle/battle-order.test.ts b/test/battle/battle-order.test.ts index 43fa1e59c14..876730169f9 100644 --- a/test/battle/battle-order.test.ts +++ b/test/battle/battle-order.test.ts @@ -32,7 +32,7 @@ describe("Battle order", () => { }); it("opponent faster than player 50 vs 150", async () => { - await game.startBattle([Species.BULBASAUR]); + await game.classicMode.startBattle([Species.BULBASAUR]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -51,7 +51,7 @@ describe("Battle order", () => { }, 20000); it("Player faster than opponent 150 vs 50", async () => { - await game.startBattle([Species.BULBASAUR]); + await game.classicMode.startBattle([Species.BULBASAUR]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -71,7 +71,7 @@ describe("Battle order", () => { it("double - both opponents faster than player 50/50 vs 150/150", async () => { game.override.battleStyle("double"); - await game.startBattle([Species.BULBASAUR, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.BULBASAUR, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -95,7 +95,7 @@ describe("Battle order", () => { it("double - speed tie except 1 - 100/100 vs 100/150", async () => { game.override.battleStyle("double"); - await game.startBattle([Species.BULBASAUR, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.BULBASAUR, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -119,7 +119,7 @@ describe("Battle order", () => { it("double - speed tie 100/150 vs 100/150", async () => { game.override.battleStyle("double"); - await game.startBattle([Species.BULBASAUR, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.BULBASAUR, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); diff --git a/test/battle/battle.test.ts b/test/battle/battle.test.ts index e980984580e..8c4315dcabc 100644 --- a/test/battle/battle.test.ts +++ b/test/battle/battle.test.ts @@ -85,7 +85,7 @@ describe("Test Battle Phase", () => { }, 20000); it("newGame one-liner", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); @@ -98,7 +98,7 @@ describe("Test Battle Phase", () => { game.override.moveset([Moves.TACKLE]); game.override.enemyAbility(Abilities.HYDRATION); game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.TACKLE); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(SelectModifierPhase, false); }, 20000); @@ -112,7 +112,7 @@ describe("Test Battle Phase", () => { game.override.enemyAbility(Abilities.HYDRATION); game.override.enemyMoveset([Moves.TAIL_WHIP, Moves.TAIL_WHIP, Moves.TAIL_WHIP, Moves.TAIL_WHIP]); game.override.battleStyle("single"); - await game.startBattle(); + await game.classicMode.startBattle(); game.move.select(Moves.TACKLE); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase, false); }, 20000); @@ -127,7 +127,7 @@ describe("Test Battle Phase", () => { }, 20000); it("start battle with selected team", async () => { - await game.startBattle([Species.CHARIZARD, Species.CHANSEY, Species.MEW]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.CHANSEY, Species.MEW]); expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(Species.CHARIZARD); expect(game.scene.getPlayerParty()[1].species.speciesId).toBe(Species.CHANSEY); expect(game.scene.getPlayerParty()[2].species.speciesId).toBe(Species.MEW); @@ -207,7 +207,7 @@ describe("Test Battle Phase", () => { game.override.enemySpecies(Species.MIGHTYENA); game.override.enemyAbility(Abilities.HYDRATION); game.override.ability(Abilities.HYDRATION); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); @@ -217,7 +217,7 @@ describe("Test Battle Phase", () => { game.override.enemySpecies(Species.MIGHTYENA); game.override.enemyAbility(Abilities.HYDRATION); game.override.ability(Abilities.HYDRATION); - await game.startBattle([Species.BLASTOISE]); + await game.classicMode.startBattle([Species.BLASTOISE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); @@ -228,7 +228,7 @@ describe("Test Battle Phase", () => { game.override.enemyAbility(Abilities.HYDRATION); game.override.ability(Abilities.HYDRATION); game.override.startingWave(3); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); @@ -239,7 +239,7 @@ describe("Test Battle Phase", () => { game.override.enemyAbility(Abilities.HYDRATION); game.override.ability(Abilities.HYDRATION); game.override.startingWave(3); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD, Species.DARKRAI, Species.GABITE]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD, Species.DARKRAI, Species.GABITE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); @@ -255,7 +255,7 @@ describe("Test Battle Phase", () => { game.override.startingWave(3); game.override.moveset([moveToUse]); game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); - await game.startBattle([Species.DARMANITAN, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.DARMANITAN, Species.CHARIZARD]); game.move.select(moveToUse); await game.phaseInterceptor.to(DamageAnimPhase, false); @@ -275,7 +275,7 @@ describe("Test Battle Phase", () => { game.override.startingWave(3); game.override.moveset([moveToUse]); game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]); - await game.startBattle(); + await game.classicMode.startBattle(); const turn = game.scene.currentBattle.turn; game.move.select(moveToUse); await game.toNextTurn(); @@ -318,7 +318,7 @@ describe("Test Battle Phase", () => { .enemyMoveset(Moves.SPLASH) .startingHeldItems([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }]); - await game.startBattle(); + await game.classicMode.startBattle(); game.scene.getPlayerPokemon()!.hp = 1; game.move.select(moveToUse); diff --git a/test/battle/double_battle.test.ts b/test/battle/double_battle.test.ts index a30d55aac3d..99c52ea5add 100644 --- a/test/battle/double_battle.test.ts +++ b/test/battle/double_battle.test.ts @@ -34,7 +34,7 @@ describe("Double Battles", () => { // (There were bugs that either only summon one when can summon two, player stuck in switchPhase etc) it("3v2 edge case: player summons 2 pokemon on the next battle after being fainted and revived", async () => { game.override.battleStyle("double").enemyMoveset(Moves.SPLASH).moveset(Moves.SPLASH); - await game.startBattle([Species.BULBASAUR, Species.CHARIZARD, Species.SQUIRTLE]); + await game.classicMode.startBattle([Species.BULBASAUR, Species.CHARIZARD, Species.SQUIRTLE]); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH, 1); diff --git a/test/battle/special_battle.test.ts b/test/battle/special_battle.test.ts index 163f23e488d..6f4034cd8cd 100644 --- a/test/battle/special_battle.test.ts +++ b/test/battle/special_battle.test.ts @@ -33,63 +33,63 @@ describe("Test Battle Phase", () => { it("startBattle 2vs1 boss", async () => { game.override.battleStyle("single").startingWave(10); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 boss", async () => { game.override.battleStyle("double").startingWave(10); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs1 trainer", async () => { game.override.battleStyle("single").startingWave(5); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs1 rival", async () => { game.override.battleStyle("single").startingWave(8); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 rival", async () => { game.override.battleStyle("double").startingWave(8); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 1vs1 trainer", async () => { game.override.battleStyle("single").startingWave(5); - await game.startBattle([Species.BLASTOISE]); + await game.classicMode.startBattle([Species.BLASTOISE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 2vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); it("startBattle 4vs2 trainer", async () => { game.override.battleStyle("double").startingWave(5); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD, Species.DARKRAI, Species.GABITE]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD, Species.DARKRAI, Species.GABITE]); expect(game.scene.ui?.getMode()).toBe(UiMode.COMMAND); expect(game.scene.getCurrentPhase()!.constructor.name).toBe(CommandPhase.name); }, 20000); diff --git a/test/daily_mode.test.ts b/test/daily_mode.test.ts index a7f5784087a..a5901da4821 100644 --- a/test/daily_mode.test.ts +++ b/test/daily_mode.test.ts @@ -30,7 +30,7 @@ describe("Daily Mode", () => { }); it("should initialize properly", async () => { - await game.dailyMode.runToSummon(); + await game.dailyMode.startBattle(); const party = game.scene.getPlayerParty(); expect(party).toHaveLength(3); diff --git a/test/evolution.test.ts b/test/evolution.test.ts index 4f91cd99382..a453d744da8 100644 --- a/test/evolution.test.ts +++ b/test/evolution.test.ts @@ -110,7 +110,7 @@ describe("Evolution", () => { .startingLevel(16) .enemyLevel(50); - await game.startBattle([Species.TOTODILE]); + await game.classicMode.startBattle([Species.TOTODILE]); const totodile = game.scene.getPlayerPokemon()!; const hpBefore = totodile.hp; @@ -138,7 +138,7 @@ describe("Evolution", () => { .startingLevel(13) .enemyLevel(30); - await game.startBattle([Species.CYNDAQUIL]); + await game.classicMode.startBattle([Species.CYNDAQUIL]); const cyndaquil = game.scene.getPlayerPokemon()!; cyndaquil.hp = Math.floor(cyndaquil.getMaxHp() / 2); @@ -171,7 +171,7 @@ describe("Evolution", () => { * If the value is 0, it's a 3 family maushold, whereas if the value is * 1, 2 or 3, it's a 4 family maushold */ - await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus + await game.classicMode.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus const playerPokemon = game.scene.getPlayerPokemon()!; playerPokemon.level = 25; // tandemaus evolves at level 25 vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); // setting the random generator to be 0 to force a three family maushold diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index 6e20bc723e5..e848bceb514 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -40,7 +40,7 @@ describe("Items - Dire Hit", () => { }, 20000); it("should raise CRIT stage by 1", async () => { - await game.startBattle([Species.GASTLY]); + await game.classicMode.startBattle([Species.GASTLY]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -56,7 +56,7 @@ describe("Items - Dire Hit", () => { it("should renew how many battles are left of existing DIRE_HIT when picking up new DIRE_HIT", async () => { game.override.itemRewards([{ name: "DIRE_HIT" }]); - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); game.move.select(Moves.SPLASH); diff --git a/test/items/exp_booster.test.ts b/test/items/exp_booster.test.ts index ec7528c3b23..106574b6849 100644 --- a/test/items/exp_booster.test.ts +++ b/test/items/exp_booster.test.ts @@ -29,7 +29,7 @@ describe("EXP Modifier Items", () => { it("EXP booster items stack multiplicatively", async () => { game.override.startingHeldItems([{ name: "LUCKY_EGG", count: 3 }, { name: "GOLDEN_EGG" }]); - await game.startBattle(); + await game.classicMode.startBattle(); const partyMember = game.scene.getPlayerPokemon()!; partyMember.exp = 100; diff --git a/test/items/leek.test.ts b/test/items/leek.test.ts index 9bde2c86339..7a6975f6dae 100644 --- a/test/items/leek.test.ts +++ b/test/items/leek.test.ts @@ -32,7 +32,7 @@ describe("Items - Leek", () => { }); it("should raise CRIT stage by 2 when held by FARFETCHD", async () => { - await game.startBattle([Species.FARFETCHD]); + await game.classicMode.startBattle([Species.FARFETCHD]); const enemyMember = game.scene.getEnemyPokemon()!; @@ -46,7 +46,7 @@ describe("Items - Leek", () => { }, 20000); it("should raise CRIT stage by 2 when held by GALAR_FARFETCHD", async () => { - await game.startBattle([Species.GALAR_FARFETCHD]); + await game.classicMode.startBattle([Species.GALAR_FARFETCHD]); const enemyMember = game.scene.getEnemyPokemon()!; @@ -60,7 +60,7 @@ describe("Items - Leek", () => { }, 20000); it("should raise CRIT stage by 2 when held by SIRFETCHD", async () => { - await game.startBattle([Species.SIRFETCHD]); + await game.classicMode.startBattle([Species.SIRFETCHD]); const enemyMember = game.scene.getEnemyPokemon()!; @@ -77,7 +77,7 @@ describe("Items - Leek", () => { // Randomly choose from the Farfetch'd line const species = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD]; - await game.startBattle([species[randInt(species.length)], Species.PIKACHU]); + await game.classicMode.startBattle([species[randInt(species.length)], Species.PIKACHU]); const [partyMember, ally] = game.scene.getPlayerParty(); @@ -105,7 +105,7 @@ describe("Items - Leek", () => { // Randomly choose from the Farfetch'd line const species = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD]; - await game.startBattle([Species.PIKACHU, species[randInt(species.length)]]); + await game.classicMode.startBattle([Species.PIKACHU, species[randInt(species.length)]]); const [partyMember, ally] = game.scene.getPlayerParty(); @@ -130,7 +130,7 @@ describe("Items - Leek", () => { }, 20000); it("should not raise CRIT stage when held by a Pokemon outside of FARFETCHD line", async () => { - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); const enemyMember = game.scene.getEnemyPokemon()!; diff --git a/test/items/leftovers.test.ts b/test/items/leftovers.test.ts index 19739703f19..f4825c2b721 100644 --- a/test/items/leftovers.test.ts +++ b/test/items/leftovers.test.ts @@ -34,7 +34,7 @@ describe("Items - Leftovers", () => { }); it("leftovers works", async () => { - await game.startBattle([Species.ARCANINE]); + await game.classicMode.startBattle([Species.ARCANINE]); // Make sure leftovers are there expect(game.scene.modifiers[0].type.id).toBe("LEFTOVERS"); diff --git a/test/items/light_ball.test.ts b/test/items/light_ball.test.ts index cdcffe810fa..214f6f624e6 100644 --- a/test/items/light_ball.test.ts +++ b/test/items/light_ball.test.ts @@ -150,7 +150,7 @@ describe("Items - Light Ball", () => { }, 20000); it("LIGHT_BALL held by fused PIKACHU (part)", async () => { - await game.startBattle([Species.MAROWAK, Species.PIKACHU]); + await game.classicMode.startBattle([Species.MAROWAK, Species.PIKACHU]); const partyMember = game.scene.getPlayerParty()[0]; const ally = game.scene.getPlayerParty()[1]; @@ -189,7 +189,7 @@ describe("Items - Light Ball", () => { }, 20000); it("LIGHT_BALL not held by PIKACHU", async () => { - await game.startBattle([Species.MAROWAK]); + await game.classicMode.startBattle([Species.MAROWAK]); const partyMember = game.scene.getPlayerParty()[0]; diff --git a/test/items/metal_powder.test.ts b/test/items/metal_powder.test.ts index e924d75fce9..a9a81072622 100644 --- a/test/items/metal_powder.test.ts +++ b/test/items/metal_powder.test.ts @@ -82,7 +82,7 @@ describe("Items - Metal Powder", () => { }); it("METAL_POWDER held by DITTO", async () => { - await game.startBattle([Species.DITTO]); + await game.classicMode.startBattle([Species.DITTO]); const partyMember = game.scene.getPlayerParty()[0]; @@ -105,7 +105,7 @@ describe("Items - Metal Powder", () => { }, 20000); it("METAL_POWDER held by fused DITTO (base)", async () => { - await game.startBattle([Species.DITTO, Species.MAROWAK]); + await game.classicMode.startBattle([Species.DITTO, Species.MAROWAK]); const partyMember = game.scene.getPlayerParty()[0]; const ally = game.scene.getPlayerParty()[1]; @@ -138,7 +138,7 @@ describe("Items - Metal Powder", () => { }, 20000); it("METAL_POWDER held by fused DITTO (part)", async () => { - await game.startBattle([Species.MAROWAK, Species.DITTO]); + await game.classicMode.startBattle([Species.MAROWAK, Species.DITTO]); const partyMember = game.scene.getPlayerParty()[0]; const ally = game.scene.getPlayerParty()[1]; @@ -171,7 +171,7 @@ describe("Items - Metal Powder", () => { }, 20000); it("METAL_POWDER not held by DITTO", async () => { - await game.startBattle([Species.MAROWAK]); + await game.classicMode.startBattle([Species.MAROWAK]); const partyMember = game.scene.getPlayerParty()[0]; diff --git a/test/items/scope_lens.test.ts b/test/items/scope_lens.test.ts index f67966ea3c9..c8061ea3696 100644 --- a/test/items/scope_lens.test.ts +++ b/test/items/scope_lens.test.ts @@ -31,7 +31,7 @@ describe("Items - Scope Lens", () => { }, 20000); it("should raise CRIT stage by 1", async () => { - await game.startBattle([Species.GASTLY]); + await game.classicMode.startBattle([Species.GASTLY]); const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/astonish.test.ts b/test/moves/astonish.test.ts index accddcd545d..daa9b0bb878 100644 --- a/test/moves/astonish.test.ts +++ b/test/moves/astonish.test.ts @@ -39,7 +39,7 @@ describe("Moves - Astonish", () => { }); test("move effect should cancel the target's move on the turn it applies", async () => { - await game.startBattle([Species.MEOWSCARADA]); + await game.classicMode.startBattle([Species.MEOWSCARADA]); const leadPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/moves/beat_up.test.ts b/test/moves/beat_up.test.ts index ad6cad40d32..09166dafb9d 100644 --- a/test/moves/beat_up.test.ts +++ b/test/moves/beat_up.test.ts @@ -35,7 +35,7 @@ describe("Moves - Beat Up", () => { }); it("should hit once for each healthy player Pokemon", async () => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.MAGIKARP, Species.BULBASAUR, Species.CHARMANDER, @@ -63,7 +63,7 @@ describe("Moves - Beat Up", () => { }); it("should not count player Pokemon with status effects towards hit count", async () => { - await game.startBattle([ + await game.classicMode.startBattle([ Species.MAGIKARP, Species.BULBASAUR, Species.CHARMANDER, diff --git a/test/moves/belly_drum.test.ts b/test/moves/belly_drum.test.ts index 8ee1026bf20..9deff207446 100644 --- a/test/moves/belly_drum.test.ts +++ b/test/moves/belly_drum.test.ts @@ -42,7 +42,7 @@ describe("Moves - BELLY DRUM", () => { // Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Belly_Drum_(move) test("raises the user's ATK stat stage to its max, at the cost of 1/2 of its maximum HP", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); @@ -55,7 +55,7 @@ describe("Moves - BELLY DRUM", () => { }); test("will still take effect if an uninvolved stat stage is at max", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); @@ -73,7 +73,7 @@ describe("Moves - BELLY DRUM", () => { }); test("fails if the pokemon's ATK stat stage is at its maximum", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -87,7 +87,7 @@ describe("Moves - BELLY DRUM", () => { }); test("fails if the user's health is less than 1/2", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); diff --git a/test/moves/chilly_reception.test.ts b/test/moves/chilly_reception.test.ts index 56da5dd400c..1c6c5ce127e 100644 --- a/test/moves/chilly_reception.test.ts +++ b/test/moves/chilly_reception.test.ts @@ -77,7 +77,7 @@ describe("Moves - Chilly Reception", () => { await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.TACKLE); + await game.move.selectEnemyMove(Moves.TACKLE); await game.phaseInterceptor.to("BerryPhase", false); expect(game.scene.arena.weather?.weatherType).toBe(undefined); diff --git a/test/moves/clangorous_soul.test.ts b/test/moves/clangorous_soul.test.ts index 56f19a0e088..c7165a0a881 100644 --- a/test/moves/clangorous_soul.test.ts +++ b/test/moves/clangorous_soul.test.ts @@ -38,7 +38,7 @@ describe("Moves - Clangorous Soul", () => { //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Clangorous_Soul_(move) it("raises the user's ATK, DEF, SPATK, SPDEF, and SPD stat stages by 1 each at the cost of 1/3 of its maximum HP", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); @@ -55,7 +55,7 @@ describe("Moves - Clangorous Soul", () => { }); it("will still take effect if one or more of the involved stat stages are not at max", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); @@ -78,7 +78,7 @@ describe("Moves - Clangorous Soul", () => { }); it("fails if all stat stages involved are at max", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -100,7 +100,7 @@ describe("Moves - Clangorous Soul", () => { }); it("fails if the user's health is less than 1/3", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); diff --git a/test/moves/crafty_shield.test.ts b/test/moves/crafty_shield.test.ts index c61e6d3848a..ec4c87fa060 100644 --- a/test/moves/crafty_shield.test.ts +++ b/test/moves/crafty_shield.test.ts @@ -39,7 +39,7 @@ describe("Moves - Crafty Shield", () => { }); test("should protect the user and allies from status moves", async () => { - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -57,7 +57,7 @@ describe("Moves - Crafty Shield", () => { test("should not protect the user and allies from attack moves", async () => { game.override.enemyMoveset([Moves.TACKLE]); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -76,7 +76,7 @@ describe("Moves - Crafty Shield", () => { game.override.enemySpecies(Species.DUSCLOPS); game.override.enemyMoveset([Moves.CURSE]); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -92,7 +92,7 @@ describe("Moves - Crafty Shield", () => { }); test("should not block allies' self-targeted moves", async () => { - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); diff --git a/test/moves/defog.test.ts b/test/moves/defog.test.ts index 58631150b6f..87c3845d55c 100644 --- a/test/moves/defog.test.ts +++ b/test/moves/defog.test.ts @@ -39,7 +39,7 @@ describe("Moves - Defog", () => { const enemyPokemon = game.scene.getEnemyField(); game.move.select(Moves.SAFEGUARD); - await game.forceEnemyMove(Moves.DEFOG); + await game.move.selectEnemyMove(Moves.DEFOG); await game.phaseInterceptor.to("BerryPhase"); expect(playerPokemon[0].isSafeguarded(enemyPokemon[0])).toBe(false); @@ -53,12 +53,12 @@ describe("Moves - Defog", () => { const playerPokemon = game.scene.getPlayerField(); game.move.select(Moves.MIST); - await game.forceEnemyMove(Moves.DEFOG); + await game.move.selectEnemyMove(Moves.DEFOG); await game.toNextTurn(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.GROWL); + await game.move.selectEnemyMove(Moves.GROWL); await game.phaseInterceptor.to("BerryPhase"); diff --git a/test/moves/disable.test.ts b/test/moves/disable.test.ts index d21716145a4..5b2b687bd5d 100644 --- a/test/moves/disable.test.ts +++ b/test/moves/disable.test.ts @@ -139,12 +139,12 @@ describe("Moves - Disable", () => { const enemyMon = game.scene.getEnemyPokemon()!; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); game.move.select(Moves.DISABLE); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); diff --git a/test/moves/double_team.test.ts b/test/moves/double_team.test.ts index 8eac6be11f4..b97fb1a338e 100644 --- a/test/moves/double_team.test.ts +++ b/test/moves/double_team.test.ts @@ -33,7 +33,7 @@ describe("Moves - Double Team", () => { }); it("raises the user's EVA stat stage by 1", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const ally = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/moves/dragon_tail.test.ts b/test/moves/dragon_tail.test.ts index 0e7cd04d078..409ce7e28f8 100644 --- a/test/moves/dragon_tail.test.ts +++ b/test/moves/dragon_tail.test.ts @@ -206,7 +206,7 @@ describe("Moves - Dragon Tail", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.DRAGON_TAIL); + await game.move.selectEnemyMove(Moves.DRAGON_TAIL); await game.toNextTurn(); expect(bulbasaur.isOnField()).toBe(false); @@ -260,7 +260,7 @@ describe("Moves - Dragon Tail", () => { eevee.status = new Status(StatusEffect.FAINT); expect(eevee.isFainted()).toBe(true); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted @@ -268,7 +268,7 @@ describe("Moves - Dragon Tail", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.DRAGON_TAIL); + await game.move.selectEnemyMove(Moves.DRAGON_TAIL); await game.toNextTurn(); expect(lapras.isOnField()).toBe(false); @@ -289,7 +289,7 @@ describe("Moves - Dragon Tail", () => { eevee.status = new Status(StatusEffect.FAINT); expect(eevee.isFainted()).toBe(true); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted @@ -297,7 +297,7 @@ describe("Moves - Dragon Tail", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.DRAGON_TAIL); + await game.move.selectEnemyMove(Moves.DRAGON_TAIL); await game.toNextTurn(); expect(lapras.isOnField()).toBe(true); diff --git a/test/moves/dynamax_cannon.test.ts b/test/moves/dynamax_cannon.test.ts index 6c4f1a321e3..62004e62778 100644 --- a/test/moves/dynamax_cannon.test.ts +++ b/test/moves/dynamax_cannon.test.ts @@ -45,7 +45,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 100 power against an enemy below level cap", async () => { game.override.enemyLevel(1); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -57,7 +57,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 100 power against an enemy at level cap", async () => { game.override.enemyLevel(10); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -69,7 +69,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 120 power against an enemy 1% above level cap", async () => { game.override.enemyLevel(101); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -84,7 +84,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 140 power against an enemy 2% above level capp", async () => { game.override.enemyLevel(102); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -99,7 +99,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 160 power against an enemy 3% above level cap", async () => { game.override.enemyLevel(103); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -114,7 +114,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 180 power against an enemy 4% above level cap", async () => { game.override.enemyLevel(104); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -129,7 +129,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 200 power against an enemy 5% above level cap", async () => { game.override.enemyLevel(105); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); @@ -144,7 +144,7 @@ describe("Moves - Dynamax Cannon", () => { it("should return 200 power against an enemy way above level cap", async () => { game.override.enemyLevel(999); - await game.startBattle([Species.ETERNATUS]); + await game.classicMode.startBattle([Species.ETERNATUS]); game.move.select(dynamaxCannon.id); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); diff --git a/test/moves/encore.test.ts b/test/moves/encore.test.ts index 519e7860c04..f941328fc90 100644 --- a/test/moves/encore.test.ts +++ b/test/moves/encore.test.ts @@ -42,7 +42,7 @@ describe("Moves - Encore", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; game.move.select(Moves.ENCORE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(enemyPokemon.getTag(BattlerTagType.ENCORE)).toBeDefined(); diff --git a/test/moves/fairy_lock.test.ts b/test/moves/fairy_lock.test.ts index e967221bcae..faffdee2304 100644 --- a/test/moves/fairy_lock.test.ts +++ b/test/moves/fairy_lock.test.ts @@ -40,8 +40,8 @@ describe("Moves - Fairy Lock", () => { game.move.select(Moves.FAIRY_LOCK); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER)).toBeDefined(); expect(game.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.ENEMY)).toBeDefined(); @@ -50,8 +50,8 @@ describe("Moves - Fairy Lock", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(playerPokemon[0].isTrapped()).toEqual(true); expect(playerPokemon[1].isTrapped()).toEqual(true); @@ -70,8 +70,8 @@ describe("Moves - Fairy Lock", () => { game.move.select(Moves.FAIRY_LOCK); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER)).toBeDefined(); @@ -84,8 +84,8 @@ describe("Moves - Fairy Lock", () => { game.move.select(Moves.SPLASH); game.doSwitchPokemon(2); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); await game.toNextTurn(); @@ -98,8 +98,8 @@ describe("Moves - Fairy Lock", () => { game.move.select(Moves.FAIRY_LOCK); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER)).toBeDefined(); @@ -108,9 +108,9 @@ describe("Moves - Fairy Lock", () => { await game.toNextTurn(); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WHIRLWIND, 0); + await game.move.selectEnemyMove(Moves.WHIRLWIND, 0); game.doSelectPartyPokemon(2); - await game.forceEnemyMove(Moves.WHIRLWIND, 1); + await game.move.selectEnemyMove(Moves.WHIRLWIND, 1); game.doSelectPartyPokemon(2); await game.phaseInterceptor.to("BerryPhase"); await game.toNextTurn(); @@ -126,8 +126,8 @@ describe("Moves - Fairy Lock", () => { game.move.select(Moves.FAIRY_LOCK); game.move.select(Moves.MEMENTO, 1); game.doSelectPartyPokemon(2); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER)).toBeDefined(); expect(game.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.ENEMY)).toBeDefined(); @@ -135,8 +135,8 @@ describe("Moves - Fairy Lock", () => { await game.toNextTurn(); game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); + await game.move.selectEnemyMove(Moves.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getPlayerField()[0].isTrapped()).toEqual(true); expect(game.scene.getPlayerField()[1].isTrapped()).toEqual(true); diff --git a/test/moves/fillet_away.test.ts b/test/moves/fillet_away.test.ts index 477cdf76fc7..9de237b28a1 100644 --- a/test/moves/fillet_away.test.ts +++ b/test/moves/fillet_away.test.ts @@ -39,7 +39,7 @@ describe("Moves - FILLET AWAY", () => { //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/fillet_away_(move) test("raises the user's ATK, SPATK, and SPD stat stages by 2 each, at the cost of 1/2 of its maximum HP", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); @@ -54,7 +54,7 @@ describe("Moves - FILLET AWAY", () => { }); test("still takes effect if one or more of the involved stat stages are not at max", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); @@ -73,7 +73,7 @@ describe("Moves - FILLET AWAY", () => { }); test("fails if all stat stages involved are at max", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; @@ -91,7 +91,7 @@ describe("Moves - FILLET AWAY", () => { }); test("fails if the user's health is less than 1/2", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO); diff --git a/test/moves/flame_burst.test.ts b/test/moves/flame_burst.test.ts index fb92537a238..963b0f04a9a 100644 --- a/test/moves/flame_burst.test.ts +++ b/test/moves/flame_burst.test.ts @@ -46,7 +46,7 @@ describe("Moves - Flame Burst", () => { }); it("inflicts damage to the target's ally equal to 1/16 of its max HP", async () => { - await game.startBattle([Species.PIKACHU, Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]); const [leftEnemy, rightEnemy] = game.scene.getEnemyField(); game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); @@ -60,7 +60,7 @@ describe("Moves - Flame Burst", () => { it("does not inflict damage to the target's ally if the target was not affected by Flame Burst", async () => { game.override.enemyAbility(Abilities.FLASH_FIRE); - await game.startBattle([Species.PIKACHU, Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]); const [leftEnemy, rightEnemy] = game.scene.getEnemyField(); game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); @@ -72,7 +72,7 @@ describe("Moves - Flame Burst", () => { }); it("does not interact with the target ally's abilities", async () => { - await game.startBattle([Species.PIKACHU, Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]); const [leftEnemy, rightEnemy] = game.scene.getEnemyField(); vi.spyOn(rightEnemy, "getAbility").mockReturnValue(allAbilities[Abilities.FLASH_FIRE]); @@ -86,7 +86,7 @@ describe("Moves - Flame Burst", () => { }); it("effect damage is prevented by Magic Guard", async () => { - await game.startBattle([Species.PIKACHU, Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]); const [leftEnemy, rightEnemy] = game.scene.getEnemyField(); vi.spyOn(rightEnemy, "getAbility").mockReturnValue(allAbilities[Abilities.MAGIC_GUARD]); diff --git a/test/moves/flower_shield.test.ts b/test/moves/flower_shield.test.ts index 4840c6f018f..d95ef6299b7 100644 --- a/test/moves/flower_shield.test.ts +++ b/test/moves/flower_shield.test.ts @@ -36,7 +36,7 @@ describe("Moves - Flower Shield", () => { it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - single battle", async () => { game.override.enemySpecies(Species.CHERRIM); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const cherrim = game.scene.getEnemyPokemon()!; const magikarp = game.scene.getPlayerPokemon()!; @@ -53,7 +53,7 @@ describe("Moves - Flower Shield", () => { it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - double battle", async () => { game.override.enemySpecies(Species.MAGIKARP).startingBiome(Biome.GRASS).battleStyle("double"); - await game.startBattle([Species.CHERRIM, Species.MAGIKARP]); + await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]); const field = game.scene.getField(true); const grassPokemons = field.filter(p => p.getTypes().includes(PokemonType.GRASS)); @@ -78,7 +78,7 @@ describe("Moves - Flower Shield", () => { game.override.enemyMoveset([Moves.DIG, Moves.DIG, Moves.DIG, Moves.DIG]); game.override.enemyLevel(50); - await game.startBattle([Species.CHERRIM]); + await game.classicMode.startBattle([Species.CHERRIM]); const paras = game.scene.getEnemyPokemon()!; const cherrim = game.scene.getPlayerPokemon()!; @@ -97,7 +97,7 @@ describe("Moves - Flower Shield", () => { it("does nothing if there are no Grass-type Pokemon on the field", async () => { game.override.enemySpecies(Species.MAGIKARP); - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const enemy = game.scene.getEnemyPokemon()!; const ally = game.scene.getPlayerPokemon()!; diff --git a/test/moves/fly.test.ts b/test/moves/fly.test.ts index 74f8bc4a770..81d04f53ff8 100644 --- a/test/moves/fly.test.ts +++ b/test/moves/fly.test.ts @@ -104,10 +104,10 @@ describe("Moves - Fly", () => { game.move.select(Moves.FLY); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); - await game.forceEnemyMove(Moves.GRAVITY); + await game.move.selectEnemyMove(Moves.GRAVITY); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/moves/follow_me.test.ts b/test/moves/follow_me.test.ts index 68c4f111bb1..228a835b17d 100644 --- a/test/moves/follow_me.test.ts +++ b/test/moves/follow_me.test.ts @@ -43,8 +43,8 @@ describe("Moves - Follow Me", () => { game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY); // Force both enemies to target the player Pokemon that did not use Follow Me - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to(TurnEndPhase, false); @@ -61,8 +61,8 @@ describe("Moves - Follow Me", () => { game.move.select(Moves.FOLLOW_ME, 1); // Each player is targeted by an enemy - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to(TurnEndPhase, false); @@ -84,8 +84,8 @@ describe("Moves - Follow Me", () => { game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2); // Target doesn't need to be specified if the move is self-targeted - await game.forceEnemyMove(Moves.FOLLOW_ME); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.FOLLOW_ME); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase, false); @@ -104,8 +104,8 @@ describe("Moves - Follow Me", () => { game.move.select(Moves.SNIPE_SHOT, 0, BattlerIndex.ENEMY); game.move.select(Moves.SNIPE_SHOT, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.FOLLOW_ME); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.FOLLOW_ME); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase, false); diff --git a/test/moves/foresight.test.ts b/test/moves/foresight.test.ts index d33a00bf136..82a39eceae5 100644 --- a/test/moves/foresight.test.ts +++ b/test/moves/foresight.test.ts @@ -31,7 +31,7 @@ describe("Moves - Foresight", () => { }); it("should allow Normal and Fighting moves to hit Ghost types", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const enemy = game.scene.getEnemyPokemon()!; @@ -55,7 +55,7 @@ describe("Moves - Foresight", () => { it("should ignore target's evasiveness boosts", async () => { game.override.enemyMoveset([Moves.MINIMIZE]); - await game.startBattle(); + await game.classicMode.startBattle(); const pokemon = game.scene.getPlayerPokemon()!; vi.spyOn(pokemon, "getAccuracyMultiplier"); diff --git a/test/moves/fusion_bolt.test.ts b/test/moves/fusion_bolt.test.ts index 33498a857a9..5e515a3bc47 100644 --- a/test/moves/fusion_bolt.test.ts +++ b/test/moves/fusion_bolt.test.ts @@ -36,7 +36,7 @@ describe("Moves - Fusion Bolt", () => { }); it("should not make contact", async () => { - await game.startBattle([Species.ZEKROM]); + await game.classicMode.startBattle([Species.ZEKROM]); const partyMember = game.scene.getPlayerPokemon()!; const initialHp = partyMember.hp; diff --git a/test/moves/fusion_flare.test.ts b/test/moves/fusion_flare.test.ts index 61bb126a75a..b947b9e7f7e 100644 --- a/test/moves/fusion_flare.test.ts +++ b/test/moves/fusion_flare.test.ts @@ -36,7 +36,7 @@ describe("Moves - Fusion Flare", () => { }); it("should thaw freeze status condition", async () => { - await game.startBattle([Species.RESHIRAM]); + await game.classicMode.startBattle([Species.RESHIRAM]); const partyMember = game.scene.getPlayerPokemon()!; diff --git a/test/moves/fusion_flare_bolt.test.ts b/test/moves/fusion_flare_bolt.test.ts index da2d48a7cdb..1d248e09510 100644 --- a/test/moves/fusion_flare_bolt.test.ts +++ b/test/moves/fusion_flare_bolt.test.ts @@ -138,7 +138,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { }, 20000); it("FUSION_FLARE should double power of subsequent FUSION_BOLT if moves are aimed at allies", async () => { - await game.startBattle([Species.ZEKROM, Species.RESHIRAM]); + await game.classicMode.startBattle([Species.ZEKROM, Species.RESHIRAM]); game.move.select(fusionBolt.id, 0, BattlerIndex.PLAYER_2); game.move.select(fusionFlare.id, 1, BattlerIndex.PLAYER); diff --git a/test/moves/growth.test.ts b/test/moves/growth.test.ts index 37cd84638ba..2e3ebfcde52 100644 --- a/test/moves/growth.test.ts +++ b/test/moves/growth.test.ts @@ -32,7 +32,7 @@ describe("Moves - Growth", () => { }); it("should raise SPATK stat stage by 1", async () => { - await game.startBattle([Species.MIGHTYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA]); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/moves/grudge.test.ts b/test/moves/grudge.test.ts index ecde5351d6d..48bb79c5f02 100644 --- a/test/moves/grudge.test.ts +++ b/test/moves/grudge.test.ts @@ -37,7 +37,7 @@ describe("Moves - Grudge", () => { const playerPokemon = game.scene.getPlayerPokemon(); game.move.select(Moves.EMBER); - await game.forceEnemyMove(Moves.GRUDGE); + await game.move.selectEnemyMove(Moves.GRUDGE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); @@ -51,12 +51,12 @@ describe("Moves - Grudge", () => { const playerPokemon = game.scene.getPlayerPokemon(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.GRUDGE); + await game.move.selectEnemyMove(Moves.GRUDGE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); game.move.select(Moves.EMBER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("BerryPhase"); @@ -78,7 +78,7 @@ describe("Moves - Grudge", () => { const playerPokemon = game.scene.getPlayerPokemon(); game.move.select(Moves.FALSE_SWIPE); - await game.forceEnemyMove(Moves.GRUDGE); + await game.move.selectEnemyMove(Moves.GRUDGE); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase"); diff --git a/test/moves/guard_split.test.ts b/test/moves/guard_split.test.ts index d182e94b203..0f5c69c7d85 100644 --- a/test/moves/guard_split.test.ts +++ b/test/moves/guard_split.test.ts @@ -34,7 +34,7 @@ describe("Moves - Guard Split", () => { it("should average the user's DEF and SPDEF stats with those of the target", async () => { game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.INDEEDEE]); + await game.classicMode.startBattle([Species.INDEEDEE]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -54,7 +54,7 @@ describe("Moves - Guard Split", () => { it("should be idempotent", async () => { game.override.enemyMoveset([Moves.GUARD_SPLIT]); - await game.startBattle([Species.INDEEDEE]); + await game.classicMode.startBattle([Species.INDEEDEE]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/moves/hard_press.test.ts b/test/moves/hard_press.test.ts index e1a01c0109d..12cf049a022 100644 --- a/test/moves/hard_press.test.ts +++ b/test/moves/hard_press.test.ts @@ -37,7 +37,7 @@ describe("Moves - Hard Press", () => { }); it("should return 100 power if target HP ratio is at 100%", async () => { - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); game.move.select(Moves.HARD_PRESS); await game.phaseInterceptor.to(MoveEffectPhase); @@ -46,7 +46,7 @@ describe("Moves - Hard Press", () => { }); it("should return 50 power if target HP ratio is at 50%", async () => { - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); const targetHpRatio = 0.5; const enemy = game.scene.getEnemyPokemon()!; @@ -59,7 +59,7 @@ describe("Moves - Hard Press", () => { }); it("should return 1 power if target HP ratio is at 1%", async () => { - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); const targetHpRatio = 0.01; const enemy = game.scene.getEnemyPokemon()!; @@ -72,7 +72,7 @@ describe("Moves - Hard Press", () => { }); it("should return 1 power if target HP ratio is less than 1%", async () => { - await game.startBattle([Species.PIKACHU]); + await game.classicMode.startBattle([Species.PIKACHU]); const targetHpRatio = 0.005; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/moves/haze.test.ts b/test/moves/haze.test.ts index 4ddb6d1c7c5..33d4fe2dfda 100644 --- a/test/moves/haze.test.ts +++ b/test/moves/haze.test.ts @@ -36,7 +36,7 @@ describe("Moves - Haze", () => { }); it("should reset all stat changes of all Pokemon on field", async () => { - await game.startBattle([Species.RATTATA]); + await game.classicMode.startBattle([Species.RATTATA]); const user = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/moves/hyper_beam.test.ts b/test/moves/hyper_beam.test.ts index e6b3955ef0d..a4cbf7d9245 100644 --- a/test/moves/hyper_beam.test.ts +++ b/test/moves/hyper_beam.test.ts @@ -38,7 +38,7 @@ describe("Moves - Hyper Beam", () => { }); it("should force the user to recharge on the next turn (and only that turn)", async () => { - await game.startBattle([Species.MAGIKARP]); + await game.classicMode.startBattle([Species.MAGIKARP]); const leadPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/imprison.test.ts b/test/moves/imprison.test.ts index cefbaa52cad..dfa3b3ad757 100644 --- a/test/moves/imprison.test.ts +++ b/test/moves/imprison.test.ts @@ -36,7 +36,7 @@ describe("Moves - Imprison", () => { const playerPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.TRANSFORM); - await game.forceEnemyMove(Moves.IMPRISON); + await game.move.selectEnemyMove(Moves.IMPRISON); await game.toNextTurn(); const playerMoveset = playerPokemon.getMoveset().map(x => x?.moveId); const enemyMoveset = game.scene @@ -51,7 +51,7 @@ describe("Moves - Imprison", () => { // Second turn, Imprison forces Struggle to occur game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.STRUGGLE); @@ -63,7 +63,7 @@ describe("Moves - Imprison", () => { const playerPokemon1 = game.scene.getPlayerPokemon()!; game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.IMPRISON); + await game.move.selectEnemyMove(Moves.IMPRISON); await game.toNextTurn(); const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON); const imprisonBattlerTag1 = playerPokemon1.getTag(BattlerTagType.IMPRISON); @@ -72,7 +72,7 @@ describe("Moves - Imprison", () => { // Second turn, Imprison forces Struggle to occur game.doSwitchPokemon(1); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const playerPokemon2 = game.scene.getPlayerPokemon()!; const imprisonBattlerTag2 = playerPokemon2.getTag(BattlerTagType.IMPRISON); @@ -87,12 +87,12 @@ describe("Moves - Imprison", () => { const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; game.move.select(Moves.IMPRISON); - await game.forceEnemyMove(Moves.GROWL); + await game.move.selectEnemyMove(Moves.GROWL); await game.toNextTurn(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined(); expect(enemyPokemon.getTag(BattlerTagType.IMPRISON)).toBeDefined(); game.doSwitchPokemon(1); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(playerPokemon.isActive(true)).toBeFalsy(); expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeUndefined(); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index dd25db4ec90..719349760dc 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -48,7 +48,7 @@ describe("Moves - Instruct", () => { game.move.changeMoveset(enemy, Moves.SONIC_BOOM); game.move.select(Moves.INSTRUCT); - await game.forceEnemyMove(Moves.SONIC_BOOM); + await game.move.selectEnemyMove(Moves.SONIC_BOOM); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MovePhase"); // enemy attacks us @@ -75,12 +75,12 @@ describe("Moves - Instruct", () => { game.move.changeMoveset(enemy, [Moves.SONIC_BOOM, Moves.SUBSTITUTE]); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SUBSTITUTE); + await game.move.selectEnemyMove(Moves.SUBSTITUTE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); game.move.select(Moves.INSTRUCT); - await game.forceEnemyMove(Moves.SONIC_BOOM); + await game.move.selectEnemyMove(Moves.SONIC_BOOM); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase", false); @@ -169,7 +169,7 @@ describe("Moves - Instruct", () => { moveUsed.ppUsed = moveUsed.getMovePp() - 1; game.move.select(Moves.INSTRUCT); - await game.forceEnemyMove(Moves.HIDDEN_POWER); + await game.move.selectEnemyMove(Moves.HIDDEN_POWER); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase", false); @@ -210,8 +210,8 @@ describe("Moves - Instruct", () => { game.move.select(Moves.SPLASH, BattlerIndex.PLAYER); game.move.select(Moves.FIERY_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); - await game.forceEnemyMove(Moves.INSTRUCT, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.INSTRUCT, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); @@ -248,7 +248,7 @@ describe("Moves - Instruct", () => { await game.classicMode.startBattle([Species.AMOONGUSS]); game.move.select(Moves.INSTRUCT); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("TurnEndPhase", false); @@ -265,7 +265,7 @@ describe("Moves - Instruct", () => { game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(Moves.DISABLE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); - await game.forceEnemyMove(Moves.SONIC_BOOM, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SONIC_BOOM, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("TurnEndPhase", false); @@ -311,7 +311,7 @@ describe("Moves - Instruct", () => { ]; game.move.select(Moves.INSTRUCT); - await game.forceEnemyMove(Moves.HYPER_BEAM); + await game.move.selectEnemyMove(Moves.HYPER_BEAM); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toNextTurn(); @@ -326,23 +326,21 @@ describe("Moves - Instruct", () => { }); it("should not repeat move since forgotten by target", async () => { - game.override.enemyLevel(5).xpMultiplier(0).enemySpecies(Species.WURMPLE).enemyMoveset(Moves.INSTRUCT); + game.override.enemyMoveset(Moves.INSTRUCT); await game.classicMode.startBattle([Species.REGIELEKI]); const regieleki = game.scene.getPlayerPokemon()!; - // fill out moveset with random moves - game.move.changeMoveset(regieleki, [Moves.ELECTRO_DRIFT, Moves.SPLASH, Moves.ICE_BEAM, Moves.ANCIENT_POWER]); - - game.move.select(Moves.ELECTRO_DRIFT); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("FaintPhase"); - await game.move.learnMove(Moves.ELECTROWEB); - await game.toNextWave(); + regieleki.pushMoveHistory({ + move: Moves.ELECTRO_DRIFT, + targets: [BattlerIndex.PLAYER], + result: MoveResult.SUCCESS, + virtual: false, + }); game.move.select(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("TurnEndPhase", false); - expect(game.scene.getEnemyField()[0].getLastXMoves()[0].result).toBe(MoveResult.FAIL); + await game.toEndOfTurn(); + expect(game.field.getEnemyPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); }); it("should disregard priority of instructed move on use", async () => { @@ -360,7 +358,7 @@ describe("Moves - Instruct", () => { ]; game.move.select(Moves.INSTRUCT); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase", false); // lucario instructed enemy whirlwind at 0 priority to switch itself out @@ -379,8 +377,8 @@ describe("Moves - Instruct", () => { game.move.select(Moves.QUICK_ATTACK, BattlerIndex.PLAYER, BattlerIndex.ENEMY); // succeeds due to terrain no game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.PSYCHIC_TERRAIN); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.PSYCHIC_TERRAIN); await game.toNextTurn(); game.move.select(Moves.SPLASH, BattlerIndex.PLAYER); @@ -404,8 +402,8 @@ describe("Moves - Instruct", () => { game.move.select(Moves.VINE_WHIP, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.PSYCHIC_TERRAIN); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.PSYCHIC_TERRAIN); await game.toNextTurn(); game.move.select(Moves.SPLASH, BattlerIndex.PLAYER); @@ -515,14 +513,14 @@ describe("Moves - Instruct", () => { game.move.select(Moves.SPLASH, BattlerIndex.PLAYER); game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); - await game.forceEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); @@ -531,8 +529,8 @@ describe("Moves - Instruct", () => { await game.toNextTurn(); game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); - await game.forceEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2]); await game.phaseInterceptor.to("BerryPhase"); diff --git a/test/moves/jaw_lock.test.ts b/test/moves/jaw_lock.test.ts index 71896dc3b62..85750b6efda 100644 --- a/test/moves/jaw_lock.test.ts +++ b/test/moves/jaw_lock.test.ts @@ -40,7 +40,7 @@ describe("Moves - Jaw Lock", () => { }); it("should trap the move's user and target", async () => { - await game.startBattle([Species.BULBASAUR]); + await game.classicMode.startBattle([Species.BULBASAUR]); const leadPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -61,7 +61,7 @@ describe("Moves - Jaw Lock", () => { it("should not trap either pokemon if the target faints", async () => { game.override.enemyLevel(1); - await game.startBattle([Species.BULBASAUR]); + await game.classicMode.startBattle([Species.BULBASAUR]); const leadPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -86,7 +86,7 @@ describe("Moves - Jaw Lock", () => { }); it("should only trap the user until the target faints", async () => { - await game.startBattle([Species.BULBASAUR]); + await game.classicMode.startBattle([Species.BULBASAUR]); const leadPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -109,7 +109,7 @@ describe("Moves - Jaw Lock", () => { it("should not trap other targets after the first target is trapped", async () => { game.override.battleStyle("double"); - await game.startBattle([Species.CHARMANDER, Species.BULBASAUR]); + await game.classicMode.startBattle([Species.CHARMANDER, Species.BULBASAUR]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -138,7 +138,7 @@ describe("Moves - Jaw Lock", () => { it("should not trap either pokemon if the target is protected", async () => { game.override.enemyMoveset([Moves.PROTECT]); - await game.startBattle([Species.BULBASAUR]); + await game.classicMode.startBattle([Species.BULBASAUR]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/last-resort.test.ts b/test/moves/last-resort.test.ts index a7b462f3ca4..65b50bc4e89 100644 --- a/test/moves/last-resort.test.ts +++ b/test/moves/last-resort.test.ts @@ -86,11 +86,11 @@ describe("Moves - Last Resort", () => { // use mirror move normally to trigger absorb virtually game.move.select(Moves.MIRROR_MOVE); - await game.forceEnemyMove(Moves.ABSORB); + await game.move.selectEnemyMove(Moves.ABSORB); await game.toNextTurn(); game.move.select(Moves.LAST_RESORT); - await game.forceEnemyMove(Moves.SWORDS_DANCE); // goes first to proc dancer ahead of time + await game.move.selectEnemyMove(Moves.SWORDS_DANCE); // goes first to proc dancer ahead of time await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase"); expectLastResortFail(); @@ -154,10 +154,10 @@ describe("Moves - Last Resort", () => { // ensure enemy last resort succeeds game.move.select(Moves.MIRROR_MOVE); - await game.forceEnemyMove(Moves.ABSORB); + await game.move.selectEnemyMove(Moves.ABSORB); await game.phaseInterceptor.to("TurnEndPhase"); game.move.select(Moves.MIRROR_MOVE); - await game.forceEnemyMove(Moves.LAST_RESORT); + await game.move.selectEnemyMove(Moves.LAST_RESORT); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/moves/lucky_chant.test.ts b/test/moves/lucky_chant.test.ts index e2a28a7bbe3..9fa6ff43eab 100644 --- a/test/moves/lucky_chant.test.ts +++ b/test/moves/lucky_chant.test.ts @@ -35,7 +35,7 @@ describe("Moves - Lucky Chant", () => { }); it("should prevent critical hits from moves", async () => { - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -56,7 +56,7 @@ describe("Moves - Lucky Chant", () => { it("should prevent critical hits against the user's ally", async () => { game.override.battleStyle("double"); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerField(); @@ -79,7 +79,7 @@ describe("Moves - Lucky Chant", () => { it("should prevent critical hits from field effects", async () => { game.override.enemyMoveset([Moves.TACKLE]); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/lunar_blessing.test.ts b/test/moves/lunar_blessing.test.ts index ee35107fccd..5021a47ff7f 100644 --- a/test/moves/lunar_blessing.test.ts +++ b/test/moves/lunar_blessing.test.ts @@ -33,7 +33,7 @@ describe("Moves - Lunar Blessing", () => { }); it("should restore 25% HP of the user and its ally", async () => { - await game.startBattle([Species.RATTATA, Species.RATTATA]); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA]); const [leftPlayer, rightPlayer] = game.scene.getPlayerField(); vi.spyOn(leftPlayer, "getMaxHp").mockReturnValue(100); @@ -61,7 +61,7 @@ describe("Moves - Lunar Blessing", () => { it("should cure status effect of the user and its ally", async () => { game.override.statusEffect(StatusEffect.BURN); - await game.startBattle([Species.RATTATA, Species.RATTATA]); + await game.classicMode.startBattle([Species.RATTATA, Species.RATTATA]); const [leftPlayer, rightPlayer] = game.scene.getPlayerField(); vi.spyOn(leftPlayer, "resetStatus"); diff --git a/test/moves/magic_coat.test.ts b/test/moves/magic_coat.test.ts index 4e0bd7f0a98..ad0bd47d7cf 100644 --- a/test/moves/magic_coat.test.ts +++ b/test/moves/magic_coat.test.ts @@ -62,12 +62,12 @@ describe("Moves - Magic Coat", () => { // turn 1 game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.MAGIC_COAT); + await game.move.selectEnemyMove(Moves.MAGIC_COAT); await game.toNextTurn(); // turn 2 game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1); }); @@ -102,8 +102,8 @@ describe("Moves - Magic Coat", () => { game.move.select(Moves.GROWL, 0); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.MAGIC_COAT); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.MAGIC_COAT); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getPlayerField().every(p => p.getStatStage(Stat.ATK) === -1)).toBeTruthy(); @@ -129,8 +129,8 @@ describe("Moves - Magic Coat", () => { game.move.select(Moves.MAGIC_COAT, 0); game.move.select(Moves.GROWL, 1); - await game.forceEnemyMove(Moves.MAGIC_COAT); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.MAGIC_COAT); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.getEnemyField()[0].getStatStage(Stat.ATK)).toBe(0); @@ -192,12 +192,12 @@ describe("Moves - Magic Coat", () => { // turn 1 game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.MAGIC_COAT); + await game.move.selectEnemyMove(Moves.MAGIC_COAT); await game.toNextTurn(); // turn 2 game.move.select(Moves.ENCORE); - await game.forceEnemyMove(Moves.TACKLE); + await game.move.selectEnemyMove(Moves.TACKLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("BerryPhase"); expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(Moves.TACKLE); @@ -233,7 +233,7 @@ describe("Moves - Magic Coat", () => { vi.spyOn(stomping_tantrum, "calculateBattlePower"); game.move.select(Moves.SPORE); - await game.forceEnemyMove(Moves.CHARM); + await game.move.selectEnemyMove(Moves.CHARM); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemy.getLastXMoves(1)[0].result).toBe("success"); diff --git a/test/moves/magnet_rise.test.ts b/test/moves/magnet_rise.test.ts index 62ad0c88091..3f15a109f11 100644 --- a/test/moves/magnet_rise.test.ts +++ b/test/moves/magnet_rise.test.ts @@ -33,7 +33,7 @@ describe("Moves - Magnet Rise", () => { }); it("MAGNET RISE", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const startingHp = game.scene.getPlayerParty()[0].hp; game.move.select(moveToUse); @@ -44,7 +44,7 @@ describe("Moves - Magnet Rise", () => { }, 20000); it("MAGNET RISE - Gravity", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const startingHp = game.scene.getPlayerParty()[0].hp; game.move.select(moveToUse); diff --git a/test/moves/make_it_rain.test.ts b/test/moves/make_it_rain.test.ts index 4d94537bcec..b897304662d 100644 --- a/test/moves/make_it_rain.test.ts +++ b/test/moves/make_it_rain.test.ts @@ -34,7 +34,7 @@ describe("Moves - Make It Rain", () => { }); it("should only lower SPATK stat stage by 1 once in a double battle", async () => { - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -50,7 +50,7 @@ describe("Moves - Make It Rain", () => { game.override.enemyLevel(1); // ensures the enemy will faint game.override.battleStyle("single"); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -66,7 +66,7 @@ describe("Moves - Make It Rain", () => { it("should reduce Sp. Atk. once after KOing two enemies", async () => { game.override.enemyLevel(1); // ensures the enemy will faint - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyField(); @@ -81,7 +81,7 @@ describe("Moves - Make It Rain", () => { }); it("should lower SPATK stat stage by 1 if it only hits the second target", async () => { - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const playerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/moves/mat_block.test.ts b/test/moves/mat_block.test.ts index 9ed0f497af9..3e7958d665b 100644 --- a/test/moves/mat_block.test.ts +++ b/test/moves/mat_block.test.ts @@ -39,7 +39,7 @@ describe("Moves - Mat Block", () => { }); test("should protect the user and allies from attack moves", async () => { - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -57,7 +57,7 @@ describe("Moves - Mat Block", () => { test("should not protect the user and allies from status moves", async () => { game.override.enemyMoveset([Moves.GROWL]); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -73,7 +73,7 @@ describe("Moves - Mat Block", () => { }); test("should fail when used after the first turn", async () => { - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); const leadPokemon = game.scene.getPlayerField(); diff --git a/test/moves/metal_burst.test.ts b/test/moves/metal_burst.test.ts index 7fa5434dc58..cadcbe07a0d 100644 --- a/test/moves/metal_burst.test.ts +++ b/test/moves/metal_burst.test.ts @@ -42,8 +42,8 @@ describe("Moves - Metal Burst", () => { game.move.select(Moves.METAL_BURST); game.move.select(Moves.FISSURE, 1, BattlerIndex.ENEMY); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); @@ -63,8 +63,8 @@ describe("Moves - Metal Burst", () => { game.move.select(Moves.METAL_BURST); game.move.select(Moves.PRECIPICE_BLADES, 1); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]); diff --git a/test/moves/mirror_move.test.ts b/test/moves/mirror_move.test.ts index 438c594d839..6a73e3a1f05 100644 --- a/test/moves/mirror_move.test.ts +++ b/test/moves/mirror_move.test.ts @@ -40,8 +40,8 @@ describe("Moves - Mirror Move", () => { game.move.select(Moves.MIRROR_MOVE, 0, BattlerIndex.ENEMY); // target's last move is Tackle, enemy should receive damage from Mirror Move copying Tackle game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.GROWL, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.GROWL, BattlerIndex.PLAYER_2); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER]); await game.toNextTurn(); diff --git a/test/moves/multi_target.test.ts b/test/moves/multi_target.test.ts index ad47d540a14..af98c9a89f4 100644 --- a/test/moves/multi_target.test.ts +++ b/test/moves/multi_target.test.ts @@ -36,7 +36,7 @@ describe("Multi-target damage reduction", () => { }); it("should reduce d.gleam damage when multiple enemies but not tackle", async () => { - await game.startBattle([Species.MAGIKARP, Species.FEEBAS]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const [enemy1, enemy2] = game.scene.getEnemyField(); @@ -76,7 +76,7 @@ describe("Multi-target damage reduction", () => { }); it("should reduce earthquake when more than one pokemon other than user is not fainted", async () => { - await game.startBattle([Species.MAGIKARP, Species.FEEBAS]); + await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]); const player2 = game.scene.getPlayerParty()[1]; const [enemy1, enemy2] = game.scene.getEnemyField(); diff --git a/test/moves/parting_shot.test.ts b/test/moves/parting_shot.test.ts index a65c1a5b3a5..26f8790d3b9 100644 --- a/test/moves/parting_shot.test.ts +++ b/test/moves/parting_shot.test.ts @@ -35,7 +35,7 @@ describe("Moves - Parting Shot", () => { test("Parting Shot when buffed by prankster should fail against dark types", async () => { game.override.enemySpecies(Species.POOCHYENA).ability(Abilities.PRANKSTER); - await game.startBattle([Species.MURKROW, Species.MEOWTH]); + await game.classicMode.startBattle([Species.MURKROW, Species.MEOWTH]); const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon).toBeDefined(); @@ -50,7 +50,7 @@ describe("Moves - Parting Shot", () => { test("Parting shot should fail against good as gold ability", async () => { game.override.enemySpecies(Species.GHOLDENGO).enemyAbility(Abilities.GOOD_AS_GOLD); - await game.startBattle([Species.MURKROW, Species.MEOWTH]); + await game.classicMode.startBattle([Species.MURKROW, Species.MEOWTH]); const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon).toBeDefined(); @@ -68,7 +68,13 @@ describe("Moves - Parting Shot", () => { "Parting shot should fail if target is -6/-6 de-buffed", async () => { game.override.moveset([Moves.PARTING_SHOT, Moves.MEMENTO, Moves.SPLASH]); - await game.startBattle([Species.MEOWTH, Species.MEOWTH, Species.MEOWTH, Species.MURKROW, Species.ABRA]); + await game.classicMode.startBattle([ + Species.MEOWTH, + Species.MEOWTH, + Species.MEOWTH, + Species.MURKROW, + Species.ABRA, + ]); // use Memento 3 times to debuff enemy game.move.select(Moves.MEMENTO); @@ -111,7 +117,7 @@ describe("Moves - Parting Shot", () => { "Parting shot shouldn't allow switch out when mist is active", async () => { game.override.enemySpecies(Species.ALTARIA).enemyAbility(Abilities.NONE).enemyMoveset([Moves.MIST]); - await game.startBattle([Species.SNORLAX, Species.MEOWTH]); + await game.classicMode.startBattle([Species.SNORLAX, Species.MEOWTH]); const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon).toBeDefined(); @@ -130,7 +136,7 @@ describe("Moves - Parting Shot", () => { "Parting shot shouldn't allow switch out against clear body ability", async () => { game.override.enemySpecies(Species.TENTACOOL).enemyAbility(Abilities.CLEAR_BODY); - await game.startBattle([Species.SNORLAX, Species.MEOWTH]); + await game.classicMode.startBattle([Species.SNORLAX, Species.MEOWTH]); const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon).toBeDefined(); @@ -148,7 +154,7 @@ describe("Moves - Parting Shot", () => { // TODO: fix this bug to pass the test! "Parting shot should de-buff and not fail if no party available to switch - party size 1", async () => { - await game.startBattle([Species.MURKROW]); + await game.classicMode.startBattle([Species.MURKROW]); const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon).toBeDefined(); @@ -166,7 +172,7 @@ describe("Moves - Parting Shot", () => { // TODO: fix this bug to pass the test! "Parting shot regularly not fail if no party available to switch - party fainted", async () => { - await game.startBattle([Species.MURKROW, Species.MEOWTH]); + await game.classicMode.startBattle([Species.MURKROW, Species.MEOWTH]); game.move.select(Moves.SPLASH); // intentionally kill party pokemon, switch to second slot (now 1 party mon is fainted) diff --git a/test/moves/plasma_fists.test.ts b/test/moves/plasma_fists.test.ts index b6a5ceaed68..0c3412d8e60 100644 --- a/test/moves/plasma_fists.test.ts +++ b/test/moves/plasma_fists.test.ts @@ -42,8 +42,8 @@ describe("Moves - Plasma Fists", () => { game.move.select(Moves.PLASMA_FISTS, 0, BattlerIndex.ENEMY); game.move.select(Moves.TACKLE, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); diff --git a/test/moves/pledge_moves.test.ts b/test/moves/pledge_moves.test.ts index 9dbf2b4cb02..6c66c4bbe2d 100644 --- a/test/moves/pledge_moves.test.ts +++ b/test/moves/pledge_moves.test.ts @@ -256,8 +256,8 @@ describe("Moves - Pledge Moves", () => { game.move.select(Moves.FIRE_PLEDGE, 0, BattlerIndex.ENEMY); game.move.select(Moves.GRASS_PLEDGE, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.SPORE, BattlerIndex.PLAYER_2); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPORE, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2]); @@ -290,8 +290,8 @@ describe("Moves - Pledge Moves", () => { game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.FOLLOW_ME); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.FOLLOW_ME); await game.phaseInterceptor.to("BerryPhase", false); diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index c6114db3ff1..b65525109ad 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -233,8 +233,8 @@ describe("Moves - Powder", () => { game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.GRASS_PLEDGE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.GRASS_PLEDGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(BerryPhase, false); @@ -250,8 +250,8 @@ describe("Moves - Powder", () => { game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to(BerryPhase, false); @@ -267,8 +267,8 @@ describe("Moves - Powder", () => { game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY]); await game.phaseInterceptor.to(BerryPhase, false); diff --git a/test/moves/power_split.test.ts b/test/moves/power_split.test.ts index f15275fce9e..ca712f0a64e 100644 --- a/test/moves/power_split.test.ts +++ b/test/moves/power_split.test.ts @@ -34,7 +34,7 @@ describe("Moves - Power Split", () => { it("should average the user's ATK and SPATK stats with those of the target", async () => { game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.INDEEDEE]); + await game.classicMode.startBattle([Species.INDEEDEE]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; @@ -54,7 +54,7 @@ describe("Moves - Power Split", () => { it("should be idempotent", async () => { game.override.enemyMoveset([Moves.POWER_SPLIT]); - await game.startBattle([Species.INDEEDEE]); + await game.classicMode.startBattle([Species.INDEEDEE]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/moves/purify.test.ts b/test/moves/purify.test.ts index 191539d8cec..0439ba39108 100644 --- a/test/moves/purify.test.ts +++ b/test/moves/purify.test.ts @@ -37,7 +37,7 @@ describe("Moves - Purify", () => { }); test("Purify heals opponent status effect and restores user hp", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyPokemon()!; const playerPokemon: PlayerPokemon = game.scene.getPlayerPokemon()!; @@ -54,7 +54,7 @@ describe("Moves - Purify", () => { }); test("Purify does not heal if opponent doesnt have any status effect", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); const playerPokemon: PlayerPokemon = game.scene.getPlayerPokemon()!; diff --git a/test/moves/quash.test.ts b/test/moves/quash.test.ts index 5bf8271320b..f242dafe365 100644 --- a/test/moves/quash.test.ts +++ b/test/moves/quash.test.ts @@ -39,8 +39,8 @@ describe("Moves - Quash", () => { game.move.select(Moves.QUASH, 0, BattlerIndex.PLAYER_2); game.move.select(Moves.SUNNY_DAY, 1); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.RAIN_DANCE); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.RAIN_DANCE); await game.phaseInterceptor.to("TurnEndPhase", false); // will be sunny if player_2 moved last because of quash, rainy otherwise @@ -67,8 +67,8 @@ describe("Moves - Quash", () => { game.move.select(Moves.RAIN_DANCE, 0); game.move.select(Moves.SUNNY_DAY, 1); - await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.QUASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to("TurnEndPhase", false); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SUNNY); @@ -81,15 +81,15 @@ describe("Moves - Quash", () => { game.move.select(Moves.SPLASH, 0); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.TRICK_ROOM); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.TRICK_ROOM); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnInitPhase"); // both users are quashed - accelgor should move last w/ TR so rain should be up at end of turn game.move.select(Moves.RAIN_DANCE, 0); game.move.select(Moves.SUNNY_DAY, 1); - await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2); + await game.move.selectEnemyMove(Moves.QUASH, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.QUASH, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to("TurnEndPhase", false); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.RAIN); diff --git a/test/moves/rage_fist.test.ts b/test/moves/rage_fist.test.ts index 0aabb717f1b..9e5810039a8 100644 --- a/test/moves/rage_fist.test.ts +++ b/test/moves/rage_fist.test.ts @@ -75,7 +75,7 @@ describe("Moves - Rage Fist", () => { await game.classicMode.startBattle([Species.REGIROCK]); game.move.select(Moves.SUBSTITUTE); - await game.forceEnemyMove(Moves.DOUBLE_KICK); + await game.move.selectEnemyMove(Moves.DOUBLE_KICK); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); @@ -84,12 +84,12 @@ describe("Moves - Rage Fist", () => { // remove substitute and get confused game.move.select(Moves.TIDY_UP); - await game.forceEnemyMove(Moves.CONFUSE_RAY); + await game.move.selectEnemyMove(Moves.CONFUSE_RAY); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.toNextTurn(); game.move.select(Moves.RAGE_FIST); - await game.forceEnemyMove(Moves.CONFUSE_RAY); + await game.move.selectEnemyMove(Moves.CONFUSE_RAY); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.move.forceConfusionActivation(true); await game.toNextTurn(); @@ -123,7 +123,7 @@ describe("Moves - Rage Fist", () => { // beat up a magikarp game.move.select(Moves.RAGE_FIST); - await game.forceEnemyMove(Moves.DOUBLE_KICK); + await game.move.selectEnemyMove(Moves.DOUBLE_KICK); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("TurnEndPhase"); diff --git a/test/moves/rage_powder.test.ts b/test/moves/rage_powder.test.ts index 284b558f842..206fd590896 100644 --- a/test/moves/rage_powder.test.ts +++ b/test/moves/rage_powder.test.ts @@ -38,8 +38,8 @@ describe("Moves - Rage Powder", () => { game.move.select(Moves.QUICK_ATTACK, 0, BattlerIndex.ENEMY); game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.RAGE_POWDER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.RAGE_POWDER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("BerryPhase", false); @@ -61,8 +61,8 @@ describe("Moves - Rage Powder", () => { game.move.select(Moves.QUICK_ATTACK, 0, BattlerIndex.ENEMY); game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.RAGE_POWDER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.RAGE_POWDER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("BerryPhase", false); diff --git a/test/moves/reflect_type.test.ts b/test/moves/reflect_type.test.ts index efd58bfeadf..63772aa746f 100644 --- a/test/moves/reflect_type.test.ts +++ b/test/moves/reflect_type.test.ts @@ -37,17 +37,17 @@ describe("Moves - Reflect Type", () => { const enemyPokemon = game.scene.getEnemyPokemon(); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.BURN_UP); + await game.move.selectEnemyMove(Moves.BURN_UP); await game.toNextTurn(); game.move.select(Moves.FORESTS_CURSE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); expect(enemyPokemon?.getTypes().includes(PokemonType.UNKNOWN)).toBe(true); expect(enemyPokemon?.getTypes().includes(PokemonType.GRASS)).toBe(true); game.move.select(Moves.REFLECT_TYPE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); expect(playerPokemon?.getTypes()[0]).toBe(PokemonType.NORMAL); expect(playerPokemon?.getTypes().includes(PokemonType.GRASS)).toBe(true); diff --git a/test/moves/retaliate.test.ts b/test/moves/retaliate.test.ts index 81ea353a120..40f31635d9b 100644 --- a/test/moves/retaliate.test.ts +++ b/test/moves/retaliate.test.ts @@ -37,7 +37,7 @@ describe("Moves - Retaliate", () => { it("increases power if ally died previous turn", async () => { vi.spyOn(retaliate, "calculateBattlePower"); - await game.startBattle([Species.ABRA, Species.COBALION]); + await game.classicMode.startBattle([Species.ABRA, Species.COBALION]); game.move.select(Moves.RETALIATE); await game.phaseInterceptor.to("TurnEndPhase"); expect(retaliate.calculateBattlePower).toHaveLastReturnedWith(70); diff --git a/test/moves/revival_blessing.test.ts b/test/moves/revival_blessing.test.ts index b36cd43eb83..ded18e3ffb8 100644 --- a/test/moves/revival_blessing.test.ts +++ b/test/moves/revival_blessing.test.ts @@ -98,8 +98,8 @@ describe("Moves - Revival Blessing", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.REVIVAL_BLESSING, 1); - await game.forceEnemyMove(Moves.FISSURE, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.FISSURE, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2]); await game.phaseInterceptor.to("MoveEndPhase"); diff --git a/test/moves/rollout.test.ts b/test/moves/rollout.test.ts index dab9ef67596..dafa72dd5fa 100644 --- a/test/moves/rollout.test.ts +++ b/test/moves/rollout.test.ts @@ -42,7 +42,7 @@ describe("Moves - Rollout", () => { const turns = 6; const dmgHistory: number[] = []; - await game.startBattle(); + await game.classicMode.startBattle(); const playerPkm = game.scene.getPlayerParty()[0]; vi.spyOn(playerPkm, "stats", "get").mockReturnValue([500000, 1, 1, 1, 1, 1]); // HP, ATK, DEF, SPATK, SPDEF, SPD diff --git a/test/moves/round.test.ts b/test/moves/round.test.ts index 43e505705ae..ccdf4cbf089 100644 --- a/test/moves/round.test.ts +++ b/test/moves/round.test.ts @@ -45,8 +45,8 @@ describe("Moves - Round", () => { game.move.select(Moves.ROUND, 0, BattlerIndex.ENEMY); game.move.select(Moves.ROUND, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.ROUND, BattlerIndex.PLAYER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.ROUND, BattlerIndex.PLAYER); + await game.move.selectEnemyMove(Moves.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY]); diff --git a/test/moves/secret_power.test.ts b/test/moves/secret_power.test.ts index f6870c5ed1d..99945002b8d 100644 --- a/test/moves/secret_power.test.ts +++ b/test/moves/secret_power.test.ts @@ -49,13 +49,13 @@ describe("Moves - Secret Power", () => { // No Terrain + Biome.VOLCANO --> Burn game.move.select(Moves.SECRET_POWER); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN); // Misty Terrain --> SpAtk -1 game.move.select(Moves.SECRET_POWER); - await game.forceEnemyMove(Moves.MISTY_TERRAIN); + await game.move.selectEnemyMove(Moves.MISTY_TERRAIN); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1); }); diff --git a/test/moves/shell_trap.test.ts b/test/moves/shell_trap.test.ts index 313d02b4d73..2aa4712152d 100644 --- a/test/moves/shell_trap.test.ts +++ b/test/moves/shell_trap.test.ts @@ -38,7 +38,7 @@ describe("Moves - Shell Trap", () => { }); it("should activate after the user is hit by a physical attack", async () => { - await game.startBattle([Species.CHARIZARD, Species.TURTONATOR]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.TURTONATOR]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -61,7 +61,7 @@ describe("Moves - Shell Trap", () => { it("should fail if the user is only hit by special attacks", async () => { game.override.enemyMoveset([Moves.SWIFT]); - await game.startBattle([Species.CHARIZARD, Species.TURTONATOR]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.TURTONATOR]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -84,7 +84,7 @@ describe("Moves - Shell Trap", () => { it("should fail if the user isn't hit with any attack", async () => { game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.CHARIZARD, Species.TURTONATOR]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.TURTONATOR]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -107,7 +107,7 @@ describe("Moves - Shell Trap", () => { it("should not activate from an ally's attack", async () => { game.override.enemyMoveset(Moves.SPLASH); - await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]); + await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]); const playerPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); @@ -131,7 +131,7 @@ describe("Moves - Shell Trap", () => { game.override.battleStyle("single"); vi.spyOn(allMoves[Moves.RAZOR_LEAF], "priority", "get").mockReturnValue(-4); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/speed_swap.test.ts b/test/moves/speed_swap.test.ts index 2b010885e34..f2be761b64e 100644 --- a/test/moves/speed_swap.test.ts +++ b/test/moves/speed_swap.test.ts @@ -34,7 +34,7 @@ describe("Moves - Speed Swap", () => { }); it("should swap the user's SPD and the target's SPD stats", async () => { - await game.startBattle([Species.INDEEDEE]); + await game.classicMode.startBattle([Species.INDEEDEE]); const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; diff --git a/test/moves/spikes.test.ts b/test/moves/spikes.test.ts index 3dfa398d7d6..f37b54a2904 100644 --- a/test/moves/spikes.test.ts +++ b/test/moves/spikes.test.ts @@ -32,7 +32,7 @@ describe("Moves - Spikes", () => { }); it("should not damage the team that set them", async () => { - await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); game.move.select(Moves.SPIKES); await game.toNextTurn(); @@ -52,7 +52,7 @@ describe("Moves - Spikes", () => { it("should damage opposing pokemon that are forced to switch in", async () => { game.override.startingWave(5); - await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); game.move.select(Moves.SPIKES); await game.toNextTurn(); @@ -66,7 +66,7 @@ describe("Moves - Spikes", () => { it("should damage opposing pokemon that choose to switch in", async () => { game.override.startingWave(5); - await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); game.move.select(Moves.SPIKES); await game.toNextTurn(); diff --git a/test/moves/spit_up.test.ts b/test/moves/spit_up.test.ts index 7197d9b75c3..b11d74da64a 100644 --- a/test/moves/spit_up.test.ts +++ b/test/moves/spit_up.test.ts @@ -50,7 +50,7 @@ describe("Moves - Spit Up", () => { const stacksToSetup = 1; const expectedPower = 100; - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); @@ -72,7 +72,7 @@ describe("Moves - Spit Up", () => { const stacksToSetup = 2; const expectedPower = 200; - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); @@ -95,7 +95,7 @@ describe("Moves - Spit Up", () => { const stacksToSetup = 3; const expectedPower = 300; - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); @@ -117,7 +117,7 @@ describe("Moves - Spit Up", () => { }); it("fails without stacks", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; @@ -138,7 +138,7 @@ describe("Moves - Spit Up", () => { describe("restores stat boosts granted by stacks", () => { it("decreases stats based on stored values (both boosts equal)", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); @@ -169,7 +169,7 @@ describe("Moves - Spit Up", () => { }); it("decreases stats based on stored values (different boosts)", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); diff --git a/test/moves/spotlight.test.ts b/test/moves/spotlight.test.ts index 2c4f652e408..e617682bdd5 100644 --- a/test/moves/spotlight.test.ts +++ b/test/moves/spotlight.test.ts @@ -39,8 +39,8 @@ describe("Moves - Spotlight", () => { game.move.select(Moves.SPOTLIGHT, 0, BattlerIndex.ENEMY); game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase, false); @@ -56,8 +56,8 @@ describe("Moves - Spotlight", () => { game.move.select(Moves.SPOTLIGHT, 0, BattlerIndex.ENEMY); game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.FOLLOW_ME); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.FOLLOW_ME); await game.phaseInterceptor.to("BerryPhase", false); diff --git a/test/moves/stockpile.test.ts b/test/moves/stockpile.test.ts index 4b8f51c32b2..196a1d8ca9a 100644 --- a/test/moves/stockpile.test.ts +++ b/test/moves/stockpile.test.ts @@ -39,7 +39,7 @@ describe("Moves - Stockpile", () => { }); it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const user = game.scene.getPlayerPokemon()!; @@ -83,7 +83,7 @@ describe("Moves - Stockpile", () => { }); it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const user = game.scene.getPlayerPokemon()!; diff --git a/test/moves/swallow.test.ts b/test/moves/swallow.test.ts index d548522068b..cff9daaf97a 100644 --- a/test/moves/swallow.test.ts +++ b/test/moves/swallow.test.ts @@ -43,7 +43,7 @@ describe("Moves - Swallow", () => { const stacksToSetup = 1; const expectedHeal = 25; - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; vi.spyOn(pokemon, "getMaxHp").mockReturnValue(100); @@ -70,7 +70,7 @@ describe("Moves - Swallow", () => { const stacksToSetup = 2; const expectedHeal = 50; - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; vi.spyOn(pokemon, "getMaxHp").mockReturnValue(100); @@ -98,7 +98,7 @@ describe("Moves - Swallow", () => { const stacksToSetup = 3; const expectedHeal = 100; - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; vi.spyOn(pokemon, "getMaxHp").mockReturnValue(100); @@ -125,7 +125,7 @@ describe("Moves - Swallow", () => { }); it("fails without stacks", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; @@ -144,7 +144,7 @@ describe("Moves - Swallow", () => { describe("restores stat stage boosts granted by stacks", () => { it("decreases stats based on stored values (both boosts equal)", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); @@ -173,7 +173,7 @@ describe("Moves - Swallow", () => { }); it("lower stat stages based on stored values (different boosts)", async () => { - await game.startBattle([Species.ABOMASNOW]); + await game.classicMode.startBattle([Species.ABOMASNOW]); const pokemon = game.scene.getPlayerPokemon()!; pokemon.addTag(BattlerTagType.STOCKPILING); diff --git a/test/moves/tackle.test.ts b/test/moves/tackle.test.ts index 162836cd181..5fd8ba589fc 100644 --- a/test/moves/tackle.test.ts +++ b/test/moves/tackle.test.ts @@ -36,7 +36,7 @@ describe("Moves - Tackle", () => { it("TACKLE against ghost", async () => { const moveToUse = Moves.TACKLE; game.override.enemySpecies(Species.GENGAR); - await game.startBattle([Species.MIGHTYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA]); const hpOpponent = game.scene.currentBattle.enemyParty[0].hp; game.move.select(moveToUse); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); @@ -46,7 +46,7 @@ describe("Moves - Tackle", () => { it("TACKLE against not resistant", async () => { const moveToUse = Moves.TACKLE; - await game.startBattle([Species.MIGHTYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA]); game.scene.currentBattle.enemyParty[0].stats[Stat.DEF] = 50; game.scene.getPlayerParty()[0].stats[Stat.ATK] = 50; diff --git a/test/moves/tail_whip.test.ts b/test/moves/tail_whip.test.ts index 2d3ade2691d..d15864dd671 100644 --- a/test/moves/tail_whip.test.ts +++ b/test/moves/tail_whip.test.ts @@ -36,7 +36,7 @@ describe("Moves - Tail whip", () => { it("should lower DEF stat stage by 1", async () => { const moveToUse = Moves.TAIL_WHIP; - await game.startBattle([Species.MIGHTYENA, Species.MIGHTYENA]); + await game.classicMode.startBattle([Species.MIGHTYENA, Species.MIGHTYENA]); const enemyPokemon = game.scene.getEnemyPokemon()!; expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); diff --git a/test/moves/taunt.test.ts b/test/moves/taunt.test.ts index e0bb13c61fb..bcb4789f888 100644 --- a/test/moves/taunt.test.ts +++ b/test/moves/taunt.test.ts @@ -37,7 +37,7 @@ describe("Moves - Taunt", () => { // First turn, Player Pokemon succeeds using Growl without Taunt game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.TAUNT); + await game.move.selectEnemyMove(Moves.TAUNT); await game.toNextTurn(); const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.GROWL); @@ -46,7 +46,7 @@ describe("Moves - Taunt", () => { // Second turn, Taunt forces Struggle to occur game.move.select(Moves.GROWL); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const move2 = playerPokemon.getLastXMoves(1)[0]!; expect(move2.move).toBe(Moves.STRUGGLE); diff --git a/test/moves/telekinesis.test.ts b/test/moves/telekinesis.test.ts index e889926b5c8..e5a21e915fa 100644 --- a/test/moves/telekinesis.test.ts +++ b/test/moves/telekinesis.test.ts @@ -106,7 +106,7 @@ describe("Moves - Telekinesis", () => { const enemyOpponent = game.scene.getEnemyPokemon()!; game.move.select(Moves.TELEKINESIS); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); @@ -114,7 +114,7 @@ describe("Moves - Telekinesis", () => { await game.toNextTurn(); vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(0); game.move.select(Moves.MUD_SHOT); - await game.forceEnemyMove(Moves.INGRAIN); + await game.move.selectEnemyMove(Moves.INGRAIN); await game.phaseInterceptor.to("TurnEndPhase"); expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); expect(enemyOpponent.getTag(BattlerTagType.INGRAIN)).toBeDefined(); diff --git a/test/moves/thousand_arrows.test.ts b/test/moves/thousand_arrows.test.ts index 7259fda8560..39bc8617767 100644 --- a/test/moves/thousand_arrows.test.ts +++ b/test/moves/thousand_arrows.test.ts @@ -33,7 +33,7 @@ describe("Moves - Thousand Arrows", () => { }); it("move should hit and ground Flying-type targets", async () => { - await game.startBattle([Species.ILLUMISE]); + await game.classicMode.startBattle([Species.ILLUMISE]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -53,7 +53,7 @@ describe("Moves - Thousand Arrows", () => { game.override.enemySpecies(Species.SNORLAX); game.override.enemyAbility(Abilities.LEVITATE); - await game.startBattle([Species.ILLUMISE]); + await game.classicMode.startBattle([Species.ILLUMISE]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -72,7 +72,7 @@ describe("Moves - Thousand Arrows", () => { it("move should hit and ground targets under the effects of Magnet Rise", async () => { game.override.enemySpecies(Species.SNORLAX); - await game.startBattle([Species.ILLUMISE]); + await game.classicMode.startBattle([Species.ILLUMISE]); const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/thunder_wave.test.ts b/test/moves/thunder_wave.test.ts index abfb5828d3b..326d7ecbdc0 100644 --- a/test/moves/thunder_wave.test.ts +++ b/test/moves/thunder_wave.test.ts @@ -34,7 +34,7 @@ describe("Moves - Thunder Wave", () => { it("paralyzes non-statused Pokemon that are not Ground types", async () => { game.override.enemySpecies(Species.MAGIKARP); - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyPokemon()!; @@ -47,7 +47,7 @@ describe("Moves - Thunder Wave", () => { it("does not paralyze if the Pokemon is a Ground-type", async () => { game.override.enemySpecies(Species.DIGLETT); - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyPokemon()!; @@ -60,7 +60,7 @@ describe("Moves - Thunder Wave", () => { it("does not paralyze if the Pokemon already has a status effect", async () => { game.override.enemySpecies(Species.MAGIKARP).enemyStatusEffect(StatusEffect.BURN); - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyPokemon()!; @@ -73,7 +73,7 @@ describe("Moves - Thunder Wave", () => { it("affects Ground types if the user has Normalize", async () => { game.override.ability(Abilities.NORMALIZE).enemySpecies(Species.DIGLETT); - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyPokemon()!; @@ -86,7 +86,7 @@ describe("Moves - Thunder Wave", () => { it("does not affect Ghost types if the user has Normalize", async () => { game.override.ability(Abilities.NORMALIZE).enemySpecies(Species.HAUNTER); - await game.startBattle(); + await game.classicMode.startBattle(); const enemyPokemon: EnemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/moves/torment.test.ts b/test/moves/torment.test.ts index d06837d2806..d11de46bf10 100644 --- a/test/moves/torment.test.ts +++ b/test/moves/torment.test.ts @@ -40,7 +40,7 @@ describe("Moves - Torment", () => { // First turn, Player Pokemon uses Tackle successfully game.move.select(Moves.TACKLE); - await game.forceEnemyMove(Moves.TORMENT); + await game.move.selectEnemyMove(Moves.TORMENT); await game.toNextTurn(); const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(Moves.TACKLE); @@ -49,14 +49,14 @@ describe("Moves - Torment", () => { // Second turn, Torment forces Struggle to occur game.move.select(Moves.TACKLE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); const move2 = playerPokemon.getLastXMoves(1)[0]!; expect(move2.move).toBe(Moves.STRUGGLE); // Third turn, Tackle can be used. game.move.select(Moves.TACKLE); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.phaseInterceptor.to(TurnEndPhase); const move3 = playerPokemon.getLastXMoves(1)[0]!; expect(move3.move).toBe(Moves.TACKLE); diff --git a/test/moves/whirlwind.test.ts b/test/moves/whirlwind.test.ts index 67ffb77612c..3558f968c66 100644 --- a/test/moves/whirlwind.test.ts +++ b/test/moves/whirlwind.test.ts @@ -51,7 +51,7 @@ describe("Moves - Whirlwind", () => { const staraptor = game.scene.getPlayerPokemon()!; game.move.select(move); - await game.forceEnemyMove(Moves.WHIRLWIND); + await game.move.selectEnemyMove(Moves.WHIRLWIND); await game.phaseInterceptor.to("BerryPhase", false); @@ -69,7 +69,7 @@ describe("Moves - Whirlwind", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WHIRLWIND); + await game.move.selectEnemyMove(Moves.WHIRLWIND); await game.toNextTurn(); expect(bulbasaur.isOnField()).toBe(false); @@ -81,7 +81,7 @@ describe("Moves - Whirlwind", () => { return min + 1; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WHIRLWIND); + await game.move.selectEnemyMove(Moves.WHIRLWIND); await game.toNextTurn(); expect(bulbasaur.isOnField()).toBe(false); @@ -101,7 +101,7 @@ describe("Moves - Whirlwind", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WHIRLWIND); + await game.move.selectEnemyMove(Moves.WHIRLWIND); await game.toNextTurn(); expect(lapras.isOnField()).toBe(false); @@ -120,7 +120,7 @@ describe("Moves - Whirlwind", () => { eevee.status = new Status(StatusEffect.FAINT); expect(eevee.isFainted()).toBe(true); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted @@ -128,7 +128,7 @@ describe("Moves - Whirlwind", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WHIRLWIND); + await game.move.selectEnemyMove(Moves.WHIRLWIND); await game.toNextTurn(); expect(lapras.isOnField()).toBe(false); @@ -147,7 +147,7 @@ describe("Moves - Whirlwind", () => { eevee.status = new Status(StatusEffect.FAINT); expect(eevee.isFainted()).toBe(true); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted @@ -155,7 +155,7 @@ describe("Moves - Whirlwind", () => { return min; }); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.WHIRLWIND); + await game.move.selectEnemyMove(Moves.WHIRLWIND); await game.toNextTurn(); expect(lapras.isOnField()).toBe(true); @@ -184,7 +184,7 @@ describe("Moves - Whirlwind", () => { // Player uses Whirlwind; opponent uses Splash game.move.select(Moves.WHIRLWIND); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Verify that the failure message is displayed for Whirlwind @@ -214,8 +214,8 @@ describe("Moves - Whirlwind", () => { game.move.select(Moves.SPLASH); game.move.select(Moves.SPLASH); - await game.forceEnemyMove(Moves.MEMENTO); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.MEMENTO); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); // Get the enemy pokemon id so we can check if is the same after switch. @@ -225,8 +225,8 @@ describe("Moves - Whirlwind", () => { game.move.select(Moves.WHIRLWIND, 0, BattlerIndex.ENEMY); game.move.select(Moves.SPLASH, 1); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.SPLASH); await game.toNextTurn(); diff --git a/test/moves/wide_guard.test.ts b/test/moves/wide_guard.test.ts index 85ebad806d7..08357476e5e 100644 --- a/test/moves/wide_guard.test.ts +++ b/test/moves/wide_guard.test.ts @@ -38,7 +38,7 @@ describe("Moves - Wide Guard", () => { }); test("should protect the user and allies from multi-target attack moves", async () => { - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -56,7 +56,7 @@ describe("Moves - Wide Guard", () => { test("should protect the user and allies from multi-target status moves", async () => { game.override.enemyMoveset([Moves.GROWL]); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -74,7 +74,7 @@ describe("Moves - Wide Guard", () => { test("should not protect the user and allies from single-target moves", async () => { game.override.enemyMoveset([Moves.TACKLE]); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); @@ -92,7 +92,7 @@ describe("Moves - Wide Guard", () => { test("should protect the user from its ally's multi-target move", async () => { game.override.enemyMoveset([Moves.SPLASH]); - await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]); + await game.classicMode.startBattle([Species.CHARIZARD, Species.BLASTOISE]); const leadPokemon = game.scene.getPlayerField(); const enemyPokemon = game.scene.getEnemyField(); diff --git a/test/phases/learn-move-phase.test.ts b/test/phases/learn-move-phase.test.ts index 019b833d386..b8b718a9669 100644 --- a/test/phases/learn-move-phase.test.ts +++ b/test/phases/learn-move-phase.test.ts @@ -56,9 +56,7 @@ describe("Learn Move Phase", () => { game.scene.ui.processInput(Button.ACTION); }); game.onNextPrompt("LearnMovePhase", UiMode.SUMMARY, () => { - for (let x = 0; x < moveSlotNum; x++) { - game.scene.ui.processInput(Button.DOWN); - } + game.scene.ui.setCursor(moveSlotNum); game.scene.ui.processInput(Button.ACTION); }); await game.phaseInterceptor.to(LearnMovePhase); @@ -88,9 +86,7 @@ describe("Learn Move Phase", () => { game.scene.ui.processInput(Button.ACTION); }); game.onNextPrompt("LearnMovePhase", UiMode.SUMMARY, () => { - for (let x = 0; x < 4; x++) { - game.scene.ui.processInput(Button.DOWN); // moves down 4 times to the 5th move slot - } + game.scene.ui.setCursor(4); game.scene.ui.processInput(Button.ACTION); }); game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { @@ -102,64 +98,4 @@ describe("Learn Move Phase", () => { expect(bulbasaur.level).toBeGreaterThanOrEqual(levelReq); expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual(prevMoveset); }); - - it("macro should add moves in free slots normally", async () => { - await game.classicMode.startBattle([Species.BULBASAUR]); - const bulbasaur = game.scene.getPlayerPokemon()!; - - game.move.changeMoveset(bulbasaur, [Moves.SPLASH, Moves.ABSORB, Moves.ACID]); - game.move.select(Moves.SPLASH); - await game.move.learnMove(Moves.SACRED_FIRE, 0, 1); - expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual([ - Moves.SPLASH, - Moves.ABSORB, - Moves.ACID, - Moves.SACRED_FIRE, - ]); - }); - - it("macro should replace moves", async () => { - await game.classicMode.startBattle([Species.BULBASAUR]); - const bulbasaur = game.scene.getPlayerPokemon()!; - - game.move.changeMoveset(bulbasaur, [Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP]); - game.move.select(Moves.SPLASH); - await game.move.learnMove(Moves.SACRED_FIRE, 0, 1); - expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual([ - Moves.SPLASH, - Moves.SACRED_FIRE, - Moves.ACID, - Moves.VINE_WHIP, - ]); - }); - - it("macro should allow for cancelling move learning", async () => { - await game.classicMode.startBattle([Species.BULBASAUR]); - const bulbasaur = game.scene.getPlayerPokemon()!; - - game.move.changeMoveset(bulbasaur, [Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP]); - game.move.select(Moves.SPLASH); - await game.move.learnMove(Moves.SACRED_FIRE, 0, 4); - expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual([ - Moves.SPLASH, - Moves.ABSORB, - Moves.ACID, - Moves.VINE_WHIP, - ]); - }); - - it("macro works on off-field party members", async () => { - await game.classicMode.startBattle([Species.BULBASAUR, Species.SQUIRTLE]); - const squirtle = game.scene.getPlayerParty()[1]!; - - game.move.changeMoveset(squirtle, [Moves.SPLASH, Moves.WATER_GUN, Moves.FREEZE_DRY, Moves.GROWL]); - game.move.select(Moves.TACKLE); - await game.move.learnMove(Moves.SHELL_SMASH, 1, 0); - expect(squirtle.getMoveset().map(m => m?.moveId)).toEqual([ - Moves.SHELL_SMASH, - Moves.WATER_GUN, - Moves.FREEZE_DRY, - Moves.GROWL, - ]); - }); }); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index 07cea3b1328..39b6000e308 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -1,7 +1,6 @@ import { updateUserInfo } from "#app/account"; import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { getMoveTargets } from "#app/data/moves/move"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; import { GameModes, getGameMode } from "#app/game-mode"; @@ -11,7 +10,6 @@ import overrides from "#app/overrides"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CommandPhase } from "#app/phases/command-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; -import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { FaintPhase } from "#app/phases/faint-phase"; import { LoginPhase } from "#app/phases/login-phase"; import { MovePhase } from "#app/phases/move-phase"; @@ -31,11 +29,9 @@ import type PartyUiHandler from "#app/ui/party-ui-handler"; import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; import type TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; import { isNullOrUndefined } from "#app/utils/common"; -import { BattleStyle } from "#enums/battle-style"; import { Button } from "#enums/buttons"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { ExpNotification } from "#enums/exp-notification"; -import type { Moves } from "#enums/moves"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PlayerGender } from "#enums/player-gender"; import type { Species } from "#enums/species"; @@ -274,42 +270,6 @@ export default class GameManager { } } - /** - * @deprecated Use `game.classicMode.startBattle()` or `game.dailyMode.startBattle()` instead - * - * Transitions to the start of a battle. - * @param species - Optional array of species to start the battle with. - * @returns A promise that resolves when the battle is started. - */ - async startBattle(species?: Species[]) { - await this.classicMode.runToSummon(species); - - if (this.scene.battleStyle === BattleStyle.SWITCH) { - this.onNextPrompt( - "CheckSwitchPhase", - UiMode.CONFIRM, - () => { - this.setMode(UiMode.MESSAGE); - this.endPhase(); - }, - () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(TurnInitPhase), - ); - - this.onNextPrompt( - "CheckSwitchPhase", - UiMode.CONFIRM, - () => { - this.setMode(UiMode.MESSAGE); - this.endPhase(); - }, - () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(TurnInitPhase), - ); - } - - await this.phaseInterceptor.to(CommandPhase); - console.log("==================[New Turn]=================="); - } - /** * Emulate a player's target selection after a move is chosen, usually called automatically by {@linkcode MoveHelper.select}. * Will trigger during the next {@linkcode SelectTargetPhase} @@ -380,36 +340,6 @@ export default class GameManager { ); } - /** - * Forces the next enemy selecting a move to use the given move in its moveset against the - * given target (if applicable). - * @param moveId - The {@linkcode Moves | move} the enemy will use - * @param target - The {@linkcode BattlerIndex} of the target against which the enemy will use the given move; - * will use normal target selection priorities if omitted. - * @deprecated Use {@linkcode MoveHelper.forceEnemyMove} or {@linkcode MoveHelper.selectEnemyMove} - */ - async forceEnemyMove(moveId: Moves, target?: BattlerIndex) { - // Wait for the next EnemyCommandPhase to start - await this.phaseInterceptor.to(EnemyCommandPhase, false); - const enemy = this.scene.getEnemyField()[(this.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()]; - const legalTargets = getMoveTargets(enemy, moveId); - - vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({ - move: moveId, - targets: - target !== undefined && !legalTargets.multiple && legalTargets.targets.includes(target) - ? [target] - : enemy.getNextTargets(moveId), - }); - - /** - * Run the EnemyCommandPhase to completion. - * This allows this function to be called consecutively to - * force a move for each enemy in a double battle. - */ - await this.phaseInterceptor.to(EnemyCommandPhase); - } - forceEnemyToSwitch() { const originalMatchupScore = Trainer.prototype.getPartyMemberMatchupScores; Trainer.prototype.getPartyMemberMatchupScores = () => { diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index ab10486867d..b3cdffeb636 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -1,12 +1,10 @@ import type { BattlerIndex } from "#app/battle"; import { getMoveTargets } from "#app/data/moves/move"; -import { Button } from "#app/enums/buttons"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; import Overrides from "#app/overrides"; import type { CommandPhase } from "#app/phases/command-phase"; import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; -import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Command } from "#app/ui/command-ui-handler"; import { Moves } from "#enums/moves"; @@ -233,39 +231,4 @@ export class MoveHelper extends GameManagerHelper { */ await this.game.phaseInterceptor.to("EnemyCommandPhase"); } - - /** - * Simulates learning a move for a player pokemon. - * @param move The {@linkcode Moves} being learnt - * @param partyIndex The party position of the {@linkcode PlayerPokemon} learning the move (defaults to 0) - * @param moveSlotIndex The INDEX (0-4) of the move slot to replace if existent move slots are full; - * defaults to 0 (first slot) and 4 aborts the procedure - * @returns a promise that resolves once the move has been successfully learnt - */ - public async learnMove(move: Moves | number, partyIndex = 0, moveSlotIndex = 0) { - return new Promise(async (resolve, reject) => { - this.game.scene.pushPhase(new LearnMovePhase(partyIndex, move)); - - // if slots are full, queue up inputs to replace existing moves - if (this.game.scene.getPlayerParty()[partyIndex].moveset.filter(m => m).length === 4) { - this.game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { - this.game.scene.ui.processInput(Button.ACTION); // "Should a move be forgotten and replaced with XXX?" - }); - this.game.onNextPrompt("LearnMovePhase", UiMode.SUMMARY, () => { - for (let x = 0; x < (moveSlotIndex ?? 0); x++) { - this.game.scene.ui.processInput(Button.DOWN); // Scrolling in summary pane to move position - } - this.game.scene.ui.processInput(Button.ACTION); - if (moveSlotIndex === 4) { - this.game.onNextPrompt("LearnMovePhase", UiMode.CONFIRM, () => { - this.game.scene.ui.processInput(Button.ACTION); // "Give up on learning XXX?" - }); - } - }); - } - - await this.game.phaseInterceptor.to(LearnMovePhase).catch(e => reject(e)); - resolve(); - }); - } } diff --git a/test/ui/type-hints.test.ts b/test/ui/type-hints.test.ts index 2051af76754..b32f5ed9b88 100644 --- a/test/ui/type-hints.test.ts +++ b/test/ui/type-hints.test.ts @@ -99,8 +99,8 @@ describe("UI - Type Hints", () => { // Use soak to change type of remaining abra to water game.move.select(Moves.SOAK, 1); - await game.forceEnemyMove(Moves.SPLASH); - await game.forceEnemyMove(Moves.TELEPORT); + await game.move.selectEnemyMove(Moves.SPLASH); + await game.move.selectEnemyMove(Moves.TELEPORT); await game.toNextTurn(); game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { From ccd9480240ea26285683fb997fcb1af67e3d4a9b Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Sat, 31 May 2025 01:54:17 +0200 Subject: [PATCH 20/20] [Localization] Secondary pending languages inclusion (#5903) * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Update mysterious-chest-dialogue.json * Update mysterious-chest-dialogue.json * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Update i18n.ts * Update settings.ts * Update settings-display-ui-handler.ts * Update starter-select-ui-handler.ts * Update i18n.ts * Update i18n.ts * Update settings.ts * Update settings-display-ui-handler.ts * Update i18n.ts * Update starter-select-ui-handler.ts * Update utils.ts * Update utils.ts * Add files via upload * Rename statuses_dk.json to statuses_da.json * Update statuses_da.json * Update and rename types_dk.json to types_da.json * Rename statuses_dk.png to statuses_da.png * Rename types_dk.png to types_da.png * Delete src/locales/dk directory * Add files via upload * Apply suggestions from code review * Delete src/locales/da directory * Delete src/locales/tr directory * Update i18n.ts * Update i18n.ts * Update utils.ts * Main -> Beta (1.1.6) (#4751) * Comment out startGame call on manifest fetch failure * [Hotfix] Fix status damage triggering before berry usage (#4732) * [Hotfix] Fix Eternatus egg tier (#4734) * [Hotfix] Fix manifest getting loaded before the game is initialized (#4739) * fix locales path for offline builds (#4739) * [Sprite] Hotfix cut off Binacle sprite (#4741) * [Sprite][hotfix] Fixed cropping on 658 static greninja and ash greninja (#4743) * [Sprite][hotfix] Fixed cropping on static greninja and ash greninja * [Hotfix] Fix crash when Mist would block a stat drop (#4746) --------- Co-authored-by: Frederico Santos Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Co-authored-by: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Co-authored-by: pom-eranian * Update settings-display-ui-handler.ts * Delete src/utils.ts * Update common.ts * Delete src/utils.ts * Update common.ts * Romanian workspace (#25) * Russian workspace (#26) * Update settings-display-ui-handler.ts * Update settings-display-ui-handler.ts * Update settings.ts * Update settings-display-ui-handler.ts * Update i18n.ts * Update settings.ts * Update settings-display-ui-handler.ts * Update common.ts * Update and rename statuses_ca-ES.json to statuses_ca.json * Update and rename types_ca-ES.json to types_ca.json * Add files via upload * Delete public/images/statuses_ca-ES.png * Delete public/images/types_ca-ES.png * Update locales submodule --------- Co-authored-by: Moka <54149968+MokaStitcher@users.noreply.github.com> Co-authored-by: Frederico Santos Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Co-authored-by: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Co-authored-by: pom-eranian Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- .../{statuses_ca-ES.json => statuses_ca.json} | 2 +- .../{statuses_ca-ES.png => statuses_ca.png} | Bin public/images/statuses_da.json | 188 ++++++++ public/images/statuses_da.png | Bin 0 -> 2355 bytes public/images/statuses_ro.json | 188 ++++++++ public/images/statuses_ro.png | Bin 0 -> 2111 bytes public/images/statuses_ru.json | 188 ++++++++ public/images/statuses_ru.png | Bin 0 -> 2091 bytes public/images/statuses_tr.json | 188 ++++++++ public/images/statuses_tr.png | Bin 0 -> 441 bytes .../{types_ca-ES.json => types_ca.json} | 2 +- .../images/{types_ca-ES.png => types_ca.png} | Bin public/images/types_da.json | 440 ++++++++++++++++++ public/images/types_da.png | Bin 0 -> 6247 bytes public/images/types_ro.json | 440 ++++++++++++++++++ public/images/types_ro.png | Bin 0 -> 6536 bytes public/images/types_ru.json | 440 ++++++++++++++++++ public/images/types_ru.png | Bin 0 -> 7570 bytes public/images/types_tr.json | 440 ++++++++++++++++++ public/images/types_tr.png | Bin 0 -> 4467 bytes public/locales | 2 +- src/plugins/i18n.ts | 6 +- src/system/settings/settings.ts | 44 +- .../settings/settings-display-ui-handler.ts | 64 ++- src/ui/starter-select-ui-handler.ts | 18 + src/utils/common.ts | 6 +- 26 files changed, 2615 insertions(+), 41 deletions(-) rename public/images/{statuses_ca-ES.json => statuses_ca.json} (98%) rename public/images/{statuses_ca-ES.png => statuses_ca.png} (100%) create mode 100644 public/images/statuses_da.json create mode 100644 public/images/statuses_da.png create mode 100644 public/images/statuses_ro.json create mode 100644 public/images/statuses_ro.png create mode 100644 public/images/statuses_ru.json create mode 100644 public/images/statuses_ru.png create mode 100644 public/images/statuses_tr.json create mode 100644 public/images/statuses_tr.png rename public/images/{types_ca-ES.json => types_ca.json} (99%) rename public/images/{types_ca-ES.png => types_ca.png} (100%) create mode 100644 public/images/types_da.json create mode 100644 public/images/types_da.png create mode 100644 public/images/types_ro.json create mode 100644 public/images/types_ro.png create mode 100644 public/images/types_ru.json create mode 100644 public/images/types_ru.png create mode 100644 public/images/types_tr.json create mode 100644 public/images/types_tr.png diff --git a/public/images/statuses_ca-ES.json b/public/images/statuses_ca.json similarity index 98% rename from public/images/statuses_ca-ES.json rename to public/images/statuses_ca.json index be1b78e0e41..6ff0f74a73b 100644 --- a/public/images/statuses_ca-ES.json +++ b/public/images/statuses_ca.json @@ -1,7 +1,7 @@ { "textures": [ { - "image": "statuses_ca_ES.png", + "image": "statuses_ca.png", "format": "RGBA8888", "size": { "w": 22, diff --git a/public/images/statuses_ca-ES.png b/public/images/statuses_ca.png similarity index 100% rename from public/images/statuses_ca-ES.png rename to public/images/statuses_ca.png diff --git a/public/images/statuses_da.json b/public/images/statuses_da.json new file mode 100644 index 00000000000..b9bf56324de --- /dev/null +++ b/public/images/statuses_da.json @@ -0,0 +1,188 @@ +{ + "textures": [ + { + "image": "statuses_da.png", + "format": "RGBA8888", + "size": { + "w": 22, + "h": 64 + }, + "scale": 1, + "frames": [ + { + "filename": "pokerus", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 22, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 22, + "h": 8 + }, + "frame": { + "x": 0, + "y": 0, + "w": 22, + "h": 8 + } + }, + { + "filename": "burn", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 8, + "w": 20, + "h": 8 + } + }, + { + "filename": "faint", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 16, + "w": 20, + "h": 8 + } + }, + { + "filename": "freeze", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 24, + "w": 20, + "h": 8 + } + }, + { + "filename": "paralysis", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 32, + "w": 20, + "h": 8 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 40, + "w": 20, + "h": 8 + } + }, + { + "filename": "sleep", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 48, + "w": 20, + "h": 8 + } + }, + { + "filename": "toxic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 56, + "w": 20, + "h": 8 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$" + } +} diff --git a/public/images/statuses_da.png b/public/images/statuses_da.png new file mode 100644 index 0000000000000000000000000000000000000000..02780bddd98699e74c276fb44903db9426131710 GIT binary patch literal 2355 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 BAo>6R literal 0 HcmV?d00001 diff --git a/public/images/statuses_ru.json b/public/images/statuses_ru.json new file mode 100644 index 00000000000..5e4790b2874 --- /dev/null +++ b/public/images/statuses_ru.json @@ -0,0 +1,188 @@ +{ + "textures": [ + { + "image": "statuses_ru.png", + "format": "RGBA8888", + "size": { + "w": 22, + "h": 64 + }, + "scale": 1, + "frames": [ + { + "filename": "pokerus", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 22, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 22, + "h": 8 + }, + "frame": { + "x": 0, + "y": 0, + "w": 22, + "h": 8 + } + }, + { + "filename": "burn", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 8, + "w": 20, + "h": 8 + } + }, + { + "filename": "faint", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 16, + "w": 20, + "h": 8 + } + }, + { + "filename": "freeze", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 24, + "w": 20, + "h": 8 + } + }, + { + "filename": "paralysis", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 32, + "w": 20, + "h": 8 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 40, + "w": 20, + "h": 8 + } + }, + { + "filename": "sleep", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 48, + "w": 20, + "h": 8 + } + }, + { + "filename": "toxic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 20, + "h": 8 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 20, + "h": 8 + }, + "frame": { + "x": 0, + "y": 56, + "w": 20, + "h": 8 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:37686e85605d17b806f22d43081c1139:70535ffee63ba61b3397d8470c2c8982:e6649238c018d3630e55681417c698ca$" + } +} diff --git a/public/images/statuses_ru.png b/public/images/statuses_ru.png new file mode 100644 index 0000000000000000000000000000000000000000..1da8b66a4f8be4867fe00ade4eed9bbd67e0e449 GIT binary patch literal 2091 zcmbVN2~ZPP7~ZHwjp7L6fmU5Z#RHPv4G_peB1c4UNI;GOkE+XNlkAW^OcoNninfX( z$XF1m7ebL*McYE7RjgPnU=g)C7AjD+BI1p8s@PiFZa6a9(Q)dVdHb&KegFM-V~l2w zvv9l+008I62vsaUj^TUo5GVdS3D3O14@23A`DOqZHp<=w!1kTv0ANr%8K1-@siRPw zp(L1=NhTy#isjJ&5H!omVt5L{fysoPq!psBs%jBPY89eHKQ*jo!w3TzvD`$&E!V{3 z%TsW}%xg3I}kW?z>5n^*X&0$tCZT9GAP!VR_M6w*o(4d_WOJ-6zg^1U* z*93}n(9-5UH}MXGtQZR+64-814^WFcaBQl{*dttvLxho_2%0nVSj2&44GhPa4a{q( z9mg*T;9aX$J7m047m9L-Fmtohcr*GO@=CNhKAk0?Si;Ptns8!v8n32@-5M4RGZ7fa znBp16*dM5vewD$nFmQSzNoyI4dBzKHLWOaJLL`NyzG7G|M*QPpDT*Lo{1=9MacV|O z>e63@llr56@JmoWTeKL5y%DU%Q5|EVFy2;@!t?~h(s~i-@E{t-7#R~Um{-lGM+>zY zjik*SM&m@JN+IISm5`(s^_O7;=I;lK310*kYcXFRu^dMN#ByCgKyorJ*JX)OkVCw*P4dogB4$5}8F5u)c^^@cQpc)cNe_4S$n{nxslvllA0AI_J* zed>63_=V%;B53}(GV$-yG(liC01S8#sS1ftT6eZ^3ioO7$n_ssH*EFVa{BoGxvF03qP;Oii?^mEN;9sIKOL0$yZ-;MU!feg=MnBnz83y zqGxWm)l^>oJWs8Q0!I|%(UcG@Z%3-p<;4Obl;T z6|DElY9AN3`I@c6Z|h`lc%`+JscdBv3rE~x-N(%hJhi9wuRCWuKXbmdf6|kN%K6Bo z<^!WIJ?OrM5A{oT%G0M6L!N8W8k6)#L)VsZ6O;ve3u8+Yy%tVbJO@1tg{RJHvDyZ} zi;6zXUFTD56W z33t11H3wz1E6+C{$$9K)8g=f^`IDb<`>!q>uKX!DeC9k2wbHiB>q?We@Tkp`wIb(kbvSJLmK4Z=-oxl+~les_s^V{&~YJjfp_P! zWlj5XJ4(#9)AS>iGDjC=W6RwZZE0#~TCcAtb!&4M01_x7%KzeP1y+B2_I7G_xHE-s8-)5{aFy1?zvW;$NS#fE(5J*QH>;TO# zqj-;m3y@V^jDKUiXB4T~am+que5?h|bD2QvJlYc8%_G~aG}z~@!Q2dSmt873PQXLU zg+O|T!QM`p^A|K=@Suc&?b@)04> jC;!H^NCF;OJ`4N>xriuwM360I00000NkvXXu0mjf67avU literal 0 HcmV?d00001 diff --git a/public/images/types_ca-ES.json b/public/images/types_ca.json similarity index 99% rename from public/images/types_ca-ES.json rename to public/images/types_ca.json index fa3abaaf259..aaa04fbad36 100644 --- a/public/images/types_ca-ES.json +++ b/public/images/types_ca.json @@ -1,7 +1,7 @@ { "textures": [ { - "image": "types_ca-ES.png", + "image": "types_ca.png", "format": "RGBA8888", "size": { "w": 32, diff --git a/public/images/types_ca-ES.png b/public/images/types_ca.png similarity index 100% rename from public/images/types_ca-ES.png rename to public/images/types_ca.png diff --git a/public/images/types_da.json b/public/images/types_da.json new file mode 100644 index 00000000000..d1c01de1e0e --- /dev/null +++ b/public/images/types_da.json @@ -0,0 +1,440 @@ +{ + "textures": [ + { + "image": "types_da.png", + "format": "RGBA8888", + "size": { + "w": 32, + "h": 280 + }, + "scale": 1, + "frames": [ + { + "filename": "unknown", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + } + }, + { + "filename": "bug", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 14, + "w": 32, + "h": 14 + } + }, + { + "filename": "dark", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 28, + "w": 32, + "h": 14 + } + }, + { + "filename": "dragon", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 42, + "w": 32, + "h": 14 + } + }, + { + "filename": "electric", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 56, + "w": 32, + "h": 14 + } + }, + { + "filename": "fairy", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 70, + "w": 32, + "h": 14 + } + }, + { + "filename": "fighting", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 84, + "w": 32, + "h": 14 + } + }, + { + "filename": "fire", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 98, + "w": 32, + "h": 14 + } + }, + { + "filename": "flying", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 112, + "w": 32, + "h": 14 + } + }, + { + "filename": "ghost", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 126, + "w": 32, + "h": 14 + } + }, + { + "filename": "grass", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 140, + "w": 32, + "h": 14 + } + }, + { + "filename": "ground", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 154, + "w": 32, + "h": 14 + } + }, + { + "filename": "ice", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 168, + "w": 32, + "h": 14 + } + }, + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 182, + "w": 32, + "h": 14 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 196, + "w": 32, + "h": 14 + } + }, + { + "filename": "psychic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 210, + "w": 32, + "h": 14 + } + }, + { + "filename": "rock", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 224, + "w": 32, + "h": 14 + } + }, + { + "filename": "steel", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 238, + "w": 32, + "h": 14 + } + }, + { + "filename": "water", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 252, + "w": 32, + "h": 14 + } + }, + { + "filename": "stellar", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 266, + "w": 32, + "h": 14 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$" + } +} diff --git a/public/images/types_da.png b/public/images/types_da.png new file mode 100644 index 0000000000000000000000000000000000000000..f2b7af8967aef2e29fa68d2dd0cecfaf99562458 GIT binary patch literal 6247 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 literal 0 HcmV?d00001 diff --git a/public/images/types_ro.json b/public/images/types_ro.json new file mode 100644 index 00000000000..efdbeba38a0 --- /dev/null +++ b/public/images/types_ro.json @@ -0,0 +1,440 @@ +{ + "textures": [ + { + "image": "types_ro.png", + "format": "RGBA8888", + "size": { + "w": 32, + "h": 280 + }, + "scale": 1, + "frames": [ + { + "filename": "unknown", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + } + }, + { + "filename": "bug", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 14, + "w": 32, + "h": 14 + } + }, + { + "filename": "dark", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 28, + "w": 32, + "h": 14 + } + }, + { + "filename": "dragon", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 42, + "w": 32, + "h": 14 + } + }, + { + "filename": "electric", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 56, + "w": 32, + "h": 14 + } + }, + { + "filename": "fairy", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 70, + "w": 32, + "h": 14 + } + }, + { + "filename": "fighting", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 84, + "w": 32, + "h": 14 + } + }, + { + "filename": "fire", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 98, + "w": 32, + "h": 14 + } + }, + { + "filename": "flying", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 112, + "w": 32, + "h": 14 + } + }, + { + "filename": "ghost", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 126, + "w": 32, + "h": 14 + } + }, + { + "filename": "grass", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 140, + "w": 32, + "h": 14 + } + }, + { + "filename": "ground", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 154, + "w": 32, + "h": 14 + } + }, + { + "filename": "ice", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 168, + "w": 32, + "h": 14 + } + }, + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 182, + "w": 32, + "h": 14 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 196, + "w": 32, + "h": 14 + } + }, + { + "filename": "psychic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 210, + "w": 32, + "h": 14 + } + }, + { + "filename": "rock", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 224, + "w": 32, + "h": 14 + } + }, + { + "filename": "steel", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 238, + "w": 32, + "h": 14 + } + }, + { + "filename": "water", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 252, + "w": 32, + "h": 14 + } + }, + { + "filename": "stellar", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 266, + "w": 32, + "h": 14 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$" + } +} diff --git a/public/images/types_ro.png b/public/images/types_ro.png new file mode 100644 index 0000000000000000000000000000000000000000..35b09a37035c42476115e2bbf166650358cc5a92 GIT binary patch literal 6536 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 LBtrK7-_HFH=U70Y literal 0 HcmV?d00001 diff --git a/public/images/types_ru.json b/public/images/types_ru.json new file mode 100644 index 00000000000..536a0e31929 --- /dev/null +++ b/public/images/types_ru.json @@ -0,0 +1,440 @@ +{ + "textures": [ + { + "image": "types_ru.png", + "format": "RGBA8888", + "size": { + "w": 32, + "h": 280 + }, + "scale": 1, + "frames": [ + { + "filename": "unknown", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + } + }, + { + "filename": "bug", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 14, + "w": 32, + "h": 14 + } + }, + { + "filename": "dark", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 28, + "w": 32, + "h": 14 + } + }, + { + "filename": "dragon", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 42, + "w": 32, + "h": 14 + } + }, + { + "filename": "electric", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 56, + "w": 32, + "h": 14 + } + }, + { + "filename": "fairy", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 70, + "w": 32, + "h": 14 + } + }, + { + "filename": "fighting", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 84, + "w": 32, + "h": 14 + } + }, + { + "filename": "fire", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 98, + "w": 32, + "h": 14 + } + }, + { + "filename": "flying", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 112, + "w": 32, + "h": 14 + } + }, + { + "filename": "ghost", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 126, + "w": 32, + "h": 14 + } + }, + { + "filename": "grass", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 140, + "w": 32, + "h": 14 + } + }, + { + "filename": "ground", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 154, + "w": 32, + "h": 14 + } + }, + { + "filename": "ice", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 168, + "w": 32, + "h": 14 + } + }, + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 182, + "w": 32, + "h": 14 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 196, + "w": 32, + "h": 14 + } + }, + { + "filename": "psychic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 210, + "w": 32, + "h": 14 + } + }, + { + "filename": "rock", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 224, + "w": 32, + "h": 14 + } + }, + { + "filename": "steel", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 238, + "w": 32, + "h": 14 + } + }, + { + "filename": "water", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 252, + "w": 32, + "h": 14 + } + }, + { + "filename": "stellar", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 266, + "w": 32, + "h": 14 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$" + } +} diff --git a/public/images/types_ru.png b/public/images/types_ru.png new file mode 100644 index 0000000000000000000000000000000000000000..571e162e8dc9f3da298566982b2634402f9ff570 GIT binary patch literal 7570 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

4oN6_d$h(=No=`(HwA?WO-Ub)vY~UtQ(D G`@aB6|Ab5c literal 0 HcmV?d00001 diff --git a/public/images/types_tr.json b/public/images/types_tr.json new file mode 100644 index 00000000000..ee82cce3fb4 --- /dev/null +++ b/public/images/types_tr.json @@ -0,0 +1,440 @@ +{ + "textures": [ + { + "image": "types_tr.png", + "format": "RGBA8888", + "size": { + "w": 32, + "h": 280 + }, + "scale": 1, + "frames": [ + { + "filename": "unknown", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + } + }, + { + "filename": "bug", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 14, + "w": 32, + "h": 14 + } + }, + { + "filename": "dark", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 28, + "w": 32, + "h": 14 + } + }, + { + "filename": "dragon", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 42, + "w": 32, + "h": 14 + } + }, + { + "filename": "electric", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 56, + "w": 32, + "h": 14 + } + }, + { + "filename": "fairy", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 70, + "w": 32, + "h": 14 + } + }, + { + "filename": "fighting", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 84, + "w": 32, + "h": 14 + } + }, + { + "filename": "fire", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 98, + "w": 32, + "h": 14 + } + }, + { + "filename": "flying", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 112, + "w": 32, + "h": 14 + } + }, + { + "filename": "ghost", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 126, + "w": 32, + "h": 14 + } + }, + { + "filename": "grass", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 140, + "w": 32, + "h": 14 + } + }, + { + "filename": "ground", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 154, + "w": 32, + "h": 14 + } + }, + { + "filename": "ice", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 168, + "w": 32, + "h": 14 + } + }, + { + "filename": "normal", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 182, + "w": 32, + "h": 14 + } + }, + { + "filename": "poison", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 196, + "w": 32, + "h": 14 + } + }, + { + "filename": "psychic", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 210, + "w": 32, + "h": 14 + } + }, + { + "filename": "rock", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 224, + "w": 32, + "h": 14 + } + }, + { + "filename": "steel", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 238, + "w": 32, + "h": 14 + } + }, + { + "filename": "water", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 252, + "w": 32, + "h": 14 + } + }, + { + "filename": "stellar", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 32, + "h": 14 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 32, + "h": 14 + }, + "frame": { + "x": 0, + "y": 266, + "w": 32, + "h": 14 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:f14cf47d9a8f1d40c8e03aa6ba00fff3:6fc4227b57a95d429a1faad4280f7ec8:5961efbfbf4c56b8745347e7a663a32f$" + } +} diff --git a/public/images/types_tr.png b/public/images/types_tr.png new file mode 100644 index 0000000000000000000000000000000000000000..8b644f1041c4236c9e729b30c529149e209a286c GIT binary patch literal 4467 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 = [ unicodeRange: rangesByLanguage.chinese, }), extraOptions: { sizeAdjust: "70%", format: "woff2" }, - only: ["en", "es", "fr", "it", "de", "zh", "pt", "ko", "ca"], + only: ["en", "es", "fr", "it", "de", "zh", "pt", "ko", "ca", "da", "tr", "ro", "ru"], }, { face: new FontFace("pkmnems", "url(./fonts/unifont-15.1.05.subset.woff2)", { unicodeRange: rangesByLanguage.chinese, }), extraOptions: { format: "woff2" }, - only: ["en", "es", "fr", "it", "de", "zh", "pt", "ko", "ca"], + only: ["en", "es", "fr", "it", "de", "zh", "pt", "ko", "ca", "da", "tr", "ro", "ru"], }, // japanese { @@ -174,7 +174,7 @@ export async function initI18n(): Promise { "es-MX": ["es-ES", "en"], default: ["en"], }, - supportedLngs: ["en", "es-ES", "es-MX", "fr", "it", "de", "zh-CN", "zh-TW", "pt-BR", "ko", "ja", "ca-ES"], + supportedLngs: ["en", "es-ES", "es-MX", "fr", "it", "de", "zh-CN", "zh-TW", "pt-BR", "ko", "ja", "ca", "da", "tr", "ro", "ru"], backend: { loadPath(lng: string, [ns]: string[]) { let fileName: string; diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 8bbba267bd6..8a0e4f8cdc0 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -921,10 +921,6 @@ export function setSetting(setting: string, value: number): boolean { label: "Español (LATAM)", handler: () => changeLocaleHandler("es-MX"), }, - { - label: "Italiano", - handler: () => changeLocaleHandler("it"), - }, { label: "Français", handler: () => changeLocaleHandler("fr"), @@ -933,18 +929,14 @@ export function setSetting(setting: string, value: number): boolean { label: "Deutsch", handler: () => changeLocaleHandler("de"), }, + { + label: "Italiano", + handler: () => changeLocaleHandler("it"), + }, { label: "Português (BR)", handler: () => changeLocaleHandler("pt-BR"), }, - { - label: "简体中文", - handler: () => changeLocaleHandler("zh-CN"), - }, - { - label: "繁體中文", - handler: () => changeLocaleHandler("zh-TW"), - }, { label: "한국어", handler: () => changeLocaleHandler("ko"), @@ -954,8 +946,32 @@ export function setSetting(setting: string, value: number): boolean { handler: () => changeLocaleHandler("ja"), }, { - label: "Català", - handler: () => changeLocaleHandler("ca-ES"), + label: "简体中文", + handler: () => changeLocaleHandler("zh-CN"), + }, + { + label: "繁體中文", + handler: () => changeLocaleHandler("zh-TW"), + }, + { + label: "Català (Needs Help)", + handler: () => changeLocaleHandler("ca"), + }, + { + label: "Türkçe (Needs Help)", + handler: () => changeLocaleHandler("tr") + }, + { + label: "Русский (Needs Help)", + handler: () => changeLocaleHandler("ru"), + }, + { + label: "Dansk (Needs Help)", + handler: () => changeLocaleHandler("da") + }, + { + label: "Română (Needs Help)", + handler: () => changeLocaleHandler("ro") }, { label: i18next.t("settings:back"), diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index 4878bae72cb..0636bd25567 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -39,12 +39,6 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler label: "Español (LATAM)", }; break; - case "it": - this.settings[languageIndex].options[0] = { - value: "Italiano", - label: "Italiano", - }; - break; case "fr": this.settings[languageIndex].options[0] = { value: "Français", @@ -57,24 +51,18 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler label: "Deutsch", }; break; + case "it": + this.settings[languageIndex].options[0] = { + value: "Italiano", + label: "Italiano", + }; + break; case "pt-BR": this.settings[languageIndex].options[0] = { value: "Português (BR)", label: "Português (BR)", }; break; - case "zh-CN": - this.settings[languageIndex].options[0] = { - value: "简体中文", - label: "简体中文", - }; - break; - case "zh-TW": - this.settings[languageIndex].options[0] = { - value: "繁體中文", - label: "繁體中文", - }; - break; case "ko": case "ko-KR": this.settings[languageIndex].options[0] = { @@ -88,10 +76,46 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler label: "日本語", }; break; - case "ca-ES": + case "zh-CN": + this.settings[languageIndex].options[0] = { + value: "简体中文", + label: "简体中文", + }; + break; + case "zh-TW": + this.settings[languageIndex].options[0] = { + value: "繁體中文", + label: "繁體中文", + }; + break; + case "ca": this.settings[languageIndex].options[0] = { value: "Català", - label: "Català", + label: "Català (Needs Help)", + }; + break; + case "tr": + this.settings[languageIndex].options[0] = { + value: "Türkçe", + label: "Türkçe (Needs Help)", + }; + break; + case "ru": + this.settings[languageIndex].options[0] = { + value: "Русский", + label: "Русский (Needs Help)", + }; + break; + case "da": + this.settings[languageIndex].options[0] = { + value: "Dansk", + label: "Dansk (Needs Help)", + }; + break; + case "ro": + this.settings[languageIndex].options[0] = { + value: "Română", + label: "Română (Needs Help)", }; break; default: diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index ff2298f268d..945ddaa6ed4 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -162,6 +162,24 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 29, }, + da:{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + tr:{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + ro:{ + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + ru: { + starterInfoTextSize: "46px", + instructionTextSize: "38px", + starterInfoYOffset: 0.5, + starterInfoXPos: 26, + }, }; const valueReductionMax = 2; diff --git a/src/utils/common.ts b/src/utils/common.ts index a018b49da3c..1c7ea60da16 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -435,14 +435,18 @@ export function hasAllLocalizedSprites(lang?: string): boolean { case "es-ES": case "es-MX": case "fr": + case "da": case "de": case "it": case "zh-CN": case "zh-TW": case "pt-BR": + case "ro": + case "tr": case "ko": case "ja": - case "ca-ES": + case "ca": + case "ru": return true; default: return false;