From deb2035610091eedb884c60ee5f2f4cd14365f50 Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:30:28 +0200 Subject: [PATCH 01/19] [Beta][P2] Fix Grip Claw (#4614) * [Beta][P2] Fix Grip Claw * Add test for Grip Claw * [test] improve grip claw's test readability * PR feedback --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/modifier/modifier.ts | 7 ++- src/test/items/grip_claw.test.ts | 96 +++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b658d3b5277..6c9b5db1bca 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3084,11 +3084,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * Steals an item from a set of target Pokemon. * This prioritizes high-tier held items when selecting the item to steal. * @param pokemon The {@linkcode Pokemon} holding this item + * @param target The {@linkcode Pokemon} to steal from (optional) * @param _args N/A * @returns `true` if an item was stolen; false otherwise. */ - override apply(pokemon: Pokemon, ..._args: unknown[]): boolean { - const opponents = this.getTargets(pokemon); + override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean { + const opponents = this.getTargets(pokemon, target); if (!opponents.length) { return false; @@ -3187,7 +3188,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { * @see {@linkcode HeldItemTransferModifier} */ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { - private chance: number; + public readonly chance: number; constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { super(type, pokemonId, stackCount); diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index 9d44a9e4672..2909549af87 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -1,16 +1,14 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/move"; -import { Abilities } from "#app/enums/abilities"; -import { BerryType } from "#app/enums/berry-type"; -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; +import Pokemon from "#app/field/pokemon"; +import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; +import { Abilities } from "#enums/abilities"; +import { BerryType } from "#enums/berry-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -// 20 seconds - describe("Items - Grip Claw", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -30,39 +28,85 @@ describe("Items - Grip Claw", () => { game.override .battleType("double") - .moveset([ Moves.POPULATION_BOMB, Moves.SPLASH ]) + .moveset([ Moves.TACKLE, Moves.SPLASH, Moves.ATTRACT ]) .startingHeldItems([ - { name: "GRIP_CLAW", count: 5 }, // TODO: Find a way to mock the steal chance of grip claw - { name: "MULTI_LENS", count: 3 }, + { name: "GRIP_CLAW", count: 1 }, ]) .enemySpecies(Species.SNORLAX) - .ability(Abilities.KLUTZ) + .enemyAbility(Abilities.UNNERVE) + .ability(Abilities.UNNERVE) .enemyMoveset(Moves.SPLASH) .enemyHeldItems([ { name: "BERRY", type: BerryType.SITRUS, count: 2 }, { name: "BERRY", type: BerryType.LUM, count: 2 }, ]) - .startingLevel(100) .enemyLevel(100); - vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100); }); - it( - "should only steal items from the attack target", - async () => { - await game.startBattle([ Species.PANSEAR, Species.ROWLET ]); + it("should steal items on contact and only from the attack target", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); - const enemyPokemon = game.scene.getEnemyField(); + const [ playerPokemon, ] = game.scene.getPlayerField(); - const enemyHeldItemCt = enemyPokemon.map(p => p.getHeldItems.length); + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - game.move.select(Moves.POPULATION_BOMB, 0, BattlerIndex.ENEMY); - game.move.select(Moves.SPLASH, 1); + const enemyPokemon = game.scene.getEnemyField(); - await game.phaseInterceptor.to(MoveEndPhase, false); + const playerHeldItemCount = getHeldItemCount(playerPokemon); + const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + expect(enemy2HeldItemCount).toBeGreaterThan(0); - expect(enemyPokemon[1].getHeldItems.length).toBe(enemyHeldItemCt[1]); - } - ); + game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); + const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + + expect(playerHeldItemCountAfter).toBe(playerHeldItemCount + 1); + expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); + expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount - 1); + }); + + it("should not steal items when using a targetted, non attack move", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + const [ playerPokemon, ] = game.scene.getPlayerField(); + + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); + + const enemyPokemon = game.scene.getEnemyField(); + + const playerHeldItemCount = getHeldItemCount(playerPokemon); + const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + expect(enemy2HeldItemCount).toBeGreaterThan(0); + + game.move.select(Moves.ATTRACT, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); + const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + + expect(playerHeldItemCountAfter).toBe(playerHeldItemCount); + expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); + expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount); + }); }); + +/* + * Gets the total number of items a Pokemon holds + */ +function getHeldItemCount(pokemon: Pokemon) { + return pokemon.getHeldItems().reduce((currentTotal, item) => currentTotal + item.getStackCount(), 0); +} + From d2c579cf2a2d22639ba1c3998f36e27f4e5d5b3a Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:32:20 +0200 Subject: [PATCH 02/19] [P2] Prevent generating Pokemon with duplicate IDs in daily runs (#4623) --- src/phases/title-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 115e4f640a2..58683cf8ec8 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -196,7 +196,7 @@ export class TitlePhase extends Phase { this.scene.gameMode = getGameMode(GameModes.DAILY); this.scene.setSeed(seed); - this.scene.resetSeed(1); + this.scene.resetSeed(0); this.scene.money = this.scene.gameMode.getStartingMoney(); From ffe941d235f6f4923bc5b87e0d29701a609c4bba Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:04:13 -0700 Subject: [PATCH 03/19] [Feature][UI] Save Preview (#4410) * Making 3 Option UI real * idk anymore * Revert "Making 3 Option UI real" This reverts commit beaad44c1eb098a09cfd2d04043d878d24f494c1. * Let's see * Current issues - scrolling upwards and correct cursor landing * argh * Fixed reactive scrolling * Adding ME handling * set up descriptions * Cleaned up UI i think * stupid alder * Added double trainer handling + changed enum name * Apply suggestions from code review Thank you Moka! Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Arrow Visibility now depends on Session Slot hasData * documentation * Simplified calls to revertSessionSlot + changed function name per feedback * Fixed scrollCursor issue. * added comment * Update src/ui/save-slot-select-ui-handler.ts Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Fixed sound played + added better conditional * Balance Team.... * ME related changes * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Update src/data/mystery-encounters/mystery-encounter.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/mystery-encounters/mystery-encounter.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Sending Doubles-fix * eslint.. --------- Co-authored-by: frutescens Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 6 +- .../encounters/a-trainers-test-encounter.ts | 1 + .../encounters/absolute-avarice-encounter.ts | 1 + .../an-offer-you-cant-refuse-encounter.ts | 1 + .../encounters/berries-abound-encounter.ts | 1 + .../encounters/bug-type-superfan-encounter.ts | 1 + .../encounters/clowning-around-encounter.ts | 1 + .../encounters/dancing-lessons-encounter.ts | 1 + .../encounters/dark-deal-encounter.ts | 1 + .../encounters/delibirdy-encounter.ts | 1 + .../department-store-sale-encounter.ts | 1 + .../encounters/field-trip-encounter.ts | 1 + .../encounters/fiery-fallout-encounter.ts | 1 + .../encounters/fight-or-flight-encounter.ts | 1 + .../encounters/fun-and-games-encounter.ts | 1 + .../global-trade-system-encounter.ts | 1 + .../encounters/lost-at-sea-encounter.ts | 1 + .../mysterious-challengers-encounter.ts | 1 + .../encounters/mysterious-chest-encounter.ts | 1 + .../encounters/part-timer-encounter.ts | 1 + .../encounters/safari-zone-encounter.ts | 1 + .../shady-vitamin-dealer-encounter.ts | 1 + .../slumbering-snorlax-encounter.ts | 1 + .../teleporting-hijinks-encounter.ts | 1 + .../the-expert-pokemon-breeder-encounter.ts | 1 + .../the-pokemon-salesman-encounter.ts | 1 + .../encounters/the-strong-stuff-encounter.ts | 1 + .../the-winstrate-challenge-encounter.ts | 1 + .../encounters/training-session-encounter.ts | 1 + .../encounters/trash-to-treasure-encounter.ts | 1 + .../encounters/uncommon-breed-encounter.ts | 1 + .../encounters/weird-dream-encounter.ts | 1 + .../mystery-encounters/mystery-encounter.ts | 14 +- src/ui/run-history-ui-handler.ts | 3 +- src/ui/run-info-ui-handler.ts | 136 +++++++++++++++--- src/ui/save-slot-select-ui-handler.ts | 94 +++++++++--- 36 files changed, 246 insertions(+), 38 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cc6934f20d1..a586b565e13 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3161,13 +3161,17 @@ export default class BattleScene extends SceneBase { /** * Loads or generates a mystery encounter * @param encounterType used to load session encounter when restarting game, etc. + * @param canBypass optional boolean to indicate that the request is coming from a function that needs to access a Mystery Encounter outside of gameplay requirements * @returns */ - getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter { + getMysteryEncounter(encounterType?: MysteryEncounterType, canBypass?: boolean): MysteryEncounter { // Loading override or session encounter let encounter: MysteryEncounter | null; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; + } else if (canBypass) { + encounter = allMysteryEncounters[encounterType ?? -1]; + return encounter; } else { encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; } diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 13e187179d4..f3b886ac0ac 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -128,6 +128,7 @@ export const ATrainersTestEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index c98947a3f93..70b2d50fe99 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -166,6 +166,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index e445a8f481d..ab892ae00f2 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -64,6 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 3e5d75727b1..095f8a8473b 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -110,6 +110,7 @@ export const BerriesAboundEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 20c0569c725..b5d47cf6912 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -276,6 +276,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 6c028d4619a..be52ab42c9d 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -148,6 +148,7 @@ export const ClowningAroundEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index cb07bf06a81..0f784739777 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -102,6 +102,7 @@ export const DancingLessonsEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index fc8c8088d58..5ad6630386f 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -117,6 +117,7 @@ export const DarkDealEncounter: MysteryEncounter = .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withCatchAllowed(true) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index a11dc8cbe72..5686d0f6ce5 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -84,6 +84,7 @@ export const DelibirdyEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 1505768f968..10034d19263 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -51,6 +51,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index a75e5ef6a77..bf5fb28163b 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -52,6 +52,7 @@ export const FieldTripEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 9e7652e24ea..d44e7bae596 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -122,6 +122,7 @@ export const FieryFalloutEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index a04521839fe..380662ca817 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -120,6 +120,7 @@ export const FightOrFlightEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 2b103e0a293..549faa01fa1 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -76,6 +76,7 @@ export const FunAndGamesEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 7b929ea5e7b..bafc1901e5e 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -96,6 +96,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 6ca131543b4..8fd46982dc1 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -50,6 +50,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 08536f44245..fb25976ebd8 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -125,6 +125,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 2b44f6ee33d..1eb1c4cb13e 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -61,6 +61,7 @@ export const MysteriousChestEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 2f41aa96677..17a3a366569 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -69,6 +69,7 @@ export const PartTimerEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index d029460e617..c6b04b7aca6 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -54,6 +54,7 @@ export const SafariZoneEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 89cb572962c..c70048ade07 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -62,6 +62,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index d0b4cc13301..3a4bf465a78 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -88,6 +88,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 63ab178c52a..01e241f63d4 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -58,6 +58,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index aca46f1598b..4515736b30a 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -196,6 +196,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 2720653e654..95f359547e4 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -53,6 +53,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index d1d5b484129..7ee57d36027 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -117,6 +117,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index ad4f2dd8498..c7cb23fe6f8 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -94,6 +94,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index ff993f339cb..10bb956636b 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -52,6 +52,7 @@ export const TrainingSessionEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index be2cd796386..c2a0426bceb 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -54,6 +54,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 51c1d5f963f..13594f273d9 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -125,6 +125,7 @@ export const UncommonBreedEncounter: MysteryEncounter = scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 17f33c27645..6e2f8352480 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -125,6 +125,7 @@ export const WeirdDreamEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index aabb0a3311a..7e175957e21 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -190,7 +190,7 @@ export default class MysteryEncounter implements IMysteryEncounter { secondaryPokemon?: PlayerPokemon[]; // #region Post-construct / Auto-populated params - + localizationKey: string; /** * Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter */ @@ -264,6 +264,7 @@ export default class MysteryEncounter implements IMysteryEncounter { Object.assign(this, encounter); } this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON; + this.localizationKey = this.localizationKey ?? ""; this.dialogue = this.dialogue ?? {}; this.spriteConfigs = this.spriteConfigs ? [ ...this.spriteConfigs ] : []; // Default max is 1 for ROGUE encounters, 2 for others @@ -528,6 +529,7 @@ export class MysteryEncounterBuilder implements Partial { options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]]; enemyPartyConfigs: EnemyPartyConfig[] = []; + localizationKey: string = ""; dialogue: MysteryEncounterDialogue = {}; requirements: EncounterSceneRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = []; @@ -632,6 +634,16 @@ export class MysteryEncounterBuilder implements Partial { return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue); } + /** + * Sets the localization key used by the encounter + * @param localizationKey the string used as the key + * @returns `this` + */ + setLocalizationKey(localizationKey: string): this { + this.localizationKey = localizationKey; + return this; + } + /** * OPTIONAL */ diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index f4de9b21963..20de7fd832c 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -12,6 +12,7 @@ import { BattleType } from "../battle"; import { RunEntry } from "../system/game-data"; import { PlayerGender } from "#enums/player-gender"; import { TrainerVariant } from "../field/trainer"; +import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; export type RunSelectCallback = (cursor: number) => void; @@ -104,7 +105,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { if (button === Button.ACTION) { const cursor = this.cursor + this.scrollCursor; if (this.runs[cursor]) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, true); + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, RunDisplayMode.RUN_HISTORY, true); } else { return false; } diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index d5f04f90e5b..39927f8e071 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -5,6 +5,7 @@ import { SessionSaveData } from "../system/game-data"; import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import { getPokeballAtlasKey } from "#app/data/pokeball"; import * as Utils from "../utils"; import PokemonData from "../system/pokemon-data"; import i18next from "i18next"; @@ -22,6 +23,8 @@ import * as Modifier from "../modifier/modifier"; import { Species } from "#enums/species"; import { PlayerGender } from "#enums/player-gender"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { getBiomeName } from "#app/data/balance/biomes"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; /** * RunInfoUiMode indicates possible overlays of RunInfoUiHandler. @@ -34,6 +37,11 @@ enum RunInfoUiMode { ENDING_ART } +export enum RunDisplayMode { + RUN_HISTORY, + SESSION_PREVIEW +} + /** * Some variables are protected because this UI class will most likely be extended in the future to display more information. * These variables will most likely be shared across 'classes' aka pages. @@ -41,6 +49,7 @@ enum RunInfoUiMode { * For now, I leave as is. */ export default class RunInfoUiHandler extends UiHandler { + protected runDisplayMode: RunDisplayMode; protected runInfo: SessionSaveData; protected isVictory: boolean; protected pageMode: RunInfoUiMode; @@ -66,6 +75,7 @@ export default class RunInfoUiHandler extends UiHandler { // The import of the modifiersModule is loaded here to sidestep async/await issues. this.modifiersModule = Modifier; this.runContainer.setVisible(false); + this.scene.loadImage("encounter_exclaim", "mystery-encounters"); } /** @@ -87,9 +97,15 @@ export default class RunInfoUiHandler extends UiHandler { this.runContainer.add(gameStatsBg); const run = args[0]; + this.runDisplayMode = args[1]; + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); + this.isVictory = run.isVictory ?? false; + } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { + this.runInfo = args[0]; + } // Assigning information necessary for the UI's creation - this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); - this.isVictory = run.isVictory; + this.pageMode = RunInfoUiMode.MAIN; // Creates Header and adds to this.runContainer @@ -102,7 +118,11 @@ export default class RunInfoUiHandler extends UiHandler { const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65); runResultWindow.setOrigin(0, 0); this.runResultContainer.add(runResultWindow); - this.parseRunResult(); + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + this.parseRunResult(); + } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { + this.parseRunStatus(); + } // Creates Run Info Container this.runInfoContainer = this.scene.add.container(0, 89); @@ -226,6 +246,66 @@ export default class RunInfoUiHandler extends UiHandler { this.runContainer.add(this.runResultContainer); } + /** + * This function is used when the Run Info UI is used to preview a Session. + * It edits {@linkcode runResultContainer}, but most importantly - does not display the negative results of a Mystery Encounter or any details of a trainer's party. + * Trainer Parties are replaced with their sprites, names, and their party size. + * Mystery Encounters contain sprites associated with MEs + the title of the specific ME. + */ + private parseRunStatus() { + const runStatusText = addTextObject(this.scene, 6, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex} - ${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize : "65px", lineSpacing: 0.1 }); + + const enemyContainer = this.scene.add.container(0, 0); + this.runResultContainer.add(enemyContainer); + if (this.runInfo.battleType === BattleType.WILD) { + if (this.runInfo.enemyParty.length === 1) { + this.parseWildSingleDefeat(enemyContainer); + } else if (this.runInfo.enemyParty.length === 2) { + this.parseWildDoubleDefeat(enemyContainer); + } + } else if (this.runInfo.battleType === BattleType.TRAINER) { + this.showTrainerSprites(enemyContainer); + const row_limit = 3; + this.runInfo.enemyParty.forEach((p, i) => { + const pokeball = this.scene.add.sprite(0, 0, "pb"); + pokeball.setFrame(getPokeballAtlasKey(p.pokeball)); + pokeball.setScale(0.5); + pokeball.setPosition(52 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25); + enemyContainer.add(pokeball); + }); + const trainerObj = this.runInfo.trainer.toTrainer(this.scene); + const RIVAL_TRAINER_ID_THRESHOLD = 375; + let trainerName = ""; + if (this.runInfo.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) { + trainerName = (trainerObj.variant === TrainerVariant.FEMALE) ? i18next.t("trainerNames:rival_female") : i18next.t("trainerNames:rival"); + } else { + trainerName = trainerObj.getName(0, true); + } + const boxString = i18next.t(trainerObj.variant !== TrainerVariant.DOUBLE ? "battle:trainerAppeared" : "battle:trainerAppearedDouble", { trainerName: trainerName }).replace(/\n/g, " "); + const descContainer = this.scene.add.container(0, 0); + const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }}); + descContainer.add(textBox); + descContainer.setPosition(52, 29); + this.runResultContainer.add(descContainer); + } else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) { + const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim"); + encounterExclaim.setPosition(34, 26); + encounterExclaim.setScale(0.65); + const subSprite = this.scene.add.sprite(56, -106, "pkmn__sub"); + subSprite.setScale(0.65); + subSprite.setPosition(34, 46); + const mysteryEncounterTitle = i18next.t(this.scene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true).localizationKey + ":title"); + const descContainer = this.scene.add.container(0, 0); + const textBox = addTextObject(this.scene, 0, 0, mysteryEncounterTitle, TextStyle.WINDOW, { fontSize : "45px", wordWrap: { width: 160 }}); + descContainer.add(textBox); + descContainer.setPosition(47, 37); + this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]); + } + + this.runResultContainer.add(runStatusText); + this.runContainer.add(this.runResultContainer); + } + /** * This function is called to edit an enemyContainer to represent a loss from a defeat by a wild single Pokemon battle. * @param enemyContainer - container holding enemy visual and level information @@ -278,40 +358,58 @@ export default class RunInfoUiHandler extends UiHandler { } /** - * This edits a container to represent a loss from a defeat by a trainer battle. - * @param enemyContainer - container holding enemy visuals and level information - * The trainers are placed to the left of their party. - * Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...) - * - * Party Pokemon have their icons, terastalization status, and level shown. + * This loads the enemy sprites, positions, and scales them according to the current display mode of the RunInfo UI and then adds them to the container parameter. + * Used by {@linkcode parseRunStatus} and {@linkcode parseTrainerDefeat} + * @param enemyContainer a Phaser Container that should hold enemy sprites */ - private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { + private showTrainerSprites(enemyContainer: Phaser.GameObjects.Container) { // Creating the trainer sprite and adding it to enemyContainer const tObj = this.runInfo.trainer.toTrainer(this.scene); - // Loads trainer assets on demand, as they are not loaded by default in the scene tObj.config.loadAssets(this.scene, this.runInfo.trainer.variant).then(() => { const tObjSpriteKey = tObj.config.getSpriteKey(this.runInfo.trainer.variant === TrainerVariant.FEMALE, false); const tObjSprite = this.scene.add.sprite(0, 5, tObjSpriteKey); - if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE) { + if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE && !tObj.config.doubleOnly) { const doubleContainer = this.scene.add.container(5, 8); tObjSprite.setPosition(-3, -3); const tObjPartnerSpriteKey = tObj.config.getSpriteKey(true, true); const tObjPartnerSprite = this.scene.add.sprite(5, -3, tObjPartnerSpriteKey); // Double Trainers have smaller sprites than Single Trainers - tObjPartnerSprite.setScale(0.20); - tObjSprite.setScale(0.20); - doubleContainer.add(tObjSprite); - doubleContainer.add(tObjPartnerSprite); - doubleContainer.setPosition(12, 38); + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + tObjPartnerSprite.setScale(0.20); + tObjSprite.setScale(0.20); + doubleContainer.add(tObjSprite); + doubleContainer.add(tObjPartnerSprite); + doubleContainer.setPosition(12, 38); + } else { + tObjSprite.setScale(0.55); + tObjSprite.setPosition(-9, -3); + tObjPartnerSprite.setScale(0.55); + doubleContainer.add([ tObjSprite, tObjPartnerSprite ]); + doubleContainer.setPosition(28, 40); + } enemyContainer.add(doubleContainer); } else { - tObjSprite.setScale(0.35, 0.35); - tObjSprite.setPosition(12, 28); + const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.65; + const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 32, 36 ]; + tObjSprite.setScale(scale, scale); + tObjSprite.setPosition(position[0], position[1]); enemyContainer.add(tObjSprite); } }); + } + /** + * This edits a container to represent a loss from a defeat by a trainer battle. + * The trainers are placed to the left of their party. + * Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...) + * + * Party Pokemon have their icons, terastalization status, and level shown. + * @param enemyContainer - container holding enemy visuals and level information + */ + private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { + // Loads and adds trainer sprites to the UI + this.showTrainerSprites(enemyContainer); // Determining which Terastallize Modifier belongs to which Pokemon // Creates a dictionary {PokemonId: TeraShardType} const teraPokemon = {}; diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 89b20322a68..bd1a7dd9ac4 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -10,6 +10,7 @@ import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; const sessionSlotCount = 5; @@ -33,7 +34,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { private scrollCursor: integer = 0; - private cursorObj: Phaser.GameObjects.NineSlice | null; + private cursorObj: Phaser.GameObjects.Container | null; private sessionSlotsContainerInitialY: number; @@ -83,9 +84,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectCallback = args[1] as SaveSlotSelectCallback; this.saveSlotSelectContainer.setVisible(true); - this.populateSessionSlots(); - this.setScrollCursor(0); - this.setCursor(0); + this.populateSessionSlots() + .then(() => { + this.setScrollCursor(0); + this.setCursor(0); + }); return true; } @@ -147,21 +150,28 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = true; } } else { + const cursorPosition = this.cursor + this.scrollCursor; switch (button) { case Button.UP: if (this.cursor) { - success = this.setCursor(this.cursor - 1); + // Check to prevent cursor from accessing a negative index + success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); + success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); } break; case Button.DOWN: if (this.cursor < 2) { - success = this.setCursor(this.cursor + 1); + success = this.setCursor(this.cursor + 1, this.cursor); } else if (this.scrollCursor < sessionSlotCount - 3) { - success = this.setScrollCursor(this.scrollCursor + 1); + success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); } break; + case Button.RIGHT: + if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); + success = true; + } } } @@ -174,10 +184,10 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { return success || error; } - populateSessionSlots() { + async populateSessionSlots() { for (let s = 0; s < sessionSlotCount; s++) { const sessionSlot = new SessionSlot(this.scene, s); - sessionSlot.load(); + await sessionSlot.load(); this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); @@ -198,25 +208,74 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectMessageBoxContainer.setVisible(!!text?.length); } - setCursor(cursor: integer): boolean { + /** + * setCursor takes user navigation as an input and positions the cursor accordingly + * @param cursor the index provided to the cursor + * @param prevCursor the previous index occupied by the cursor - optional + * @returns `true` if the cursor position has changed | `false` if it has not + */ + override setCursor(cursor: integer, prevCursor?: integer): boolean { const changed = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); - this.cursorObj.setOrigin(0, 0); + this.cursorObj = this.scene.add.container(0, 0); + const cursorBox = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); + const rightArrow = this.scene.add.image(0, 0, "cursor"); + rightArrow.setPosition(160, 0); + rightArrow.setName("rightArrow"); + this.cursorObj.add([ cursorBox, rightArrow ]); this.sessionSlotsContainer.add(this.cursorObj); } - this.cursorObj.setPosition(4, 4 + (cursor + this.scrollCursor) * 56); + const cursorPosition = cursor + this.scrollCursor; + const cursorIncrement = cursorPosition * 56; + if (this.sessionSlots[cursorPosition] && this.cursorObj) { + const hasData = this.sessionSlots[cursorPosition].hasData; + // If the session slot lacks session data, it does not move from its default, central position. + // Only session slots with session data will move leftwards and have a visible arrow. + if (!hasData) { + this.cursorObj.setPosition(151, 26 + cursorIncrement); + this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement); + } else { + this.cursorObj.setPosition(145, 26 + cursorIncrement); + this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement); + } + this.setArrowVisibility(hasData); + } + if (!Utils.isNullOrUndefined(prevCursor)) { + this.revertSessionSlot(prevCursor); + } return changed; } - setScrollCursor(scrollCursor: integer): boolean { + /** + * Helper function that resets the session slot position to its default central position + * @param prevCursor the previous location of the cursor + */ + revertSessionSlot(prevCursor: integer): void { + const sessionSlot = this.sessionSlots[prevCursor]; + if (sessionSlot) { + sessionSlot.setPosition(0, prevCursor * 56); + } + } + + /** + * Helper function that checks if the session slot involved holds data or not + * @param hasData `true` if session slot contains data | 'false' if not + */ + setArrowVisibility(hasData: boolean): void { + if (this.cursorObj) { + const rightArrow = this.cursorObj?.getByName("rightArrow") as Phaser.GameObjects.Image; + rightArrow.setVisible(hasData); + } + } + + setScrollCursor(scrollCursor: integer, priorCursor?: integer): boolean { const changed = scrollCursor !== this.scrollCursor; if (changed) { this.scrollCursor = scrollCursor; - this.setCursor(this.cursor); + this.setCursor(this.cursor, priorCursor); this.scene.tweens.add({ targets: this.sessionSlotsContainer, y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, @@ -254,6 +313,8 @@ class SessionSlot extends Phaser.GameObjects.Container { public hasData: boolean; private loadingLabel: Phaser.GameObjects.Text; + public saveData: SessionSaveData; + constructor(scene: BattleScene, slotId: integer) { super(scene, 0, slotId * 56); @@ -337,6 +398,7 @@ class SessionSlot extends Phaser.GameObjects.Container { return; } this.hasData = true; + this.saveData = sessionData; await this.setupWithData(sessionData); resolve(true); }); From f180b6070e8f61ca06fc50a12665bab961070653 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:01:49 -0700 Subject: [PATCH 04/19] [Qol] Load i18n en locales during tests (#4553) * add: i18n backend support the backend is being supported by using msw which will import the correct file from the local locales folder * fix: tests to no longer rely on static i18n keys * Update src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/ui/type-hints.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Fix typos Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Fix linting * update locales submodule update reference to `56eeb809eb5a2de40cfc5bc6128a78bef14deea9` (from `3ccef8472dd7cc7c362538489954cb8fdad27e5f`) --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- global.d.ts | 14 +++ src/test/abilities/ability_timing.test.ts | 10 +- src/test/items/toxic_orb.test.ts | 15 ++- .../a-trainers-test-encounter.test.ts | 4 +- .../absolute-avarice-encounter.test.ts | 3 +- ...an-offer-you-cant-refuse-encounter.test.ts | 5 +- .../encounters/field-trip-encounter.test.ts | 32 +++---- .../fiery-fallout-encounter.test.ts | 3 +- .../encounters/lost-at-sea-encounter.test.ts | 5 +- .../teleporting-hijinks-encounter.test.ts | 33 +++---- .../phases/mystery-encounter-phase.test.ts | 7 +- src/test/system/game_data.test.ts | 5 +- src/test/ui/starter-select.test.ts | 91 ++++++++++--------- src/test/ui/type-hints.test.ts | 7 +- src/test/vitest.setup.ts | 58 +++++++----- 15 files changed, 166 insertions(+), 126 deletions(-) create mode 100644 global.d.ts diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 00000000000..f4dfa7d4cb2 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,14 @@ +import type { SetupServerApi } from "msw/node"; + +export {}; + +declare global { + /** + * Only used in testing. + * Can technically be undefined/null but for ease of use we are going to assume it is always defined. + * Used to load i18n files exclusively. + * + * To set up your own server in a test see `game_data.test.ts` + */ + var i18nServer: SetupServerApi; +} diff --git a/src/test/abilities/ability_timing.test.ts b/src/test/abilities/ability_timing.test.ts index 1472f9eb429..e3264c2c1a8 100644 --- a/src/test/abilities/ability_timing.test.ts +++ b/src/test/abilities/ability_timing.test.ts @@ -1,13 +1,13 @@ import { BattleStyle } from "#app/enums/battle-style"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import i18next, { initI18n } from "#app/plugins/i18n"; +import i18next from "#app/plugins/i18n"; import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Ability Timing", () => { @@ -32,11 +32,10 @@ describe("Ability Timing", () => { .enemySpecies(Species.MAGIKARP) .enemyAbility(Abilities.INTIMIDATE) .ability(Abilities.BALL_FETCH); + vi.spyOn(i18next, "t"); }); it("should trigger after switch check", async () => { - initI18n(); - i18next.changeLanguage("en"); game.settings.battleStyle = BattleStyle.SWITCH; await game.classicMode.runToSummon([ Species.EEVEE, Species.FEEBAS ]); @@ -46,7 +45,6 @@ describe("Ability Timing", () => { }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); await game.phaseInterceptor.to("MessagePhase"); - const message = game.textInterceptor.getLatestMessage(); - expect(message).toContain("battle:statFell"); + expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 })); }, 5000); }); diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 35d6e77b209..a83fd3655e5 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -2,13 +2,13 @@ import { StatusEffect } from "#app/data/status-effect"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { MessagePhase } from "#app/phases/message-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; -import i18next, { initI18n } from "#app/plugins/i18n"; +import i18next from "#app/plugins/i18n"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Items - Toxic orb", () => { @@ -39,11 +39,11 @@ describe("Items - Toxic orb", () => { game.override.startingHeldItems([{ name: "TOXIC_ORB", }]); + + vi.spyOn(i18next, "t"); }); it("TOXIC ORB", async () => { - initI18n(); - i18next.changeLanguage("en"); const moveToUse = Moves.GROWTH; await game.startBattle([ Species.MIGHTYENA, @@ -57,11 +57,10 @@ describe("Items - Toxic orb", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); // Toxic orb should trigger here await game.phaseInterceptor.run(MessagePhase); - const message = game.textInterceptor.getLatestMessage(); - expect(message).toContain("statusEffect:toxic.obtainSource"); + expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything()); + await game.phaseInterceptor.run(MessagePhase); - const message2 = game.textInterceptor.getLatestMessage(); - expect(message2).toBe("statusEffect:toxic.activation"); + expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.activation", expect.anything()); expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC); }, 20000); }); diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index f24800eaa71..b1aa378d82a 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -16,6 +16,7 @@ import { EggTier } from "#enums/egg-type"; import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/aTrainersTest"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -106,7 +107,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(scene.currentBattle.trainer).toBeDefined(); - expect([ "trainerNames:buck", "trainerNames:cheryl", "trainerNames:marley", "trainerNames:mira", "trainerNames:riley" ].includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); + expect([ i18next.t("trainerNames:buck"), i18next.t("trainerNames:cheryl"), i18next.t("trainerNames:marley"), i18next.t("trainerNames:mira"), i18next.t("trainerNames:riley") ] + .map(name => name.toLowerCase()).includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); expect(enemyField[0]).toBeDefined(); }); diff --git a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 99a835cb6ae..a72a9fbb5a3 100644 --- a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -16,6 +16,7 @@ import { Moves } from "#enums/moves"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/absoluteAvarice"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -146,7 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { const pokemonId = partyPokemon.id; const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[]; - const revSeed = pokemonItems.find(i => i.type.name === "modifierType:ModifierType.REVIVER_SEED.name"); + const revSeed = pokemonItems.find(i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name")); expect(revSeed).toBeDefined; expect(revSeed?.stackCount).toBe(1); } diff --git a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 77d5a842b47..9883b4332b9 100644 --- a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -17,6 +17,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/anOfferYouCantRefuse"; /** Gyarados for Indimidate */ @@ -93,8 +94,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined(); - expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("ability:intimidate.name"); - expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("ability:intimidate.name"); + expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe(i18next.t("ability:intimidate.name")); + expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe(i18next.t("ability:intimidate.name")); expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy(); expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price); expect(onInitResult).toBe(true); diff --git a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts index 232bad3c2b8..a6f925274c3 100644 --- a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -103,11 +103,11 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_attack"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_defense"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_attack")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_defense")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { @@ -150,11 +150,11 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_atk"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_def"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_def")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { @@ -198,12 +198,12 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_accuracy"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:ModifierType.AddPokeballModifierType.name"); - expect(i18next.t).toHaveBeenCalledWith("modifierType:ModifierType.AddPokeballModifierType.name", expect.objectContaining({ modifierCount: 5 })); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.IV_SCANNER.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { modifierCount: 5, pokeballName: i18next.t("pokeball:greatBall") })); + expect(i18next.t).toHaveBeenCalledWith(("modifierType:ModifierType.AddPokeballModifierType.name"), expect.objectContaining({ modifierCount: 5 })); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.IV_SCANNER.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 3d2533c0817..a4f303d121f 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -22,6 +22,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ @@ -205,7 +206,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE)); const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE)); - expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe("pokemon:gengar"); + expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe(i18next.t("pokemon:gengar")); burnablePokemon.forEach((pkm) => { expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2)); }); diff --git a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 456c18c572d..dec14d46cc8 100644 --- a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -14,6 +14,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import BattleScene from "#app/battle-scene"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/lostAtSea"; @@ -86,8 +87,8 @@ describe("Lost at Sea - Mystery Encounter", () => { const onInitResult = onInit!(scene); expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25"); - expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe("move:surf.name"); - expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe("move:fly.name"); + expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(i18next.t("move:surf.name")); + expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe(i18next.t("move:fly.name")); expect(onInitResult).toBe(true); }); diff --git a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 2411752baa7..02375d83b98 100644 --- a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -1,21 +1,22 @@ -import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { Biome } from "#app/enums/biome"; -import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; -import { Species } from "#app/enums/species"; -import GameManager from "#app/test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import BattleScene from "#app/battle-scene"; +import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; +import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; +import { Abilities } from "#enums/abilities"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import { CommandPhase } from "#app/phases/command-phase"; +import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import GameManager from "#test/utils/gameManager"; +import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { CommandPhase } from "#app/phases/command-phase"; -import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import { Mode } from "#app/ui/ui"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { Abilities } from "#app/enums/abilities"; +import i18next from "i18next"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/teleportingHijinks"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -300,8 +301,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.metal_coat")).toBe(true); - expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.magnet")).toBe(true); + expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"))).toBe(true); + expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"))).toBe(true); }); }); }); diff --git a/src/test/phases/mystery-encounter-phase.test.ts b/src/test/phases/mystery-encounter-phase.test.ts index 4468045756b..32e31ce1c94 100644 --- a/src/test/phases/mystery-encounter-phase.test.ts +++ b/src/test/phases/mystery-encounter-phase.test.ts @@ -9,6 +9,7 @@ import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import i18next from "i18next"; describe("Mystery Encounter Phases", () => { let phaserGame: Phaser.Game; @@ -78,9 +79,9 @@ describe("Mystery Encounter Phases", () => { expect(ui.getMode()).toBe(Mode.MESSAGE); expect(ui.showDialogue).toHaveBeenCalledTimes(1); expect(ui.showText).toHaveBeenCalledTimes(2); - expect(ui.showDialogue).toHaveBeenCalledWith("battle:mysteryEncounterAppeared", "???", null, expect.any(Function)); - expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:intro", null, expect.any(Function), 750, true); - expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:option.selected", null, expect.any(Function), 300, true); + expect(ui.showDialogue).toHaveBeenCalledWith(i18next.t("battle:mysteryEncounterAppeared"), "???", null, expect.any(Function)); + expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:intro"), null, expect.any(Function), 750, true); + expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:option.selected"), null, expect.any(Function), 300, true); }); }); diff --git a/src/test/system/game_data.test.ts b/src/test/system/game_data.test.ts index 5eb4dea3910..fcb7e9067a3 100644 --- a/src/test/system/game_data.test.ts +++ b/src/test/system/game_data.test.ts @@ -11,13 +11,15 @@ import * as account from "../../account"; const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001"; -export const server = setupServer(); +/** We need a custom server. For some reasons I can't extend the listeners of {@linkcode global.i18nServer} with {@linkcode global.i18nServer.use} */ +const server = setupServer(); describe("System - Game Data", () => { let phaserGame: Phaser.Game; let game: GameManager; beforeAll(() => { + global.i18nServer.close(); server.listen(); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -26,6 +28,7 @@ describe("System - Game Data", () => { afterAll(() => { server.close(); + global.i18nServer.listen(); }); beforeEach(() => { diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index dd0761be392..94370ca1b74 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -14,6 +14,7 @@ import { Abilities } from "#enums/abilities"; import { Button } from "#enums/buttons"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -66,11 +67,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -127,11 +128,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -191,11 +192,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -254,11 +255,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -315,11 +316,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -376,11 +377,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -436,11 +437,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -496,11 +497,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); let starterSelectUiHandler: StarterSelectUiHandler; @@ -561,11 +562,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); let starterSelectUiHandler: StarterSelectUiHandler | undefined; diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts index 450f43f1263..2977262dda7 100644 --- a/src/test/ui/type-hints.test.ts +++ b/src/test/ui/type-hints.test.ts @@ -7,7 +7,8 @@ import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import MockText from "../utils/mocks/mocksContainer/mockText"; +import MockText from "#test/utils/mocks/mocksContainer/mockText"; +import i18next from "i18next"; describe("UI - Type Hints", () => { let phaserGame: Phaser.Game; @@ -53,7 +54,7 @@ describe("UI - Type Hints", () => { const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); const dragonClawText = movesContainer .getAll() - .find((text) => text.text === "move:dragonClaw.name")! as unknown as MockText; + .find((text) => text.text === i18next.t("move:dragonClaw.name"))! as unknown as MockText; expect.soft(dragonClawText.color).toBe("#929292"); ui.getHandler().processInput(Button.ACTION); @@ -78,7 +79,7 @@ describe("UI - Type Hints", () => { const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); const growlText = movesContainer .getAll() - .find((text) => text.text === "move:growl.name")! as unknown as MockText; + .find((text) => text.text === i18next.t("move:growl.name"))! as unknown as MockText; expect.soft(growlText.color).toBe(undefined); ui.getHandler().processInput(Button.ACTION); diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 0d67d6787c4..8438f607db2 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -4,16 +4,17 @@ import { initLoggedInUser } from "#app/account"; import { initAbilities } from "#app/data/ability"; import { initBiomes } from "#app/data/balance/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves"; +import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initMoves } from "#app/data/move"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; -import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initPokemonForms } from "#app/data/pokemon-forms"; import { initSpecies } from "#app/data/pokemon-species"; import { initAchievements } from "#app/system/achv"; import { initVouchers } from "#app/system/voucher"; import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; -import { beforeAll, vi } from "vitest"; +import { afterAll, beforeAll, vi } from "vitest"; +/** Set the timezone to UTC for tests. */ process.env.TZ = "UTC"; /** Mock the override import to always return default values, ignoring any custom overrides. */ @@ -26,26 +27,36 @@ vi.mock("#app/overrides", async (importOriginal) => { } satisfies typeof import("#app/overrides"); }); -vi.mock("i18next", () => ({ - default: { - use: () => {}, - t: (key: string) => key, - changeLanguage: () => Promise.resolve(), - init: () => Promise.resolve(), - resolvedLanguage: "en", - exists: () => true, - getDataByLanguage:() => ({ - en: { - keys: [ "foo" ] - }, - }), - services: { - formatter: { - add: () => {}, +/** + * This is a hacky way to mock the i18n backend requests (with the help of {@link https://mswjs.io/ | msw}). + * The reason to put it inside of a mock is to elevate it. + * This is necessary because how our code is structured. + * Do NOT try to put any of this code into external functions, it won't work as it's elevated during runtime. + */ +vi.mock("i18next", async (importOriginal) => { + console.log("Mocking i18next"); + const { setupServer } = await import("msw/node"); + const { http, HttpResponse } = await import("msw"); + + global.i18nServer = setupServer( + http.get("/locales/en/*", async (req) => { + const filename = req.params[0]; + + try { + const json = await import(`../../public/locales/en/${req.params[0]}`); + console.log("Loaded locale", filename); + return HttpResponse.json(json); + } catch (err) { + console.log(`Failed to load locale ${filename}!`, err); + return HttpResponse.json({}); } - }, - }, -})); + }) + ); + global.i18nServer.listen({ onUnhandledRequest: "error" }); + console.log("i18n MSW server listening!"); + + return await importOriginal(); +}); initVouchers(); initAchievements(); @@ -70,3 +81,8 @@ beforeAll(() => { }, }); }); + +afterAll(() => { + global.i18nServer.close(); + console.log("Closing i18n MSW server!"); +}); From ca3cc3c9c6a569572942516f72edd8610c76b6c5 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:28:26 -0400 Subject: [PATCH 05/19] [P1 Bug] Fix infinite recursion from abilities disabled by Sheer Force (#4631) --- src/field/pokemon.ts | 4 ++-- src/test/abilities/sheer_force.test.ts | 27 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 241524df1b9..35f389b58a4 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1417,10 +1417,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns {boolean} Whether the ability is present and active */ hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean { - if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) { + if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) { + if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { return true; } return false; diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index a3add0a9964..a2600476d6d 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -9,6 +9,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { allMoves } from "#app/data/move"; describe("Abilities - Sheer Force", () => { @@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => { }, 20000); + it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => { + const moveToUse = Moves.CRUNCH; + game.override.enemyAbility(Abilities.COLOR_CHANGE) + .ability(Abilities.COLOR_CHANGE) + .moveset(moveToUse) + .enemyMoveset(moveToUse); + + await game.classicMode.startBattle([ + Species.PIDGEOT + ]); + + const pidgeot = game.scene.getParty()[0]; + const onix = game.scene.getEnemyParty()[0]; + + pidgeot.stats[Stat.DEF] = 10000; + onix.stats[Stat.DEF] = 10000; + + game.move.select(moveToUse); + await game.toNextTurn(); + + // Check that both Pokemon's Color Change activated + const expectedTypes = [ allMoves[moveToUse].type ]; + expect(pidgeot.getTypes()).toStrictEqual(expectedTypes); + expect(onix.getTypes()).toStrictEqual(expectedTypes); + }); + //TODO King's Rock Interaction Unit Test }); From 52257def2fa65aa09018800b29e89864572fc8b0 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:30:19 -0700 Subject: [PATCH 06/19] [P3] Fix enemy used PP flyout, fixes #4622 (#4629) Also add missing function return types --- src/phases/move-phase.ts | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 10cc062ea3b..94093188571 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { Type } from "#app/data/type"; import { getTerrainBlockMessage } from "#app/data/weather"; -import { Abilities } from "#app/enums/abilities"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { Moves } from "#app/enums/moves"; -import { StatusEffect } from "#app/enums/status-effect"; import { MoveUsedEvent } from "#app/events/battle-scene"; import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import * as Utils from "#app/utils"; +import { BooleanHolder, NumberHolder } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; export class MovePhase extends BattlePhase { @@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase { this.cancelled = true; } - public start() { + public start(): void { super.start(); console.log(Moves[this.move.moveId]); @@ -140,7 +140,7 @@ export class MovePhase extends BattlePhase { } /** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */ - protected resolveFinalPreMoveCancellationChecks() { + protected resolveFinalPreMoveCancellationChecks(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); @@ -150,14 +150,14 @@ export class MovePhase extends BattlePhase { } } - public getActiveTargetPokemon() { + public getActiveTargetPokemon(): Pokemon[] { return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex())); } /** * Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects. */ - protected resolvePreMoveStatusEffects() { + protected resolvePreMoveStatusEffects(): void { if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { this.pokemon.status.incrementTurn(); let activated = false; @@ -198,7 +198,7 @@ export class MovePhase extends BattlePhase { * Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed. * Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful. */ - protected lapsePreMoveAndMoveTags() { + protected lapsePreMoveAndMoveTags(): void { this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); // TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked? @@ -207,7 +207,7 @@ export class MovePhase extends BattlePhase { } } - protected useMove() { + protected useMove(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); @@ -217,7 +217,8 @@ export class MovePhase extends BattlePhase { this.showMoveText(); // TODO: Clean up implementation of two-turn moves. - if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used + if (moveQueue.length > 0) { + // Using .shift here clears out two turn moves once they've been used this.ignorePp = moveQueue.shift()?.ignorePP ?? false; } @@ -226,7 +227,7 @@ export class MovePhase extends BattlePhase { const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); this.move.usePp(ppUsed); - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } // Update the battle's "last move" pointer, unless we're currently mimicking a move. @@ -275,7 +276,7 @@ export class MovePhase extends BattlePhase { this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); let failedText: string | undefined; - const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false)); + const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false)); if (failureMessage) { failedText = failureMessage; @@ -299,7 +300,7 @@ export class MovePhase extends BattlePhase { * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * then ends the phase. */ - public end() { + public end(): void { if (!this.followUp && this.canMove()) { this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); } @@ -313,7 +314,7 @@ export class MovePhase extends BattlePhase { * * TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability. */ - public getPpIncreaseFromPressure(targets: Pokemon[]) { + public getPpIncreaseFromPressure(targets: Pokemon[]): number { const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); return foesWithPressure.length; } @@ -323,10 +324,10 @@ export class MovePhase extends BattlePhase { * - Move redirection abilities, effects, etc. * - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`). */ - protected resolveRedirectTarget() { + protected resolveRedirectTarget(): void { if (this.targets.length === 1) { const currentTarget = this.targets[0]; - const redirectTarget = new Utils.NumberHolder(currentTarget); + const redirectTarget = new NumberHolder(currentTarget); // check move redirection abilities of every pokemon *except* the user. this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); @@ -372,7 +373,7 @@ export class MovePhase extends BattlePhase { * If there is no last attacker, or they are no longer on the field, a message is displayed and the * move is marked for failure. */ - protected resolveCounterAttackTarget() { + protected resolveCounterAttackTarget(): void { if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.pokemon.turnData.attacksReceived.length) { this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex; @@ -411,7 +412,7 @@ export class MovePhase extends BattlePhase { * * TODO: handle charge moves more gracefully */ - protected handlePreMoveFailures() { + protected handlePreMoveFailures(): void { if (this.cancelled || this.failed) { if (this.failed) { const ppUsed = this.ignorePp ? 0 : 1; From e9906ea2293171aa8b32b81ee1d1a0a77254b3f2 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:31:10 -0700 Subject: [PATCH 07/19] [P2] Obstruct/Kings Shield/etc no longer reduce stats through Clear Body/etc (#4627) * bug fix * Add test --------- Co-authored-by: frutescens Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 2 +- src/test/moves/obstruct.test.ts | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a54a8c5f519..6307b3d28be 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1376,7 +1376,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { const effectPhase = pokemon.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels)); } } diff --git a/src/test/moves/obstruct.test.ts b/src/test/moves/obstruct.test.ts index fbb5437b43a..1649c199e32 100644 --- a/src/test/moves/obstruct.test.ts +++ b/src/test/moves/obstruct.test.ts @@ -1,6 +1,7 @@ -import { Moves } from "#app/enums/moves"; -import { Stat } from "#app/enums/stat"; import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => { game = new GameManager(phaserGame); game.override .battleType("single") + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.TACKLE) .enemyAbility(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH) - .moveset([ Moves.OBSTRUCT ]); + .moveset([ Moves.OBSTRUCT ]) + .starterSpecies(Species.FEEBAS); }); it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => { }); it("bypasses accuracy checks when applying protection and defense reduction", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => { ); it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN)); + game.override.enemyMoveset(Moves.WATER_GUN); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => { }); it("doesn't protect from status moves", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.GROWL)); + game.override.enemyMoveset(Moves.GROWL); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => { expect(player.getStatStage(Stat.ATK)).toBe(-1); }); + + it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => { + game.override.enemyAbility(Abilities.CLEAR_BODY); + await game.classicMode.startBattle(); + + game.move.select(Moves.OBSTRUCT); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0); + }); }); From 51894d46c265116ee391146a10bedb16eccbc056 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:38:17 -0700 Subject: [PATCH 08/19] [P2] Pollen Puff ally behavior fixed (#4615) * pollen puff fix * bcvbvcbfd * integerholder to numberholder * moved it back --------- Co-authored-by: frutescens --- src/data/move.ts | 4 ++-- src/field/pokemon.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 08c00829b48..4924341870d 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4107,11 +4107,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { * @param user {@linkcode Pokemon} using the move * @param target {@linkcode Pokemon} target of the move * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.IntegerHolder} The category of the move + * @param args [0] {@linkcode Utils.NumberHolder} The category of the move * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.IntegerHolder); + const category = (args[0] as Utils.NumberHolder); if (user.getAlly() === target) { category.value = MoveCategory.STATUS; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 35f389b58a4..4d85d5b8e1e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2684,7 +2684,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ apply(source: Pokemon, move: Move): HitResult { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (move.category === MoveCategory.STATUS) { + const moveCategory = new Utils.NumberHolder(move.category); + applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory); + if (moveCategory.value === MoveCategory.STATUS) { const cancelled = new Utils.BooleanHolder(false); const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled); From 64147e44145faa8e8f14730279e764d146797841 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:40:14 -0400 Subject: [PATCH 09/19] [P2] Fix Battle Bond continuing to affect Water Shuriken after Greninja returns to base form (#4602) * [Bug] Fix Battle Bond continuing to buff Water Shuriken after Greninja returns to base form * Test cleanup * PR feedback * Update test to use getMultiHitType() * PR Feedback --- src/data/move.ts | 13 +++- src/test/abilities/battle_bond.test.ts | 94 +++++++++++++++++--------- 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 4924341870d..bae8eea0d8a 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1938,12 +1938,21 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @see {@linkcode apply} */ export class MultiHitAttr extends MoveAttr { + /** This move's intrinsic multi-hit type. It should never be modified. */ + private readonly intrinsicMultiHitType: MultiHitType; + /** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */ private multiHitType: MultiHitType; constructor(multiHitType?: MultiHitType) { super(); - this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this.multiHitType = this.intrinsicMultiHitType; + } + + // Currently used by `battle_bond.test.ts` + getMultiHitType(): MultiHitType { + return this.multiHitType; } /** @@ -1957,7 +1966,7 @@ export class MultiHitAttr extends MoveAttr { * @returns True */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const hitType = new Utils.NumberHolder(this.multiHitType); + const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType); applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); this.multiHitType = hitType.value; diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index c7dffeb150a..283fb0d0f14 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,17 +1,19 @@ +import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move"; import { Status, StatusEffect } from "#app/data/status-effect"; -import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - BATTLE BOND", () => { let phaserGame: Phaser.Game; let game: GameManager; + const baseForm = 1; + const ashForm = 2; + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => { beforeEach(() => { game = new GameManager(phaserGame); - const moveToUse = Moves.SPLASH; - game.override.battleType("single"); - game.override.ability(Abilities.BATTLE_BOND); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); + game.override.battleType("single") + .startingWave(4) // Leads to arena reset on Wave 5 trainer battle + .ability(Abilities.BATTLE_BOND) + .starterForms({ [Species.GRENINJA]: ashForm, }) + .moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ]) + .enemySpecies(Species.BULBASAUR) + .enemyMoveset(Moves.SPLASH) + .startingLevel(100) // Avoid levelling up + .enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()` }); - test( - "check if fainted pokemon switches to base form on arena reset", - async () => { - const baseForm = 1; - const ashForm = 2; - game.override.startingWave(4); - game.override.starterForms({ - [Species.GRENINJA]: ashForm, - }); + it("check if fainted pokemon switches to base form on arena reset", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); - await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); + const greninja = game.scene.getParty()[1]; + expect(greninja.formIndex).toBe(ashForm); - const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); - expect(greninja).toBeDefined(); - expect(greninja!.formIndex).toBe(ashForm); + greninja.hp = 0; + greninja.status = new Status(StatusEffect.FAINT); + expect(greninja.isFainted()).toBe(true); - greninja!.hp = 0; - greninja!.status = new Status(StatusEffect.FAINT); - expect(greninja!.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("TurnEndPhase"); + game.doSelectModifier(); + await game.phaseInterceptor.to("QuietFormChangePhase"); - game.move.select(Moves.SPLASH); - await game.doKillOpponents(); - await game.phaseInterceptor.to(TurnEndPhase); - game.doSelectModifier(); - await game.phaseInterceptor.to(QuietFormChangePhase); + expect(greninja.formIndex).toBe(baseForm); + }); - expect(greninja!.formIndex).toBe(baseForm); - }, - ); + it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => { + await game.classicMode.startBattle([ Species.GRENINJA ]); + + const waterShuriken = allMoves[Moves.WATER_SHURIKEN]; + vi.spyOn(waterShuriken, "calculateBattlePower"); + + let actualMultiHitType: MultiHitType | null = null; + const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0]; + vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => { + actualMultiHitType = multiHitAttr.getMultiHitType(); + return 3; + }); + + // Wave 4: Use Water Shuriken in Ash form + let expectedBattlePower = 20; + let expectedMultiHitType = MultiHitType._3; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + + await game.doKillOpponents(); + await game.toNextWave(); + + // Wave 5: Use Water Shuriken in base form + expectedBattlePower = 15; + expectedMultiHitType = MultiHitType._2_TO_5; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + }); }); From a778537ccadcfe23a39766abcf8afee7b287ca09 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:43:50 -0700 Subject: [PATCH 10/19] [P2] Sketch Failure Bug involving multiple Sketch-s in a moveset (#4618) * Sketch bug fix * Added test --------- Co-authored-by: frutescens --- src/phases/turn-start-phase.ts | 2 +- src/test/moves/sketch.test.ts | 53 ++++++++++++++++++++++++++++++ src/test/utils/gameManagerUtils.ts | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/sketch.test.ts diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 95d55986185..53623f933f2 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -158,7 +158,7 @@ export class TurnStartPhase extends FieldPhase { if (!queuedMove) { continue; } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); if (move.getMove().hasAttr(MoveHeaderAttr)) { this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); } diff --git a/src/test/moves/sketch.test.ts b/src/test/moves/sketch.test.ts new file mode 100644 index 00000000000..2e3eb97a76c --- /dev/null +++ b/src/test/moves/sketch.test.ts @@ -0,0 +1,53 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Sketch", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => { + game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]); + + await game.classicMode.startBattle([ Species.REGIELEKI ]); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(Moves.SKETCH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + const moveSlot0 = playerPokemon?.getMoveset()[0]; + expect(moveSlot0?.moveId).toBe(Moves.SKETCH); + expect(moveSlot0?.getPpRatio()).toBe(0); + + await game.toNextTurn(); + game.move.select(Moves.SKETCH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + // Can't verify if the player Pokemon's moveset was successfully changed because of overrides. + }); +}); diff --git a/src/test/utils/gameManagerUtils.ts b/src/test/utils/gameManagerUtils.ts index 700d93082d8..543ee9627fe 100644 --- a/src/test/utils/gameManagerUtils.ts +++ b/src/test/utils/gameManagerUtils.ts @@ -86,7 +86,7 @@ export function waitUntil(truth) { export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) { const playerPokemon = scene.getPlayerField()[pokemonIndex]; const moveSet = playerPokemon.getMoveset(); - const index = moveSet.findIndex((m) => m?.moveId === move); + const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp()); console.log(`Move position for ${Moves[move]} (=${move}):`, index); return index; } From ba7e26152e560032792e94257b11510160ea184f Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:45:02 -0700 Subject: [PATCH 11/19] [Bug] Fix substitute interactions with `PostDefendAbAttr`s (#4570) * Fixes some Substitute interactions Specifically with Disguise/Ice Face and Gulp Missile * Add tests * Fix linting * Add `hitsSubstitute()` checks to all `PostDefendAbAttr`s Also fix comment indentation in `MoveEffectPhase` * Revert `move-effect-phase.ts` changes --- src/data/ability.ts | 140 +++++++++++++----------- src/data/battler-tags.ts | 4 + src/data/move.ts | 12 +- src/test/abilities/disguise.test.ts | 16 ++- src/test/abilities/gulp_missile.test.ts | 31 +++++- src/test/abilities/ice_face.test.ts | 38 +++++-- 6 files changed, 152 insertions(+), 89 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 43d02da1733..6a391818866 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -634,15 +634,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. * Also displays a message to show this ability was activated. * @param pokemon {@linkcode Pokemon} with this ability - * @param passive N/A + * @param _passive N/A * @param attacker {@linkcode Pokemon} that is attacking this Pokemon * @param move {@linkcode PokemonMove} that is being used - * @param hitResult N/A - * @args N/A + * @param _hitResult N/A + * @param _args N/A * @returns true if healing should be reversed on a healing move, false otherwise. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.hasAttr(HitHealAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } @@ -669,8 +669,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return true; } @@ -707,13 +707,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; - if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) { - if (!simulated ) { + if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) { + if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } return true; @@ -734,8 +734,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!simulated) { @@ -758,8 +758,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); @@ -771,8 +771,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { } export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { + if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return true; } @@ -787,7 +787,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -805,8 +805,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { + if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); } else { @@ -829,8 +829,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status + && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; if (simulated) { return attacker.canSetStatus(effect, true, false, pokemon); @@ -869,8 +870,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return attacker.canAddTag(this.tagType); } else { @@ -893,7 +894,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { this.stages = stages; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.hitsSubstitute(attacker, pokemon)) { + return false; + } + if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } @@ -901,7 +906,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { return true; } - getCondition(): AbAttrCondition { + override getCondition(): AbAttrCondition { return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; } } @@ -915,8 +920,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); return true; @@ -925,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName @@ -948,8 +954,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { return false; } else { @@ -963,24 +969,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName }); } } export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { private weatherType: WeatherType; - protected condition: PokemonDefendCondition | null; + protected condition?: PokemonDefendCondition; constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) { super(); this.weatherType = weatherType; - this.condition = condition ?? null; + this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition !== null && !this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) { return false; } if (!pokemon.scene.arena.weather?.isImmutable()) { @@ -999,8 +1005,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { super(); } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { const tempAbilityId = attacker.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id; @@ -1012,7 +1019,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } } @@ -1025,8 +1032,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) + && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { attacker.summonData.ability = this.ability; } @@ -1037,7 +1045,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilityGive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName @@ -1056,8 +1064,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (attacker.getTag(BattlerTagType.DISABLED) === null) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (simulated) { return true; @@ -1724,17 +1732,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { } export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { - private condition: PokemonDefendCondition | null; + private condition?: PokemonDefendCondition; constructor(condition?: PokemonDefendCondition) { super(); - this.condition = condition ?? null; + this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise { return new Promise(resolve => { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -4476,7 +4484,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { private multiplier: number; private tagType: BattlerTagType; - private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined; + private recoilDamageFunc?: ((pokemon: Pokemon) => number); private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string; constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) { @@ -4492,16 +4500,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * Applies the pre-defense ability to the Pokémon. * Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form. * - * @param {Pokemon} pokemon The Pokémon with the ability. - * @param {boolean} passive n/a - * @param {Pokemon} attacker The attacking Pokémon. - * @param {PokemonMove} move The move being used. - * @param {Utils.BooleanHolder} cancelled n/a - * @param {any[]} args Additional arguments. - * @returns {boolean} Whether the immunity was applied. + * @param pokemon The Pokémon with the ability. + * @param _passive n/a + * @param attacker The attacking Pokémon. + * @param move The move being used. + * @param _cancelled n/a + * @param args Additional arguments. + * @returns `true` if the immunity was applied. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { (args[0] as Utils.NumberHolder).value = this.multiplier; pokemon.removeTag(this.tagType); @@ -4517,12 +4525,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { /** * Gets the message triggered when the Pokémon avoids damage using the form-changing ability. - * @param {Pokemon} pokemon The Pokémon with the ability. - * @param {string} abilityName The name of the ability. - * @param {...any} args n/a - * @returns {string} The trigger message. + * @param pokemon The Pokémon with the ability. + * @param abilityName The name of the ability. + * @param _args n/a + * @returns The trigger message. */ - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return this.triggerMessageFunc(pokemon, abilityName); } } @@ -5503,7 +5511,8 @@ export function initAbilities() { .attr(NoFusionAbilityAbAttr) // Add BattlerTagType.DISGUISE if the pokemon is in its disguised form .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false) - .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, + .attr(FormBlockDamageAbAttr, + (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) @@ -5665,7 +5674,8 @@ export function initAbilities() { .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) - .attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, + .attr(FormBlockDamageAbAttr, + (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) .attr(PostBattleInitFormChangeAbAttr, () => 0) .bypassFaint() diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 6307b3d28be..24c82e54427 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2139,6 +2139,10 @@ export class GulpMissileTag extends BattlerTag { return false; } + if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) { + return true; + } + const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); diff --git a/src/data/move.ts b/src/data/move.ts index bae8eea0d8a..ff0c24f5032 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4844,14 +4844,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr { /** * Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio. - * @param {Pokemon} user The Pokemon using the move. - * @param {Pokemon} target The Pokemon being targeted by the move. - * @param {Move} move The move being used. - * @param {any[]} args Additional arguments, if any. + * @param user The Pokemon using the move. + * @param _target N/A + * @param move The move being used. + * @param _args N/A * @returns Whether the BattlerTag is applied. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { - if (!super.apply(user, target, move, args)) { + apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean { + if (!super.apply(user, _target, move, _args)) { return false; } diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index a295dd61443..0241aa4b9ea 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -1,8 +1,9 @@ +import { BattlerIndex } from "#app/battle"; +import { StatusEffect } from "#app/data/status-effect"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { StatusEffect } from "#app/data/status-effect"; import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => { expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu.hp).toBe(maxHp - disguiseDamage); }); + + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyMoveset(Moves.SUBSTITUTE) + .moveset(Moves.POWER_TRIP); + await game.classicMode.startBattle(); + + game.move.select(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm); + }); }); diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 1ca208996b5..01b68d0c89d 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -1,13 +1,14 @@ -import { BattlerTagType } from "#enums/battler-tag-type"; -import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; import Pokemon from "#app/field/pokemon"; -import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { Stat } from "#enums/stat"; describe("Abilities - Gulp Missile", () => { let phaserGame: Phaser.Game; @@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override + .disableCrits() .battleType("single") - .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ]) + .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ]) .enemySpecies(Species.SNORLAX) .enemyAbility(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) @@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => { expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1); }); + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyAbility(Abilities.STURDY) + .enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]); + await game.classicMode.startBattle([ Species.CRAMORANT ]); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(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.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM); + }); it("cannot be suppressed", async () => { game.override.enemyMoveset(Moves.GASTRO_ACID); diff --git a/src/test/abilities/ice_face.test.ts b/src/test/abilities/ice_face.test.ts index 723d5e8d855..1c7f7bd6093 100644 --- a/src/test/abilities/ice_face.test.ts +++ b/src/test/abilities/ice_face.test.ts @@ -1,3 +1,4 @@ +import { BattlerIndex } from "#app/battle"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; @@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => { }); it("takes no damage from physical move and transforms to Noice", async () => { - await game.startBattle([ Species.HITMONLEE ]); + await game.classicMode.startBattle([ Species.HITMONLEE ]); game.move.select(Moves.TACKLE); @@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => { it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { game.override.moveset([ Moves.SURGING_STRIKES ]); game.override.enemyLevel(1); - await game.startBattle([ Species.HITMONLEE ]); + await game.classicMode.startBattle([ Species.HITMONLEE ]); game.move.select(Moves.SURGING_STRIKES); @@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => { }); it("takes damage from special moves", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.ICE_BEAM); @@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => { }); it("takes effects from status moves", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.TOXIC_THREAD); @@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => { game.override.moveset([ Moves.QUICK_ATTACK ]); game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.QUICK_ATTACK); @@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => { game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); game.override.moveset([ Moves.SNOWSCAPE ]); - await game.startBattle([ Species.EISCUE, Species.NINJASK ]); + await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]); game.move.select(Moves.SNOWSCAPE); @@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => { game.override.enemySpecies(Species.SHUCKLE); game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); - await game.startBattle([ Species.EISCUE ]); + await game.classicMode.startBattle([ Species.EISCUE ]); game.move.select(Moves.HAIL); const eiscue = game.scene.getPlayerPokemon()!; @@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => { it("persists form change when switched out", async () => { game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]); - await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]); game.move.select(Moves.ICE_BEAM); @@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => { [Species.EISCUE]: noiceForm, }); - await game.startBattle([ Species.EISCUE ]); + await game.classicMode.startBattle([ Species.EISCUE ]); const eiscue = game.scene.getPlayerPokemon()!; @@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => { expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); }); + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyMoveset(Moves.SUBSTITUTE) + .moveset(Moves.POWER_TRIP); + await game.classicMode.startBattle(); + + game.move.select(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm); + }); + it("cannot be suppressed", async () => { game.override.moveset([ Moves.GASTRO_ACID ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.GASTRO_ACID); @@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => { it("cannot be swapped with another ability", async () => { game.override.moveset([ Moves.SKILL_SWAP ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.SKILL_SWAP); @@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => { it("cannot be copied", async () => { game.override.ability(Abilities.TRACE); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.SIMPLE_BEAM); From 0996789ee6f232c45cf4d6597c78906d8e7c2cdc Mon Sep 17 00:00:00 2001 From: "Adrian T." <68144167+torranx@users.noreply.github.com> Date: Thu, 10 Oct 2024 23:54:43 +0800 Subject: [PATCH 12/19] [Refactor] Improve typing in `phaseInterceptor.ts` (#4560) * improve typing in phaseInterceptor * add more param typings --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/test/utils/phaseInterceptor.ts | 126 +++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index d108c4cb2ea..ec9309e2405 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -53,6 +53,7 @@ import { } from "#app/phases/mystery-encounter-phases"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import { ExpPhase } from "#app/phases/exp-phase"; export interface PromptHandler { phaseTarget?: string; @@ -61,7 +62,114 @@ export interface PromptHandler { expireFn?: () => void; awaitingActionInput?: boolean; } -import { ExpPhase } from "#app/phases/exp-phase"; + +type PhaseClass = + | typeof LoginPhase + | typeof TitlePhase + | typeof SelectGenderPhase + | typeof EncounterPhase + | typeof NewBiomeEncounterPhase + | typeof SelectStarterPhase + | typeof PostSummonPhase + | typeof SummonPhase + | typeof ToggleDoublePositionPhase + | typeof CheckSwitchPhase + | typeof ShowAbilityPhase + | typeof MessagePhase + | typeof TurnInitPhase + | typeof CommandPhase + | typeof EnemyCommandPhase + | typeof TurnStartPhase + | typeof MovePhase + | typeof MoveEffectPhase + | typeof DamagePhase + | typeof FaintPhase + | typeof BerryPhase + | typeof TurnEndPhase + | typeof BattleEndPhase + | typeof EggLapsePhase + | typeof SelectModifierPhase + | typeof NextEncounterPhase + | typeof NewBattlePhase + | typeof VictoryPhase + | typeof LearnMovePhase + | typeof MoveEndPhase + | typeof StatStageChangePhase + | typeof ShinySparklePhase + | typeof SelectTargetPhase + | typeof UnavailablePhase + | typeof QuietFormChangePhase + | typeof SwitchPhase + | typeof SwitchSummonPhase + | typeof PartyHealPhase + | typeof EvolutionPhase + | typeof EndEvolutionPhase + | typeof LevelCapPhase + | typeof AttemptRunPhase + | typeof SelectBiomePhase + | typeof MysteryEncounterPhase + | typeof MysteryEncounterOptionSelectedPhase + | typeof MysteryEncounterBattlePhase + | typeof MysteryEncounterRewardsPhase + | typeof PostMysteryEncounterPhase + | typeof ModifierRewardPhase + | typeof PartyExpPhase + | typeof ExpPhase; + +type PhaseString = + | "LoginPhase" + | "TitlePhase" + | "SelectGenderPhase" + | "EncounterPhase" + | "NewBiomeEncounterPhase" + | "SelectStarterPhase" + | "PostSummonPhase" + | "SummonPhase" + | "ToggleDoublePositionPhase" + | "CheckSwitchPhase" + | "ShowAbilityPhase" + | "MessagePhase" + | "TurnInitPhase" + | "CommandPhase" + | "EnemyCommandPhase" + | "TurnStartPhase" + | "MovePhase" + | "MoveEffectPhase" + | "DamagePhase" + | "FaintPhase" + | "BerryPhase" + | "TurnEndPhase" + | "BattleEndPhase" + | "EggLapsePhase" + | "SelectModifierPhase" + | "NextEncounterPhase" + | "NewBattlePhase" + | "VictoryPhase" + | "LearnMovePhase" + | "MoveEndPhase" + | "StatStageChangePhase" + | "ShinySparklePhase" + | "SelectTargetPhase" + | "UnavailablePhase" + | "QuietFormChangePhase" + | "SwitchPhase" + | "SwitchSummonPhase" + | "PartyHealPhase" + | "EvolutionPhase" + | "EndEvolutionPhase" + | "LevelCapPhase" + | "AttemptRunPhase" + | "SelectBiomePhase" + | "MysteryEncounterPhase" + | "MysteryEncounterOptionSelectedPhase" + | "MysteryEncounterBattlePhase" + | "MysteryEncounterRewardsPhase" + | "PostMysteryEncounterPhase" + | "ModifierRewardPhase" + | "PartyExpPhase" + | "ExpPhase"; + +type PhaseInterceptorPhase = PhaseClass | PhaseString; export default class PhaseInterceptor { public scene; @@ -172,7 +280,7 @@ export default class PhaseInterceptor { * @param phaseFrom - The phase to start from. * @returns The instance of the PhaseInterceptor. */ - runFrom(phaseFrom) { + runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor { this.phaseFrom = phaseFrom; return this; } @@ -180,9 +288,10 @@ export default class PhaseInterceptor { /** * Method to transition to a target phase. * @param phaseTo - The phase to transition to. + * @param runTarget - Whether or not to run the target phase. * @returns A promise that resolves when the transition is complete. */ - async to(phaseTo, runTarget: boolean = true): Promise { + async to(phaseTo: PhaseInterceptorPhase, runTarget: boolean = true): Promise { return new Promise(async (resolve, reject) => { ErrorInterceptor.getInstance().add(this); if (this.phaseFrom) { @@ -219,7 +328,7 @@ export default class PhaseInterceptor { * @param skipFn - Optional skip function. * @returns A promise that resolves when the phase is run. */ - run(phaseTarget, skipFn?): Promise { + run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { @@ -253,7 +362,7 @@ export default class PhaseInterceptor { }); } - whenAboutToRun(phaseTarget, skipFn?): Promise { + whenAboutToRun(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { @@ -311,7 +420,7 @@ export default class PhaseInterceptor { * Method to start a phase and log it. * @param phase - The phase to start. */ - startPhase(phase) { + startPhase(phase: PhaseClass) { this.log.push(phase.name); const instance = this.scene.getCurrentPhase(); this.onHold.push({ @@ -340,9 +449,10 @@ export default class PhaseInterceptor { /** * m2m to set mode. - * @param phase - The phase to start. + * @param mode - The {@linkcode Mode} to set. + * @param args - Additional arguments to pass to the original method. */ - setMode(mode: Mode, ...args: any[]): Promise { + setMode(mode: Mode, ...args: unknown[]): Promise { const currentPhase = this.scene.getCurrentPhase(); const instance = this.scene.ui; console.log("setMode", `${Mode[mode]} (=${mode})`, args); From 6ad5ba972cc7eafc451ed264bae3e1c153463fd8 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:29:26 -0400 Subject: [PATCH 13/19] [Enhancement] Refactor Starter Species to use separate EggTier map (#4591) * creates table for tracking species egg tiers * creates table for tracking species egg tiers * rename EggTier enum values * replace clamp util function with Phaser function --------- Co-authored-by: ImperialSympathizer --- src/data/balance/species-egg-tiers.ts | 603 ++++++++++++++++++ src/data/egg.ts | 91 ++- .../encounters/a-trainers-test-encounter.ts | 4 +- .../the-expert-pokemon-breeder-encounter.ts | 2 +- src/enums/egg-type.ts | 6 +- src/field/pokemon.ts | 2 +- src/test/eggs/egg.test.ts | 36 +- .../a-trainers-test-encounter.test.ts | 4 +- .../the-expert-breeder-encounter.test.ts | 6 +- src/ui/battle-info.ts | 2 +- src/ui/egg-gacha-ui-handler.ts | 6 +- src/ui/text.ts | 6 +- src/utils.ts | 4 - 13 files changed, 676 insertions(+), 96 deletions(-) create mode 100644 src/data/balance/species-egg-tiers.ts diff --git a/src/data/balance/species-egg-tiers.ts b/src/data/balance/species-egg-tiers.ts new file mode 100644 index 00000000000..cd266dfcf54 --- /dev/null +++ b/src/data/balance/species-egg-tiers.ts @@ -0,0 +1,603 @@ +import { Species } from "#enums/species"; +import { EggTier } from "#enums/egg-type"; + +/** + * Map of all starters and their respective {@linkcode EggTier}, which determines the type of egg the starter hatches from. + */ +export const speciesEggTiers = { + [Species.BULBASAUR]: EggTier.COMMON, + [Species.CHARMANDER]: EggTier.COMMON, + [Species.SQUIRTLE]: EggTier.COMMON, + [Species.CATERPIE]: EggTier.COMMON, + [Species.WEEDLE]: EggTier.COMMON, + [Species.PIDGEY]: EggTier.COMMON, + [Species.RATTATA]: EggTier.COMMON, + [Species.SPEAROW]: EggTier.COMMON, + [Species.EKANS]: EggTier.COMMON, + [Species.PIKACHU]: EggTier.COMMON, + [Species.SANDSHREW]: EggTier.COMMON, + [Species.NIDORAN_F]: EggTier.COMMON, + [Species.NIDORAN_M]: EggTier.COMMON, + [Species.CLEFAIRY]: EggTier.COMMON, + [Species.VULPIX]: EggTier.COMMON, + [Species.JIGGLYPUFF]: EggTier.COMMON, + [Species.ZUBAT]: EggTier.COMMON, + [Species.ODDISH]: EggTier.COMMON, + [Species.PARAS]: EggTier.COMMON, + [Species.VENONAT]: EggTier.COMMON, + [Species.DIGLETT]: EggTier.COMMON, + [Species.MEOWTH]: EggTier.COMMON, + [Species.PSYDUCK]: EggTier.COMMON, + [Species.MANKEY]: EggTier.RARE, + [Species.GROWLITHE]: EggTier.RARE, + [Species.POLIWAG]: EggTier.COMMON, + [Species.ABRA]: EggTier.RARE, + [Species.MACHOP]: EggTier.COMMON, + [Species.BELLSPROUT]: EggTier.COMMON, + [Species.TENTACOOL]: EggTier.COMMON, + [Species.GEODUDE]: EggTier.COMMON, + [Species.PONYTA]: EggTier.COMMON, + [Species.SLOWPOKE]: EggTier.COMMON, + [Species.MAGNEMITE]: EggTier.RARE, + [Species.FARFETCHD]: EggTier.COMMON, + [Species.DODUO]: EggTier.COMMON, + [Species.SEEL]: EggTier.COMMON, + [Species.GRIMER]: EggTier.COMMON, + [Species.SHELLDER]: EggTier.RARE, + [Species.GASTLY]: EggTier.RARE, + [Species.ONIX]: EggTier.COMMON, + [Species.DROWZEE]: EggTier.COMMON, + [Species.KRABBY]: EggTier.COMMON, + [Species.VOLTORB]: EggTier.COMMON, + [Species.EXEGGCUTE]: EggTier.COMMON, + [Species.CUBONE]: EggTier.COMMON, + [Species.HITMONLEE]: EggTier.RARE, + [Species.HITMONCHAN]: EggTier.RARE, + [Species.LICKITUNG]: EggTier.COMMON, + [Species.KOFFING]: EggTier.COMMON, + [Species.RHYHORN]: EggTier.COMMON, + [Species.CHANSEY]: EggTier.COMMON, + [Species.TANGELA]: EggTier.COMMON, + [Species.KANGASKHAN]: EggTier.RARE, + [Species.HORSEA]: EggTier.COMMON, + [Species.GOLDEEN]: EggTier.COMMON, + [Species.STARYU]: EggTier.COMMON, + [Species.MR_MIME]: EggTier.COMMON, + [Species.SCYTHER]: EggTier.RARE, + [Species.JYNX]: EggTier.RARE, + [Species.ELECTABUZZ]: EggTier.RARE, + [Species.MAGMAR]: EggTier.RARE, + [Species.PINSIR]: EggTier.RARE, + [Species.TAUROS]: EggTier.RARE, + [Species.MAGIKARP]: EggTier.RARE, + [Species.LAPRAS]: EggTier.RARE, + [Species.DITTO]: EggTier.COMMON, + [Species.EEVEE]: EggTier.COMMON, + [Species.PORYGON]: EggTier.RARE, + [Species.OMANYTE]: EggTier.COMMON, + [Species.KABUTO]: EggTier.COMMON, + [Species.AERODACTYL]: EggTier.RARE, + [Species.SNORLAX]: EggTier.RARE, + [Species.ARTICUNO]: EggTier.EPIC, + [Species.ZAPDOS]: EggTier.EPIC, + [Species.MOLTRES]: EggTier.EPIC, + [Species.DRATINI]: EggTier.RARE, + [Species.MEWTWO]: EggTier.LEGENDARY, + [Species.MEW]: EggTier.EPIC, + + [Species.CHIKORITA]: EggTier.COMMON, + [Species.CYNDAQUIL]: EggTier.COMMON, + [Species.TOTODILE]: EggTier.COMMON, + [Species.SENTRET]: EggTier.COMMON, + [Species.HOOTHOOT]: EggTier.COMMON, + [Species.LEDYBA]: EggTier.COMMON, + [Species.SPINARAK]: EggTier.COMMON, + [Species.CHINCHOU]: EggTier.COMMON, + [Species.PICHU]: EggTier.COMMON, + [Species.CLEFFA]: EggTier.COMMON, + [Species.IGGLYBUFF]: EggTier.COMMON, + [Species.TOGEPI]: EggTier.COMMON, + [Species.NATU]: EggTier.COMMON, + [Species.MAREEP]: EggTier.COMMON, + [Species.MARILL]: EggTier.RARE, + [Species.SUDOWOODO]: EggTier.COMMON, + [Species.HOPPIP]: EggTier.COMMON, + [Species.AIPOM]: EggTier.COMMON, + [Species.SUNKERN]: EggTier.COMMON, + [Species.YANMA]: EggTier.COMMON, + [Species.WOOPER]: EggTier.COMMON, + [Species.MURKROW]: EggTier.COMMON, + [Species.MISDREAVUS]: EggTier.COMMON, + [Species.UNOWN]: EggTier.COMMON, + [Species.WOBBUFFET]: EggTier.COMMON, + [Species.GIRAFARIG]: EggTier.COMMON, + [Species.PINECO]: EggTier.COMMON, + [Species.DUNSPARCE]: EggTier.COMMON, + [Species.GLIGAR]: EggTier.COMMON, + [Species.SNUBBULL]: EggTier.COMMON, + [Species.QWILFISH]: EggTier.COMMON, + [Species.SHUCKLE]: EggTier.COMMON, + [Species.HERACROSS]: EggTier.RARE, + [Species.SNEASEL]: EggTier.RARE, + [Species.TEDDIURSA]: EggTier.RARE, + [Species.SLUGMA]: EggTier.COMMON, + [Species.SWINUB]: EggTier.COMMON, + [Species.CORSOLA]: EggTier.COMMON, + [Species.REMORAID]: EggTier.COMMON, + [Species.DELIBIRD]: EggTier.COMMON, + [Species.MANTINE]: EggTier.COMMON, + [Species.SKARMORY]: EggTier.RARE, + [Species.HOUNDOUR]: EggTier.COMMON, + [Species.PHANPY]: EggTier.COMMON, + [Species.STANTLER]: EggTier.COMMON, + [Species.SMEARGLE]: EggTier.COMMON, + [Species.TYROGUE]: EggTier.COMMON, + [Species.SMOOCHUM]: EggTier.COMMON, + [Species.ELEKID]: EggTier.COMMON, + [Species.MAGBY]: EggTier.COMMON, + [Species.MILTANK]: EggTier.RARE, + [Species.RAIKOU]: EggTier.EPIC, + [Species.ENTEI]: EggTier.EPIC, + [Species.SUICUNE]: EggTier.EPIC, + [Species.LARVITAR]: EggTier.RARE, + [Species.LUGIA]: EggTier.LEGENDARY, + [Species.HO_OH]: EggTier.LEGENDARY, + [Species.CELEBI]: EggTier.EPIC, + + [Species.TREECKO]: EggTier.COMMON, + [Species.TORCHIC]: EggTier.RARE, + [Species.MUDKIP]: EggTier.COMMON, + [Species.POOCHYENA]: EggTier.COMMON, + [Species.ZIGZAGOON]: EggTier.COMMON, + [Species.WURMPLE]: EggTier.COMMON, + [Species.LOTAD]: EggTier.COMMON, + [Species.SEEDOT]: EggTier.COMMON, + [Species.TAILLOW]: EggTier.COMMON, + [Species.WINGULL]: EggTier.COMMON, + [Species.RALTS]: EggTier.COMMON, + [Species.SURSKIT]: EggTier.COMMON, + [Species.SHROOMISH]: EggTier.COMMON, + [Species.SLAKOTH]: EggTier.RARE, + [Species.NINCADA]: EggTier.RARE, + [Species.WHISMUR]: EggTier.COMMON, + [Species.MAKUHITA]: EggTier.COMMON, + [Species.AZURILL]: EggTier.RARE, + [Species.NOSEPASS]: EggTier.COMMON, + [Species.SKITTY]: EggTier.COMMON, + [Species.SABLEYE]: EggTier.COMMON, + [Species.MAWILE]: EggTier.COMMON, + [Species.ARON]: EggTier.COMMON, + [Species.MEDITITE]: EggTier.COMMON, + [Species.ELECTRIKE]: EggTier.COMMON, + [Species.PLUSLE]: EggTier.COMMON, + [Species.MINUN]: EggTier.COMMON, + [Species.VOLBEAT]: EggTier.COMMON, + [Species.ILLUMISE]: EggTier.COMMON, + [Species.ROSELIA]: EggTier.COMMON, + [Species.GULPIN]: EggTier.COMMON, + [Species.CARVANHA]: EggTier.COMMON, + [Species.WAILMER]: EggTier.COMMON, + [Species.NUMEL]: EggTier.COMMON, + [Species.TORKOAL]: EggTier.COMMON, + [Species.SPOINK]: EggTier.COMMON, + [Species.SPINDA]: EggTier.COMMON, + [Species.TRAPINCH]: EggTier.COMMON, + [Species.CACNEA]: EggTier.COMMON, + [Species.SWABLU]: EggTier.COMMON, + [Species.ZANGOOSE]: EggTier.RARE, + [Species.SEVIPER]: EggTier.COMMON, + [Species.LUNATONE]: EggTier.COMMON, + [Species.SOLROCK]: EggTier.COMMON, + [Species.BARBOACH]: EggTier.COMMON, + [Species.CORPHISH]: EggTier.COMMON, + [Species.BALTOY]: EggTier.COMMON, + [Species.LILEEP]: EggTier.COMMON, + [Species.ANORITH]: EggTier.COMMON, + [Species.FEEBAS]: EggTier.RARE, + [Species.CASTFORM]: EggTier.COMMON, + [Species.KECLEON]: EggTier.COMMON, + [Species.SHUPPET]: EggTier.COMMON, + [Species.DUSKULL]: EggTier.COMMON, + [Species.TROPIUS]: EggTier.COMMON, + [Species.CHIMECHO]: EggTier.COMMON, + [Species.ABSOL]: EggTier.RARE, + [Species.WYNAUT]: EggTier.COMMON, + [Species.SNORUNT]: EggTier.COMMON, + [Species.SPHEAL]: EggTier.COMMON, + [Species.CLAMPERL]: EggTier.COMMON, + [Species.RELICANTH]: EggTier.COMMON, + [Species.LUVDISC]: EggTier.COMMON, + [Species.BAGON]: EggTier.RARE, + [Species.BELDUM]: EggTier.RARE, + [Species.REGIROCK]: EggTier.EPIC, + [Species.REGICE]: EggTier.EPIC, + [Species.REGISTEEL]: EggTier.EPIC, + [Species.LATIAS]: EggTier.EPIC, + [Species.LATIOS]: EggTier.EPIC, + [Species.KYOGRE]: EggTier.LEGENDARY, + [Species.GROUDON]: EggTier.LEGENDARY, + [Species.RAYQUAZA]: EggTier.LEGENDARY, + [Species.JIRACHI]: EggTier.EPIC, + [Species.DEOXYS]: EggTier.EPIC, + + [Species.TURTWIG]: EggTier.COMMON, + [Species.CHIMCHAR]: EggTier.COMMON, + [Species.PIPLUP]: EggTier.COMMON, + [Species.STARLY]: EggTier.COMMON, + [Species.BIDOOF]: EggTier.COMMON, + [Species.KRICKETOT]: EggTier.COMMON, + [Species.SHINX]: EggTier.COMMON, + [Species.BUDEW]: EggTier.COMMON, + [Species.CRANIDOS]: EggTier.COMMON, + [Species.SHIELDON]: EggTier.COMMON, + [Species.BURMY]: EggTier.COMMON, + [Species.COMBEE]: EggTier.COMMON, + [Species.PACHIRISU]: EggTier.COMMON, + [Species.BUIZEL]: EggTier.COMMON, + [Species.CHERUBI]: EggTier.COMMON, + [Species.SHELLOS]: EggTier.COMMON, + [Species.DRIFLOON]: EggTier.COMMON, + [Species.BUNEARY]: EggTier.COMMON, + [Species.GLAMEOW]: EggTier.COMMON, + [Species.CHINGLING]: EggTier.COMMON, + [Species.STUNKY]: EggTier.COMMON, + [Species.BRONZOR]: EggTier.COMMON, + [Species.BONSLY]: EggTier.COMMON, + [Species.MIME_JR]: EggTier.COMMON, + [Species.HAPPINY]: EggTier.COMMON, + [Species.CHATOT]: EggTier.COMMON, + [Species.SPIRITOMB]: EggTier.RARE, + [Species.GIBLE]: EggTier.RARE, + [Species.MUNCHLAX]: EggTier.RARE, + [Species.RIOLU]: EggTier.COMMON, + [Species.HIPPOPOTAS]: EggTier.COMMON, + [Species.SKORUPI]: EggTier.COMMON, + [Species.CROAGUNK]: EggTier.COMMON, + [Species.CARNIVINE]: EggTier.COMMON, + [Species.FINNEON]: EggTier.COMMON, + [Species.MANTYKE]: EggTier.COMMON, + [Species.SNOVER]: EggTier.COMMON, + [Species.ROTOM]: EggTier.RARE, + [Species.UXIE]: EggTier.EPIC, + [Species.MESPRIT]: EggTier.EPIC, + [Species.AZELF]: EggTier.EPIC, + [Species.DIALGA]: EggTier.LEGENDARY, + [Species.PALKIA]: EggTier.LEGENDARY, + [Species.HEATRAN]: EggTier.EPIC, + [Species.REGIGIGAS]: EggTier.EPIC, + [Species.GIRATINA]: EggTier.LEGENDARY, + [Species.CRESSELIA]: EggTier.EPIC, + [Species.PHIONE]: EggTier.RARE, + [Species.MANAPHY]: EggTier.EPIC, + [Species.DARKRAI]: EggTier.EPIC, + [Species.SHAYMIN]: EggTier.EPIC, + [Species.ARCEUS]: EggTier.LEGENDARY, + + [Species.VICTINI]: EggTier.EPIC, + [Species.SNIVY]: EggTier.COMMON, + [Species.TEPIG]: EggTier.COMMON, + [Species.OSHAWOTT]: EggTier.COMMON, + [Species.PATRAT]: EggTier.COMMON, + [Species.LILLIPUP]: EggTier.COMMON, + [Species.PURRLOIN]: EggTier.COMMON, + [Species.PANSAGE]: EggTier.COMMON, + [Species.PANSEAR]: EggTier.COMMON, + [Species.PANPOUR]: EggTier.COMMON, + [Species.MUNNA]: EggTier.COMMON, + [Species.PIDOVE]: EggTier.COMMON, + [Species.BLITZLE]: EggTier.COMMON, + [Species.ROGGENROLA]: EggTier.COMMON, + [Species.WOOBAT]: EggTier.COMMON, + [Species.DRILBUR]: EggTier.RARE, + [Species.AUDINO]: EggTier.COMMON, + [Species.TIMBURR]: EggTier.RARE, + [Species.TYMPOLE]: EggTier.COMMON, + [Species.THROH]: EggTier.RARE, + [Species.SAWK]: EggTier.RARE, + [Species.SEWADDLE]: EggTier.COMMON, + [Species.VENIPEDE]: EggTier.COMMON, + [Species.COTTONEE]: EggTier.COMMON, + [Species.PETILIL]: EggTier.COMMON, + [Species.BASCULIN]: EggTier.RARE, + [Species.SANDILE]: EggTier.RARE, + [Species.DARUMAKA]: EggTier.RARE, + [Species.MARACTUS]: EggTier.COMMON, + [Species.DWEBBLE]: EggTier.COMMON, + [Species.SCRAGGY]: EggTier.COMMON, + [Species.SIGILYPH]: EggTier.RARE, + [Species.YAMASK]: EggTier.COMMON, + [Species.TIRTOUGA]: EggTier.COMMON, + [Species.ARCHEN]: EggTier.COMMON, + [Species.TRUBBISH]: EggTier.COMMON, + [Species.ZORUA]: EggTier.COMMON, + [Species.MINCCINO]: EggTier.COMMON, + [Species.GOTHITA]: EggTier.COMMON, + [Species.SOLOSIS]: EggTier.COMMON, + [Species.DUCKLETT]: EggTier.COMMON, + [Species.VANILLITE]: EggTier.COMMON, + [Species.DEERLING]: EggTier.COMMON, + [Species.EMOLGA]: EggTier.COMMON, + [Species.KARRABLAST]: EggTier.COMMON, + [Species.FOONGUS]: EggTier.COMMON, + [Species.FRILLISH]: EggTier.COMMON, + [Species.ALOMOMOLA]: EggTier.RARE, + [Species.JOLTIK]: EggTier.COMMON, + [Species.FERROSEED]: EggTier.COMMON, + [Species.KLINK]: EggTier.COMMON, + [Species.TYNAMO]: EggTier.COMMON, + [Species.ELGYEM]: EggTier.COMMON, + [Species.LITWICK]: EggTier.COMMON, + [Species.AXEW]: EggTier.RARE, + [Species.CUBCHOO]: EggTier.COMMON, + [Species.CRYOGONAL]: EggTier.RARE, + [Species.SHELMET]: EggTier.COMMON, + [Species.STUNFISK]: EggTier.COMMON, + [Species.MIENFOO]: EggTier.COMMON, + [Species.DRUDDIGON]: EggTier.RARE, + [Species.GOLETT]: EggTier.COMMON, + [Species.PAWNIARD]: EggTier.RARE, + [Species.BOUFFALANT]: EggTier.RARE, + [Species.RUFFLET]: EggTier.COMMON, + [Species.VULLABY]: EggTier.COMMON, + [Species.HEATMOR]: EggTier.COMMON, + [Species.DURANT]: EggTier.RARE, + [Species.DEINO]: EggTier.RARE, + [Species.LARVESTA]: EggTier.RARE, + [Species.COBALION]: EggTier.EPIC, + [Species.TERRAKION]: EggTier.EPIC, + [Species.VIRIZION]: EggTier.EPIC, + [Species.TORNADUS]: EggTier.EPIC, + [Species.THUNDURUS]: EggTier.EPIC, + [Species.RESHIRAM]: EggTier.LEGENDARY, + [Species.ZEKROM]: EggTier.LEGENDARY, + [Species.LANDORUS]: EggTier.EPIC, + [Species.KYUREM]: EggTier.LEGENDARY, + [Species.KELDEO]: EggTier.EPIC, + [Species.MELOETTA]: EggTier.EPIC, + [Species.GENESECT]: EggTier.EPIC, + + [Species.CHESPIN]: EggTier.COMMON, + [Species.FENNEKIN]: EggTier.COMMON, + [Species.FROAKIE]: EggTier.RARE, + [Species.BUNNELBY]: EggTier.COMMON, + [Species.FLETCHLING]: EggTier.COMMON, + [Species.SCATTERBUG]: EggTier.COMMON, + [Species.LITLEO]: EggTier.COMMON, + [Species.FLABEBE]: EggTier.COMMON, + [Species.SKIDDO]: EggTier.COMMON, + [Species.PANCHAM]: EggTier.COMMON, + [Species.FURFROU]: EggTier.COMMON, + [Species.ESPURR]: EggTier.COMMON, + [Species.HONEDGE]: EggTier.RARE, + [Species.SPRITZEE]: EggTier.COMMON, + [Species.SWIRLIX]: EggTier.COMMON, + [Species.INKAY]: EggTier.COMMON, + [Species.BINACLE]: EggTier.COMMON, + [Species.SKRELP]: EggTier.COMMON, + [Species.CLAUNCHER]: EggTier.COMMON, + [Species.HELIOPTILE]: EggTier.COMMON, + [Species.TYRUNT]: EggTier.COMMON, + [Species.AMAURA]: EggTier.COMMON, + [Species.HAWLUCHA]: EggTier.RARE, + [Species.DEDENNE]: EggTier.COMMON, + [Species.CARBINK]: EggTier.COMMON, + [Species.GOOMY]: EggTier.RARE, + [Species.KLEFKI]: EggTier.COMMON, + [Species.PHANTUMP]: EggTier.COMMON, + [Species.PUMPKABOO]: EggTier.COMMON, + [Species.BERGMITE]: EggTier.COMMON, + [Species.NOIBAT]: EggTier.COMMON, + [Species.XERNEAS]: EggTier.LEGENDARY, + [Species.YVELTAL]: EggTier.LEGENDARY, + [Species.ZYGARDE]: EggTier.LEGENDARY, + [Species.DIANCIE]: EggTier.EPIC, + [Species.HOOPA]: EggTier.EPIC, + [Species.VOLCANION]: EggTier.EPIC, + [Species.ETERNAL_FLOETTE]: EggTier.RARE, + + [Species.ROWLET]: EggTier.COMMON, + [Species.LITTEN]: EggTier.COMMON, + [Species.POPPLIO]: EggTier.RARE, + [Species.PIKIPEK]: EggTier.COMMON, + [Species.YUNGOOS]: EggTier.COMMON, + [Species.GRUBBIN]: EggTier.COMMON, + [Species.CRABRAWLER]: EggTier.COMMON, + [Species.ORICORIO]: EggTier.COMMON, + [Species.CUTIEFLY]: EggTier.COMMON, + [Species.ROCKRUFF]: EggTier.COMMON, + [Species.WISHIWASHI]: EggTier.COMMON, + [Species.MAREANIE]: EggTier.COMMON, + [Species.MUDBRAY]: EggTier.COMMON, + [Species.DEWPIDER]: EggTier.COMMON, + [Species.FOMANTIS]: EggTier.COMMON, + [Species.MORELULL]: EggTier.COMMON, + [Species.SALANDIT]: EggTier.COMMON, + [Species.STUFFUL]: EggTier.COMMON, + [Species.BOUNSWEET]: EggTier.COMMON, + [Species.COMFEY]: EggTier.RARE, + [Species.ORANGURU]: EggTier.RARE, + [Species.PASSIMIAN]: EggTier.RARE, + [Species.WIMPOD]: EggTier.COMMON, + [Species.SANDYGAST]: EggTier.COMMON, + [Species.PYUKUMUKU]: EggTier.COMMON, + [Species.TYPE_NULL]: EggTier.RARE, + [Species.MINIOR]: EggTier.RARE, + [Species.KOMALA]: EggTier.COMMON, + [Species.TURTONATOR]: EggTier.RARE, + [Species.TOGEDEMARU]: EggTier.COMMON, + [Species.MIMIKYU]: EggTier.RARE, + [Species.BRUXISH]: EggTier.RARE, + [Species.DRAMPA]: EggTier.RARE, + [Species.DHELMISE]: EggTier.RARE, + [Species.JANGMO_O]: EggTier.RARE, + [Species.TAPU_KOKO]: EggTier.EPIC, + [Species.TAPU_LELE]: EggTier.EPIC, + [Species.TAPU_BULU]: EggTier.EPIC, + [Species.TAPU_FINI]: EggTier.EPIC, + [Species.COSMOG]: EggTier.EPIC, + [Species.NIHILEGO]: EggTier.EPIC, + [Species.BUZZWOLE]: EggTier.EPIC, + [Species.PHEROMOSA]: EggTier.EPIC, + [Species.XURKITREE]: EggTier.EPIC, + [Species.CELESTEELA]: EggTier.EPIC, + [Species.KARTANA]: EggTier.EPIC, + [Species.GUZZLORD]: EggTier.EPIC, + [Species.NECROZMA]: EggTier.LEGENDARY, + [Species.MAGEARNA]: EggTier.EPIC, + [Species.MARSHADOW]: EggTier.EPIC, + [Species.POIPOLE]: EggTier.EPIC, + [Species.STAKATAKA]: EggTier.EPIC, + [Species.BLACEPHALON]: EggTier.EPIC, + [Species.ZERAORA]: EggTier.EPIC, + [Species.MELTAN]: EggTier.EPIC, + [Species.ALOLA_RATTATA]: EggTier.COMMON, + [Species.ALOLA_SANDSHREW]: EggTier.COMMON, + [Species.ALOLA_VULPIX]: EggTier.COMMON, + [Species.ALOLA_DIGLETT]: EggTier.COMMON, + [Species.ALOLA_MEOWTH]: EggTier.COMMON, + [Species.ALOLA_GEODUDE]: EggTier.COMMON, + [Species.ALOLA_GRIMER]: EggTier.COMMON, + + [Species.GROOKEY]: EggTier.COMMON, + [Species.SCORBUNNY]: EggTier.RARE, + [Species.SOBBLE]: EggTier.COMMON, + [Species.SKWOVET]: EggTier.COMMON, + [Species.ROOKIDEE]: EggTier.COMMON, + [Species.BLIPBUG]: EggTier.COMMON, + [Species.NICKIT]: EggTier.COMMON, + [Species.GOSSIFLEUR]: EggTier.COMMON, + [Species.WOOLOO]: EggTier.COMMON, + [Species.CHEWTLE]: EggTier.COMMON, + [Species.YAMPER]: EggTier.COMMON, + [Species.ROLYCOLY]: EggTier.COMMON, + [Species.APPLIN]: EggTier.COMMON, + [Species.SILICOBRA]: EggTier.COMMON, + [Species.CRAMORANT]: EggTier.COMMON, + [Species.ARROKUDA]: EggTier.COMMON, + [Species.TOXEL]: EggTier.COMMON, + [Species.SIZZLIPEDE]: EggTier.COMMON, + [Species.CLOBBOPUS]: EggTier.COMMON, + [Species.SINISTEA]: EggTier.COMMON, + [Species.HATENNA]: EggTier.COMMON, + [Species.IMPIDIMP]: EggTier.COMMON, + [Species.MILCERY]: EggTier.COMMON, + [Species.FALINKS]: EggTier.RARE, + [Species.PINCURCHIN]: EggTier.COMMON, + [Species.SNOM]: EggTier.COMMON, + [Species.STONJOURNER]: EggTier.COMMON, + [Species.EISCUE]: EggTier.COMMON, + [Species.INDEEDEE]: EggTier.RARE, + [Species.MORPEKO]: EggTier.COMMON, + [Species.CUFANT]: EggTier.COMMON, + [Species.DRACOZOLT]: EggTier.RARE, + [Species.ARCTOZOLT]: EggTier.RARE, + [Species.DRACOVISH]: EggTier.RARE, + [Species.ARCTOVISH]: EggTier.RARE, + [Species.DURALUDON]: EggTier.RARE, + [Species.DREEPY]: EggTier.RARE, + [Species.ZACIAN]: EggTier.LEGENDARY, + [Species.ZAMAZENTA]: EggTier.LEGENDARY, + [Species.ETERNATUS]: EggTier.COMMON, + [Species.KUBFU]: EggTier.EPIC, + [Species.ZARUDE]: EggTier.EPIC, + [Species.REGIELEKI]: EggTier.EPIC, + [Species.REGIDRAGO]: EggTier.EPIC, + [Species.GLASTRIER]: EggTier.EPIC, + [Species.SPECTRIER]: EggTier.EPIC, + [Species.CALYREX]: EggTier.LEGENDARY, + [Species.GALAR_MEOWTH]: EggTier.COMMON, + [Species.GALAR_PONYTA]: EggTier.COMMON, + [Species.GALAR_SLOWPOKE]: EggTier.COMMON, + [Species.GALAR_FARFETCHD]: EggTier.COMMON, + [Species.GALAR_CORSOLA]: EggTier.COMMON, + [Species.GALAR_ZIGZAGOON]: EggTier.COMMON, + [Species.GALAR_DARUMAKA]: EggTier.RARE, + [Species.GALAR_YAMASK]: EggTier.COMMON, + [Species.GALAR_STUNFISK]: EggTier.COMMON, + [Species.GALAR_MR_MIME]: EggTier.COMMON, + [Species.GALAR_ARTICUNO]: EggTier.EPIC, + [Species.GALAR_ZAPDOS]: EggTier.EPIC, + [Species.GALAR_MOLTRES]: EggTier.EPIC, + [Species.HISUI_GROWLITHE]: EggTier.RARE, + [Species.HISUI_VOLTORB]: EggTier.COMMON, + [Species.HISUI_QWILFISH]: EggTier.RARE, + [Species.HISUI_SNEASEL]: EggTier.RARE, + [Species.HISUI_ZORUA]: EggTier.COMMON, + [Species.ENAMORUS]: EggTier.EPIC, + + [Species.SPRIGATITO]: EggTier.RARE, + [Species.FUECOCO]: EggTier.RARE, + [Species.QUAXLY]: EggTier.RARE, + [Species.LECHONK]: EggTier.COMMON, + [Species.TAROUNTULA]: EggTier.COMMON, + [Species.NYMBLE]: EggTier.COMMON, + [Species.PAWMI]: EggTier.COMMON, + [Species.TANDEMAUS]: EggTier.RARE, + [Species.FIDOUGH]: EggTier.COMMON, + [Species.SMOLIV]: EggTier.COMMON, + [Species.SQUAWKABILLY]: EggTier.COMMON, + [Species.NACLI]: EggTier.RARE, + [Species.CHARCADET]: EggTier.RARE, + [Species.TADBULB]: EggTier.COMMON, + [Species.WATTREL]: EggTier.COMMON, + [Species.MASCHIFF]: EggTier.COMMON, + [Species.SHROODLE]: EggTier.COMMON, + [Species.BRAMBLIN]: EggTier.COMMON, + [Species.TOEDSCOOL]: EggTier.COMMON, + [Species.KLAWF]: EggTier.COMMON, + [Species.CAPSAKID]: EggTier.COMMON, + [Species.RELLOR]: EggTier.COMMON, + [Species.FLITTLE]: EggTier.COMMON, + [Species.TINKATINK]: EggTier.RARE, + [Species.WIGLETT]: EggTier.COMMON, + [Species.BOMBIRDIER]: EggTier.COMMON, + [Species.FINIZEN]: EggTier.COMMON, + [Species.VAROOM]: EggTier.RARE, + [Species.CYCLIZAR]: EggTier.RARE, + [Species.ORTHWORM]: EggTier.RARE, + [Species.GLIMMET]: EggTier.RARE, + [Species.GREAVARD]: EggTier.COMMON, + [Species.FLAMIGO]: EggTier.RARE, + [Species.CETODDLE]: EggTier.COMMON, + [Species.VELUZA]: EggTier.RARE, + [Species.DONDOZO]: EggTier.RARE, + [Species.TATSUGIRI]: EggTier.RARE, + [Species.GREAT_TUSK]: EggTier.EPIC, + [Species.SCREAM_TAIL]: EggTier.EPIC, + [Species.BRUTE_BONNET]: EggTier.EPIC, + [Species.FLUTTER_MANE]: EggTier.EPIC, + [Species.SLITHER_WING]: EggTier.EPIC, + [Species.SANDY_SHOCKS]: EggTier.EPIC, + [Species.IRON_TREADS]: EggTier.EPIC, + [Species.IRON_BUNDLE]: EggTier.EPIC, + [Species.IRON_HANDS]: EggTier.EPIC, + [Species.IRON_JUGULIS]: EggTier.EPIC, + [Species.IRON_MOTH]: EggTier.EPIC, + [Species.IRON_THORNS]: EggTier.EPIC, + [Species.FRIGIBAX]: EggTier.RARE, + [Species.GIMMIGHOUL]: EggTier.RARE, + [Species.WO_CHIEN]: EggTier.EPIC, + [Species.CHIEN_PAO]: EggTier.EPIC, + [Species.TING_LU]: EggTier.EPIC, + [Species.CHI_YU]: EggTier.EPIC, + [Species.ROARING_MOON]: EggTier.EPIC, + [Species.IRON_VALIANT]: EggTier.EPIC, + [Species.KORAIDON]: EggTier.LEGENDARY, + [Species.MIRAIDON]: EggTier.LEGENDARY, + [Species.WALKING_WAKE]: EggTier.EPIC, + [Species.IRON_LEAVES]: EggTier.EPIC, + [Species.POLTCHAGEIST]: EggTier.RARE, + [Species.OKIDOGI]: EggTier.EPIC, + [Species.MUNKIDORI]: EggTier.EPIC, + [Species.FEZANDIPITI]: EggTier.EPIC, + [Species.OGERPON]: EggTier.EPIC, + [Species.GOUGING_FIRE]: EggTier.EPIC, + [Species.RAGING_BOLT]: EggTier.EPIC, + [Species.IRON_BOULDER]: EggTier.EPIC, + [Species.IRON_CROWN]: EggTier.EPIC, + [Species.TERAPAGOS]: EggTier.LEGENDARY, + [Species.PECHARUNT]: EggTier.EPIC, + [Species.PALDEA_TAUROS]: EggTier.RARE, + [Species.PALDEA_WOOPER]: EggTier.COMMON, + [Species.BLOODMOON_URSALUNA]: EggTier.EPIC, +}; diff --git a/src/data/egg.ts b/src/data/egg.ts index 5fffe4fcece..c475fc729e6 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -11,6 +11,7 @@ import { EggTier } from "#enums/egg-type"; import { Species } from "#enums/species"; import { EggSourceType } from "#enums/egg-source-types"; import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates"; +import { speciesEggTiers } from "#app/data/balance/species-egg-tiers"; export const EGG_SEED = 1073741824; @@ -160,7 +161,7 @@ export class Egg { // Override egg tier and hatchwaves if species was given if (eggOptions?.species) { - this._tier = this.getEggTierFromSpeciesStarterValue(); + this._tier = this.getEggTier(); this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); } // If species has no variant, set variantTier to common. This needs to @@ -261,11 +262,11 @@ export class Egg { return "Manaphy"; } switch (this.tier) { - case EggTier.GREAT: + case EggTier.RARE: return i18next.t("egg:greatTier"); - case EggTier.ULTRA: + case EggTier.EPIC: return i18next.t("egg:ultraTier"); - case EggTier.MASTER: + case EggTier.LEGENDARY: return i18next.t("egg:masterTier"); default: return i18next.t("egg:defaultTier"); @@ -336,9 +337,9 @@ export class Egg { switch (eggTier ?? this._tier) { case EggTier.COMMON: return HATCH_WAVES_COMMON_EGG; - case EggTier.GREAT: + case EggTier.RARE: return HATCH_WAVES_RARE_EGG; - case EggTier.ULTRA: + case EggTier.EPIC: return HATCH_WAVES_EPIC_EGG; } return HATCH_WAVES_LEGENDARY_EGG; @@ -347,7 +348,7 @@ export class Egg { private rollEggTier(): EggTier { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; const tierValue = Utils.randInt(256); - return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; + return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY; } private rollSpecies(scene: BattleScene): Species | null { @@ -367,7 +368,7 @@ export class Egg { */ const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1); return rand ? Species.PHIONE : Species.MANAPHY; - } else if (this.tier === EggTier.MASTER + } else if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) { if (!Utils.randSeedInt(2)) { return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp); @@ -378,15 +379,15 @@ export class Egg { let maxStarterValue: integer; switch (this.tier) { - case EggTier.GREAT: + case EggTier.RARE: minStarterValue = 4; maxStarterValue = 5; break; - case EggTier.ULTRA: + case EggTier.EPIC: minStarterValue = 6; maxStarterValue = 7; break; - case EggTier.MASTER: + case EggTier.LEGENDARY: minStarterValue = 8; maxStarterValue = 9; break; @@ -398,8 +399,8 @@ export class Egg { const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; - let speciesPool = Object.keys(speciesStarterCosts) - .filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue) + let speciesPool = Object.keys(speciesEggTiers) + .filter(s => speciesEggTiers[s] === this.tier) .map(s => parseInt(s) as Species) .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); @@ -430,7 +431,9 @@ export class Egg { let totalWeight = 0; const speciesWeights : number[] = []; for (const speciesId of speciesPool) { - let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); + // Accounts for species that have starter costs outside of the normal range for their EggTier + const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue); + let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); const species = getPokemonSpecies(speciesId); if (species.isRegional()) { weight = Math.floor(weight / 2); @@ -498,16 +501,16 @@ export class Egg { private checkForPityTierOverrides(scene: BattleScene): void { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; - scene.gameData.eggPity[EggTier.GREAT] += 1; - scene.gameData.eggPity[EggTier.ULTRA] += 1; - scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; + scene.gameData.eggPity[EggTier.RARE] += 1; + scene.gameData.eggPity[EggTier.EPIC] += 1; + scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset; // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. - if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.MASTER; - } else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.ULTRA; - } else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.GREAT; + if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.LEGENDARY; + } else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.EPIC; + } else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.RARE; } scene.gameData.eggPity[this._tier] = 0; } @@ -516,38 +519,24 @@ export class Egg { scene.gameData.gameStats.eggsPulled++; if (this.isManaphyEgg()) { scene.gameData.gameStats.manaphyEggsPulled++; - this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA); + this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC); return; } switch (this.tier) { - case EggTier.GREAT: + case EggTier.RARE: scene.gameData.gameStats.rareEggsPulled++; break; - case EggTier.ULTRA: + case EggTier.EPIC: scene.gameData.gameStats.epicEggsPulled++; break; - case EggTier.MASTER: + case EggTier.LEGENDARY: scene.gameData.gameStats.legendaryEggsPulled++; break; } } - private getEggTierFromSpeciesStarterValue(): EggTier { - const speciesStartValue = speciesStarterCosts[this.species]; - if (speciesStartValue >= 1 && speciesStartValue <= 3) { - return EggTier.COMMON; - } - if (speciesStartValue >= 4 && speciesStartValue <= 5) { - return EggTier.GREAT; - } - if (speciesStartValue >= 6 && speciesStartValue <= 7) { - return EggTier.ULTRA; - } - if (speciesStartValue >= 8) { - return EggTier.MASTER; - } - - return EggTier.COMMON; + private getEggTier(): EggTier { + return speciesEggTiers[this.species]; } //// @@ -556,8 +545,8 @@ export class Egg { } export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { - const legendarySpecies = Object.entries(speciesStarterCosts) - .filter(s => s[1] >= 8 && s[1] <= 9) + const legendarySpecies = Object.entries(speciesEggTiers) + .filter(s => s[1] === EggTier.LEGENDARY) .map(s => parseInt(s[0])) .filter(s => getPokemonSpecies(s).isObtainable()); @@ -579,17 +568,9 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta /** * Check for a given species EggTier Value - * @param species - Species for wich we will check the egg tier it belongs to + * @param pokemonSpecies - Species for wich we will check the egg tier it belongs to * @returns The egg tier of a given pokemon species */ export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier { - const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()]; - if (speciesBaseValue <= 3) { - return EggTier.COMMON; - } else if (speciesBaseValue <= 5) { - return EggTier.GREAT; - } else if (speciesBaseValue <= 7) { - return EggTier.ULTRA; - } - return EggTier.MASTER; + return speciesEggTiers[pokemonSpecies.getRootSpeciesId()]; } diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index f3b886ac0ac..4f3420f5194 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -150,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, - tier: EggTier.ULTRA + tier: EggTier.EPIC }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); @@ -172,7 +172,7 @@ export const ATrainersTestEncounter: MysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, - tier: EggTier.GREAT + tier: EggTier.RARE }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`)); setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 4515736b30a..0ac82243862 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -494,7 +494,7 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: eggDescription, - tier: EggTier.GREAT + tier: EggTier.RARE }); } } diff --git a/src/enums/egg-type.ts b/src/enums/egg-type.ts index d8d0facb020..901e60b3c76 100644 --- a/src/enums/egg-type.ts +++ b/src/enums/egg-type.ts @@ -1,6 +1,6 @@ export enum EggTier { COMMON, - GREAT, - ULTRA, - MASTER + RARE, + EPIC, + LEGENDARY } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 4d85d5b8e1e..d8fcc281d1b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -983,7 +983,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); } - statHolder.value = Utils.clampInt(statHolder.value, 1, Number.MAX_SAFE_INTEGER); + statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); this.setStat(s, statHolder.value); } diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts index cf53cca5af8..6f57af63e6b 100644 --- a/src/test/eggs/egg.test.ts +++ b/src/test/eggs/egg.test.ts @@ -55,7 +55,7 @@ describe("Egg Generation Tests", () => { let gachaSpeciesCount = 0; for (let i = 0; i < EGG_HATCH_COUNT; i++) { - const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId; + const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId; if (result === expectedSpecies) { gachaSpeciesCount++; } @@ -82,7 +82,7 @@ describe("Egg Generation Tests", () => { }); it("should return an rare tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.GREAT; + const expectedTier = EggTier.RARE; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -90,7 +90,7 @@ describe("Egg Generation Tests", () => { }); it("should return an epic tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.ULTRA; + const expectedTier = EggTier.EPIC; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -98,7 +98,7 @@ describe("Egg Generation Tests", () => { }); it("should return an legendary tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.MASTER; + const expectedTier = EggTier.LEGENDARY; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -200,7 +200,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedEggTier = EggTier.COMMON; - const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).tier; + const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).tier; expect(result).toBe(expectedEggTier); }); @@ -208,7 +208,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedHatchWaves = 10; - const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).hatchWaves; + const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).hatchWaves; expect(result).toBe(expectedHatchWaves); }); @@ -229,7 +229,7 @@ describe("Egg Generation Tests", () => { const result = new EggData(legacyEgg).toEgg(); - expect(result.tier).toBe(EggTier.GREAT); + expect(result.tier).toBe(EggTier.RARE); expect(result.id).toBe(legacyEgg.id); expect(result.timestamp).toBe(legacyEgg.timestamp); expect(result.hatchWaves).toBe(legacyEgg.hatchWaves); @@ -241,9 +241,9 @@ describe("Egg Generation Tests", () => { new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON }); - expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); - expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); - expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 1); + expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1); + expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1); + expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 1); }); it("should increase legendary egg pity by two", () => { const scene = game.scene; @@ -251,9 +251,9 @@ describe("Egg Generation Tests", () => { new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON }); - expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); - expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); - expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 2); + expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1); + expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1); + expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 2); }); it("should not increase manaphy egg count if bulbasaurs are pulled", () => { const scene = game.scene; @@ -277,7 +277,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.GREAT }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.RARE }); expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1); }); @@ -285,7 +285,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.ULTRA }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.EPIC }); expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1); }); @@ -293,7 +293,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.MASTER }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.LEGENDARY }); expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1); }); @@ -301,8 +301,8 @@ describe("Egg Generation Tests", () => { vi.spyOn(Utils, "randInt").mockReturnValue(1); const scene = game.scene; - const expectedTier1 = EggTier.MASTER; - const expectedTier2 = EggTier.ULTRA; + const expectedTier1 = EggTier.LEGENDARY; + const expectedTier2 = EggTier.EPIC; const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier; const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier; diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index b1aa378d82a..7d783958422 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -128,7 +128,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + 1).toBe(eggsAfter.length); const eggTier = eggsAfter[eggsAfter.length - 1].tier; - expect(eggTier === EggTier.ULTRA || eggTier === EggTier.MASTER).toBeTruthy(); + expect(eggTier === EggTier.EPIC || eggTier === EggTier.LEGENDARY).toBeTruthy(); }); }); @@ -176,7 +176,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + 1).toBe(eggsAfter.length); const eggTier = eggsAfter[eggsAfter.length - 1].tier; - expect(eggTier).toBe(EggTier.GREAT); + expect(eggTier).toBe(EggTier.RARE); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 7e445ac1fe2..bbb4f249feb 100644 --- a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -155,7 +155,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); @@ -213,7 +213,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); @@ -271,7 +271,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 79b51ba6c44..1d97998f491 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -593,7 +593,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }; const updatePokemonHp = () => { - let duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; + let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; const speed = (this.scene as BattleScene).hpBarSpeed; if (speed) { duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 366f1604740..3aa009b1b31 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -471,9 +471,9 @@ export default class EggGachaUiHandler extends MessageUiHandler { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { switch (pullCount) { case 10: - return EggTier.GREAT; + return EggTier.RARE; case 25: - return EggTier.ULTRA; + return EggTier.EPIC; default: return EggTier.COMMON; } @@ -516,7 +516,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" }); eggText.setOrigin(0.5, 0); - eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.ULTRA)); + eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.EPIC)); ret.add(eggText); this.eggGachaSummaryContainer.addAt(ret, 0); diff --git a/src/ui/text.ts b/src/ui/text.ts index e6e1978118b..22dd3f4cd6a 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -356,11 +356,11 @@ export function getEggTierTextTint(tier: EggTier): integer { switch (tier) { case EggTier.COMMON: return getModifierTierTextTint(ModifierTier.COMMON); - case EggTier.GREAT: + case EggTier.RARE: return getModifierTierTextTint(ModifierTier.GREAT); - case EggTier.ULTRA: + case EggTier.EPIC: return getModifierTierTextTint(ModifierTier.ULTRA); - case EggTier.MASTER: + case EggTier.LEGENDARY: return getModifierTierTextTint(ModifierTier.MASTER); } } diff --git a/src/utils.ts b/src/utils.ts index 9cc95b00826..c2ee7100909 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,10 +38,6 @@ export function shiftCharCodes(str: string, shiftCount: integer) { return newStr; } -export function clampInt(value: integer, min: integer, max: integer): integer { - return Math.min(Math.max(value, min), max); -} - export function randGauss(stdev: number, mean: number = 0): number { if (!stdev) { return 0; From 5d0b36132061bae767dafdd3f27c6c1be12264df Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:19:05 -0700 Subject: [PATCH 14/19] [P2] Syrup Bomb effect is removed when user leaves the field (#4606) * Syrup Bomb's effect expires when the move user leaves the field * Add test * Remove check for the affected pokemon being switched out --- src/data/battler-tags.ts | 29 +++++++++++++---------------- src/test/moves/syrup_bomb.test.ts | 26 ++++++++++++++++++++------ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 24c82e54427..3cc109df264 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2640,16 +2640,16 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Battler Tag that applies the effects of Syrup Bomb to the target Pokemon. * For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1. - * The tag can also expire by taking the target Pokemon off the field. + * The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move. */ export class SyrupBombTag extends BattlerTag { - constructor() { - super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB); + constructor(sourceId: number) { + super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB, sourceId); } /** * Adds the Syrup Bomb battler tag to the target Pokemon. - * @param {Pokemon} pokemon the target Pokemon + * @param pokemon - The target {@linkcode Pokemon} */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); @@ -2658,15 +2658,16 @@ export class SyrupBombTag extends BattlerTag { /** * Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count - * @param {Pokemon} pokemon the target Pokemon - * @param {BattlerTagLapseType} _lapseType - * @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field + * @param pokemon - The target {@linkcode Pokemon} + * @param _lapseType - N/A + * @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field */ override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - if (!pokemon.isActive(true)) { + if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) { return false; } - pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline + // Custom message in lieu of an animation in mainline + pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.unshiftPhase(new StatStageChangePhase( pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], -1, true, false, true @@ -2677,12 +2678,8 @@ export class SyrupBombTag extends BattlerTag { /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. - * - * @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}. - * @param turnCount the turn count. - * @param {Moves} sourceMove the source {@linkcode Moves}. - * @param sourceId the source ID. - * @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object. + * @param sourceId - The ID of the pokemon adding the tag + * @returns The corresponding {@linkcode BattlerTag} object. */ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { switch (tagType) { @@ -2851,7 +2848,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source case BattlerTagType.IMPRISON: return new ImprisonTag(sourceId); case BattlerTagType.SYRUP_BOMB: - return new SyrupBombTag(); + return new SyrupBombTag(sourceId); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/test/moves/syrup_bomb.test.ts b/src/test/moves/syrup_bomb.test.ts index 7f914e45cc6..ea2f8b6bab3 100644 --- a/src/test/moves/syrup_bomb.test.ts +++ b/src/test/moves/syrup_bomb.test.ts @@ -1,4 +1,3 @@ -import { allMoves } from "#app/data/move"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; @@ -7,7 +6,7 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { BattlerIndex } from "#app/battle"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - SYRUP BOMB", () => { let phaserGame: Phaser.Game; @@ -26,20 +25,21 @@ describe("Moves - SYRUP BOMB", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .starterSpecies(Species.MAGIKARP) + .battleType("single") .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH) .startingLevel(30) .enemyLevel(100) .moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ]) .enemyMoveset(Moves.SPLASH); - vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100); }); //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move) it("decreases the target Pokemon's speed stat once per turn for 3 turns", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); const targetPokemon = game.scene.getEnemyPokemon()!; expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); @@ -66,7 +66,7 @@ describe("Moves - SYRUP BOMB", () => { it("does not affect Pokemon with the ability Bulletproof", async () => { game.override.enemyAbility(Abilities.BULLETPROOF); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); const targetPokemon = game.scene.getEnemyPokemon()!; @@ -79,4 +79,18 @@ describe("Moves - SYRUP BOMB", () => { expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); } ); + + it("stops lowering the target's speed if the user leaves the field", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + game.move.select(Moves.SYRUP_BOMB); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPD)).toBe(-1); + }); }); From 3f63c147a38ef9afb05c54bfe0983ed572d6f35b Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:44:51 -0400 Subject: [PATCH 15/19] [P3] Fix "Stat Won't Go Any Lower/Higher" Not Appearing (#4635) --- src/enums/stat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enums/stat.ts b/src/enums/stat.ts index a12d53e8559..6b3f7dc6d79 100644 --- a/src/enums/stat.ts +++ b/src/enums/stat.ts @@ -50,7 +50,7 @@ export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boo return isIncrease ? "battle:statRose" : "battle:statFell"; } else if (stages === 2) { return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell"; - } else if (stages <= 6) { + } else if (stages > 2 && stages <= 6) { return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell"; } return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower"; From bd83ab9f24238f8186028bcc3b2ab2c704316d16 Mon Sep 17 00:00:00 2001 From: pom-eranian Date: Thu, 10 Oct 2024 17:13:36 -0400 Subject: [PATCH 16/19] [Sprite][Anim] 275 Shiftree- cropped ear fix - @hamez --- public/images/pokemon/275.png | Bin 10555 -> 11106 bytes public/images/pokemon/female/275.png | Bin 10686 -> 11186 bytes public/images/pokemon/shiny/275.png | Bin 10555 -> 11107 bytes public/images/pokemon/shiny/female/275.png | Bin 10686 -> 11186 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/pokemon/275.png b/public/images/pokemon/275.png index 07a6fe725adfc61aad7a862a12678b088b533a37..61f98ed16555660d553a699e2dda6d8c0b2feb6f 100644 GIT binary patch literal 11106 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 diff --git a/public/images/pokemon/female/275.png b/public/images/pokemon/female/275.png index 7f251793a15764d2a7bb5849e65045ad7884eae3..c3c358716b2d919fc2a882d2f69e3ad524d57761 100644 GIT binary patch literal 11186 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≪pFCQ%=xE@hNoXB9038F;6HZ(ytRK>+ik2e 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

v^J8bg(f%?PGJxL)) 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 literal 10686 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 Date: Fri, 11 Oct 2024 16:17:21 -0400 Subject: [PATCH 17/19] Honchcrow Murkrow [Base fixes] --- public/images/pokemon/198.png | Bin 2645 -> 2779 bytes public/images/pokemon/430.png | Bin 6743 -> 7158 bytes public/images/pokemon/back/198.png | Bin 2495 -> 2725 bytes public/images/pokemon/back/430.png | Bin 8461 -> 9481 bytes public/images/pokemon/back/female/198.png | Bin 2457 -> 2634 bytes public/images/pokemon/female/198.png | Bin 2674 -> 2796 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/pokemon/198.png b/public/images/pokemon/198.png index 3d6c15f1a1dbb7ff0a4b9d63009c7a5f67ea636f..5a31021612959f59aab585b73b1c8e16540e991e 100644 GIT binary patch literal 2779 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%0D58V!OM`ajW_`s zpuT%02xjI)GJH1heg*&w`gL{!v>G6ulY~#Z21pBIQU*iWD3ueSAxbKit4YH4VipPC zG5m(o3(#hW5`*2VkRTeIX3|g@Px*W5FP@PAYJ)`b0gv13jd1wZ!4JM-B%bdKhc5yAApD`I4z27+)kLh-v%X!h}Tue=R;uK<8R5CX)5V-ll7 zcpLIwp=O~%{A`S35t{9GiXA3km$PliB$DJDBH=LS>JQ!SGW3&Zgk&g=gu@U!>;4)r zCD24jI1KCDHC#*}N$zu)nNU)J1d$H&e!XAs*ZcK9YQ?-03rs@d00000NkvXXu0mjf DHmB-g diff --git a/public/images/pokemon/430.png b/public/images/pokemon/430.png index 07aca365e02fb03dd7ab02db4708b62cf6b66cfe..5ed0fb19c203f54273f3f56dbaef7a98e1b13435 100644 GIT binary patch literal 7158 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 Da=X10 literal 6743 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 diff --git a/public/images/pokemon/back/430.png b/public/images/pokemon/back/430.png index 6c0fc8a5166984f1e0c2c7c5f84f8c717e7fbd48..5d58e6104d19149f7b6cfcbbd47529fdcfb2184a 100644 GIT binary patch literal 9481 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 H!+`$-tngSF literal 8461 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 literal 2457 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 From 11822573ea7730b1c3bbd2cc1f72cf726b7deae8 Mon Sep 17 00:00:00 2001 From: pom-eranian Date: Fri, 11 Oct 2024 16:25:43 -0400 Subject: [PATCH 18/19] Murkrow Honchkrow - Icons for variant & female murkrow --- public/images/pokemon/icons/2/198-f.png | Bin 0 -> 301 bytes public/images/pokemon/icons/2/198s-f.png | Bin 0 -> 362 bytes public/images/pokemon/icons/variant/2/198-f_2.png | Bin 0 -> 293 bytes public/images/pokemon/icons/variant/2/198-f_3.png | Bin 0 -> 293 bytes public/images/pokemon/icons/variant/2/198_2.png | Bin 0 -> 293 bytes public/images/pokemon/icons/variant/2/198_3.png | Bin 0 -> 293 bytes public/images/pokemon/icons/variant/4/430_2.png | Bin 0 -> 358 bytes public/images/pokemon/icons/variant/4/430_3.png | Bin 0 -> 357 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/pokemon/icons/2/198-f.png create mode 100644 public/images/pokemon/icons/2/198s-f.png create mode 100644 public/images/pokemon/icons/variant/2/198-f_2.png create mode 100644 public/images/pokemon/icons/variant/2/198-f_3.png create mode 100644 public/images/pokemon/icons/variant/2/198_2.png create mode 100644 public/images/pokemon/icons/variant/2/198_3.png create mode 100644 public/images/pokemon/icons/variant/4/430_2.png create mode 100644 public/images/pokemon/icons/variant/4/430_3.png diff --git a/public/images/pokemon/icons/2/198-f.png b/public/images/pokemon/icons/2/198-f.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc3e9bea4cc26625afbb7dc3f767c99823052c9 GIT binary patch literal 301 zcmV+|0n+}7P)X0002=Nklvps*W`ud^CpXdk3m>;y?QNeBrS z+Y1Dt(@YfGl$ljP)9LB1`w06?`%jV%?r-A_w`01w)r2Q+00000NkvXXu0mjfL5_qQ literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/2/198s-f.png b/public/images/pokemon/icons/2/198s-f.png new file mode 100644 index 0000000000000000000000000000000000000000..ccc258cafac7ff9acb4795e8a3f3e639a2f57938 GIT binary patch literal 362 zcmV-w0hRuVP)X0003oNklOb|*$#Ux1XD42qliUnAMnkJ%P2?`1}U;;|Rv-HsAuoD{pNZ8VoV=GcV zy}Pq7jkUHZSxp73fEDmx0Dl>6+gfPsy=Z{fP4^LDaYqE}7CcbnP(5&>VfFW1qTnwPk(WD`+znuZS09XIfKHMzlE&u=k07*qo IM6N<$g3Hq)$ literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/2/198-f_2.png b/public/images/pokemon/icons/variant/2/198-f_2.png new file mode 100644 index 0000000000000000000000000000000000000000..35e5c2e75ea0ec0832a71dd1e42a86130c429cdb GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#R9*LR^8g zf`WpUsF0hsLZ**Fm4M7zJrz||>+rDr=7t&n|Nq}SBl~4WwG~huPf3tpFauBs1~`ud zr~$>Nd%8G=SoFS~4e{RBzh)3xUl!R^V~1LwomO5zPP+CWq|jCso?Uu`-);oSWLxzkPjF6F*8 gJy7tr!2T7l&SR#n8Q-5)0-ea<>FVdQ&MBb@0PM7SA^-pY literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/2/198-f_3.png b/public/images/pokemon/icons/variant/2/198-f_3.png new file mode 100644 index 0000000000000000000000000000000000000000..f2920cd1d0e5edf861878086210c8488fc804d84 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#R9*LR^8g zf`S64oEV?8v231uq~*$uDbM^QMOz(p4;5Mc|NnoFcMIEbrUIZko{}KHUoj_jGX#vFLp}$x+D3ki&U))3ir_-)je?|9i#U7k&Bd>)pE&u=k literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/2/198_2.png b/public/images/pokemon/icons/variant/2/198_2.png new file mode 100644 index 0000000000000000000000000000000000000000..35e5c2e75ea0ec0832a71dd1e42a86130c429cdb GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#R9*LR^8g zf`WpUsF0hsLZ**Fm4M7zJrz||>+rDr=7t&n|Nq}SBl~4WwG~huPf3tpFauBs1~`ud zr~$>Nd%8G=SoFS~4e{RBzh)3xUl!R^V~1LwomO5zPP+CWq|jCso?Uu`-);oSWLxzkPjF6F*8 gJy7tr!2T7l&SR#n8Q-5)0-ea<>FVdQ&MBb@0PM7SA^-pY literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/2/198_3.png b/public/images/pokemon/icons/variant/2/198_3.png new file mode 100644 index 0000000000000000000000000000000000000000..f2920cd1d0e5edf861878086210c8488fc804d84 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a#R9*LR^8g zf`S64oEV?8v231uq~*$uDbM^QMOz(p4;5Mc|NnoFcMIEbrUIZko{}KHUoj_jGX#vFLp}$x+D3ki&U))3ir_-)je?|9i#U7k&Bd>)pE&u=k literal 0 HcmV?d00001 diff --git a/public/images/pokemon/icons/variant/4/430_2.png b/public/images/pokemon/icons/variant/4/430_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8026dd75141db9fe5861b511a0e6efff4aa7e755 GIT binary patch literal 358 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<^!3-obnb*7rQjEnx?oJHr&dIz4a&!ZHLR^8g zf`Wp&w3L&cT5@vo<{8;?ayp5Lx%PGenLY*DtBO<3@%2bDqmR*=-XO zK5wQ?tiRTkU{8J-HMS!XJM%Qw_#R^GJgdun{ZQ_09)rzcUzd9qdTLHQ`*Mxi!rSgH zrgPSui+U*YSK2mi_3rR)3mE|$zv*Y=?IwAxVc)W`IP!bitm{QDzZ908ZQ|c?XUVJm u{JY**74F)s?81F|?Ox_PcT@JSQ9tj__IC4@&!#|sF?hQAxvX?-oBv(N;&@Lq%45_wN1w|G$iQh`(vy^yovz9=Ck?;$N{YUC6q=uYSe_ptl%2UHx3vIVCg!0JdS6od5s; literal 0 HcmV?d00001 From 78c853aea0b221e781910c047279aa9ac8d4c770 Mon Sep 17 00:00:00 2001 From: pom-eranian Date: Fri, 11 Oct 2024 17:42:23 -0400 Subject: [PATCH 19/19] 198 430 Murkrow Honchkrow Variants [Epic Rare] --- public/images/pokemon/back/430.png | Bin 9481 -> 9435 bytes public/images/pokemon/variant/198.json | 34 +++++++++++++++ public/images/pokemon/variant/430.json | 40 ++++++++++++++++++ .../images/pokemon/variant/_masterlist.json | 35 +++++++++++++++ public/images/pokemon/variant/back/198.json | 26 ++++++++++++ public/images/pokemon/variant/back/430.json | 34 +++++++++++++++ .../pokemon/variant/back/female/198.json | 26 ++++++++++++ public/images/pokemon/variant/female/198.json | 34 +++++++++++++++ 8 files changed, 229 insertions(+) create mode 100644 public/images/pokemon/variant/198.json create mode 100644 public/images/pokemon/variant/430.json create mode 100644 public/images/pokemon/variant/back/198.json create mode 100644 public/images/pokemon/variant/back/430.json create mode 100644 public/images/pokemon/variant/back/female/198.json create mode 100644 public/images/pokemon/variant/female/198.json diff --git a/public/images/pokemon/back/430.png b/public/images/pokemon/back/430.png index 5d58e6104d19149f7b6cfcbbd47529fdcfb2184a..383685fa816bc2cb2d281d48533787cb7d34796f 100644 GIT binary patch delta 9392 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 j&ORH5553ht6LAOP)g7h$@_Fw-f`O5fQu9r~wE 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 diff --git a/public/images/pokemon/variant/198.json b/public/images/pokemon/variant/198.json new file mode 100644 index 00000000000..ab9084619f3 --- /dev/null +++ b/public/images/pokemon/variant/198.json @@ -0,0 +1,34 @@ +{ + "1": { + "d94352": "7a101c", + "314263": "462b20", + "d64252": "b3986b", + "73293a": "755237", + "ffad8c": "ad2e24", + "73283a": "630c17", + "d6404f": "8c1b23", + "efd684": "a6a6b3", + "42639c": "694c30", + "5a4a21": "25253b", + "292942": "2a1512", + "b59c21": "57566f", + "752a3c": "4d0419", + "d6bd52": "838098" + }, + "2": { + "d94352": "5939a9", + "314263": "0e4333", + "d64252": "bc4b84", + "73293a": "7b2363", + "ffad8c": "b164e6", + "73283a": "630c17", + "d6404f": "8c1b23", + "efd684": "c2723a", + "42639c": "1d6e47", + "5a4a21": "4e1915", + "292942": "091e16", + "b59c21": "85412d", + "752a3c": "1e1764", + "d6bd52": "9a5524" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/430.json b/public/images/pokemon/variant/430.json new file mode 100644 index 00000000000..e6771e34524 --- /dev/null +++ b/public/images/pokemon/variant/430.json @@ -0,0 +1,40 @@ +{ + "1": { + "31313a": "1d1d2b", + "efeff7": "b9382d", + "8c313a": "b3986b", + "5a5a3a": "1e1e2c", + "545454": "3c3b4d", + "29213a": "271b1a", + "3a5a9c": "694c30", + "8c3039": "9e2933", + "de5057": "bd392d", + "a3a3b5": "60606c", + "a5a5b5": "7a1e21", + "de525a": "f3e3b3", + "4a2121": "755237", + "ad943a": "3f3e50", + "3a3a5a": "422e26", + "525252": "380514", + "f7de3a": "61616d" + }, + "2": { + "31313a": "521a16", + "efeff7": "975bc2", + "8c313a": "bc4b84", + "5a5a3a": "4e1915", + "545454": "87432e", + "29213a": "091e16", + "3a5a9c": "1d6e47", + "8c3039": "9e2933", + "de5057": "bd392d", + "a3a3b5": "c4743b", + "a5a5b5": "4f358e", + "de525a": "f17f9c", + "4a2121": "7b2363", + "ad943a": "85412d", + "3a3a5a": "0e4333", + "525252": "1c1754", + "f7de3a": "c2723a" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index a93fd220b1f..10577dcd76c 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -654,6 +654,16 @@ 1, 1 ], + "198": [ + 0, + 1, + 1 + ], + "198-f": [ + 0, + 1, + 1 + ], "199": [ 2, 1, @@ -1579,6 +1589,11 @@ 1, 1 ], + "430": [ + 0, + 1, + 1 + ], "433": [ 1, 1, @@ -3830,6 +3845,11 @@ 1, 1 ], + "198": [ + 0, + 1, + 1 + ], "203": [ 0, 1, @@ -4657,6 +4677,11 @@ 1, 1 ], + "198": [ + 0, + 1, + 1 + ], "199": [ 2, 1, @@ -5582,6 +5607,11 @@ 1, 1 ], + "430": [ + 0, + 1, + 1 + ], "433": [ 1, 1, @@ -7863,6 +7893,11 @@ 1, 1 ], + "198": [ + 0, + 1, + 1 + ], "203": [ 0, 1, diff --git a/public/images/pokemon/variant/back/198.json b/public/images/pokemon/variant/back/198.json new file mode 100644 index 00000000000..eed8404265c --- /dev/null +++ b/public/images/pokemon/variant/back/198.json @@ -0,0 +1,26 @@ +{ + "1": { + "d94352": "8c1b23", + "314263": "462b20", + "efd684": "a6a6b3", + "d64252": "b3986b", + "42639c": "694c30", + "5a4a21": "25253b", + "292942": "2a1512", + "b59c21": "57566f", + "73293a": "755237", + "d6bd52": "838098" + }, + "2": { + "d94352": "8c1b23", + "314263": "0e4333", + "efd684": "c2723a", + "d64252": "bc4b84", + "42639c": "1d6e47", + "5a4a21": "4e1915", + "292942": "091e16", + "b59c21": "85412d", + "73293a": "7b2363", + "d6bd52": "9a5524" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/430.json b/public/images/pokemon/variant/back/430.json new file mode 100644 index 00000000000..2edd26708c9 --- /dev/null +++ b/public/images/pokemon/variant/back/430.json @@ -0,0 +1,34 @@ +{ + "1": { + "31313a": "280113", + "8c313a": "b3986b", + "5a5a3a": "1e1e2c", + "545454": "3c3b4d", + "a3a3b5": "7a1e21", + "3a5a9c": "694c30", + "525151": "380514", + "a7a7b8": "60606c", + "efeff8": "b9382d", + "4a2121": "755237", + "ad943a": "3f3e50", + "f7de3a": "61616d", + "3a3a5a": "422e26", + "29213a": "271b1a" + }, + "2": { + "31313a": "080735", + "8c313a": "bc4b84", + "5a5a3a": "4e1915", + "545454": "85412d", + "a3a3b5": "4f358e", + "3a5a9c": "1d6e47", + "525151": "1c1754", + "a7a7b8": "c27238", + "efeff8": "975bc2", + "4a2121": "7b2363", + "ad943a": "85402c", + "f7de3a": "c2723a", + "3a3a5a": "0e4333", + "29213a": "091e16" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/198.json b/public/images/pokemon/variant/back/female/198.json new file mode 100644 index 00000000000..eed8404265c --- /dev/null +++ b/public/images/pokemon/variant/back/female/198.json @@ -0,0 +1,26 @@ +{ + "1": { + "d94352": "8c1b23", + "314263": "462b20", + "efd684": "a6a6b3", + "d64252": "b3986b", + "42639c": "694c30", + "5a4a21": "25253b", + "292942": "2a1512", + "b59c21": "57566f", + "73293a": "755237", + "d6bd52": "838098" + }, + "2": { + "d94352": "8c1b23", + "314263": "0e4333", + "efd684": "c2723a", + "d64252": "bc4b84", + "42639c": "1d6e47", + "5a4a21": "4e1915", + "292942": "091e16", + "b59c21": "85412d", + "73293a": "7b2363", + "d6bd52": "9a5524" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/198.json b/public/images/pokemon/variant/female/198.json new file mode 100644 index 00000000000..b0386502899 --- /dev/null +++ b/public/images/pokemon/variant/female/198.json @@ -0,0 +1,34 @@ +{ + "1": { + "d94352": "b3986b", + "314263": "462b20", + "d64252": "7a101c", + "73293a": "4d0419", + "ffad8c": "ad2e24", + "73283a": "630c17", + "d6404f": "8c1b23", + "efd684": "a6a6b3", + "42639c": "694c30", + "5a4a21": "25253b", + "292942": "2a1512", + "b59c21": "57566f", + "752a3c": "755237", + "d6bd52": "838098" + }, + "2": { + "d94352": "bc4b84", + "314263": "0e4333", + "d64252": "5939a9", + "73293a": "1e1764", + "ffad8c": "b164e6", + "73283a": "630c17", + "d6404f": "8c1b23", + "efd684": "c2723a", + "42639c": "1d6e47", + "5a4a21": "4e1915", + "292942": "091e16", + "b59c21": "85412d", + "752a3c": "7b2363", + "d6bd52": "9a5524" + } +} \ No newline at end of file