From ab2d5bcfb5aceecf2630a286efe1acb9cdd63ff1 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Mon, 27 Oct 2025 21:12:14 -0400 Subject: [PATCH 001/101] [Tests] Cleaned up `cachedFetch` test stub --- test/test-utils/game-wrapper.ts | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/test/test-utils/game-wrapper.ts b/test/test-utils/game-wrapper.ts index 20b29362927..bfaa645c99b 100644 --- a/test/test-utils/game-wrapper.ts +++ b/test/test-utils/game-wrapper.ts @@ -182,21 +182,16 @@ export class GameWrapper { this.scene.scene = this.scene; this.scene.input.keyboard = new KeyboardPlugin(this.scene); this.scene.input.gamepad = new GamepadPlugin(this.scene); - this.scene.cachedFetch = (url, _init) => { - return new Promise(resolve => { - // need to remove that if later we want to test battle-anims - const newUrl = url.includes("./battle-anims/") ? prependPath("./battle-anims/tackle.json") : prependPath(url); - // biome-ignore lint/suspicious/noImplicitAnyLet: TODO - let raw; - try { - raw = fs.readFileSync(newUrl, { encoding: "utf8", flag: "r" }); - } catch (_e) { - return resolve(createFetchBadResponse({})); - } - const data = JSON.parse(raw); - const response = createFetchResponse(data); - return resolve(response); - }); + this.scene.cachedFetch = async (url, _init): Promise => { + // Replace all battle anim fetches solely with the tackle anim to save time. + // TODO: This effectively bars us from testing battle animation related code ever + const newUrl = url.includes("./battle-anims/") ? prependPath("./battle-anims/tackle.json") : prependPath(url); + try { + const raw = fs.readFileSync(newUrl, { encoding: "utf8", flag: "r" }); + return createFetchResponse(JSON.parse(raw)); + } catch { + return createFetchBadResponse({}); + } }; this.scene.make = new MockGameObjectCreator(mockTextureManager); this.scene.time = new MockClock(this.scene); @@ -213,7 +208,7 @@ function prependPath(originalPath) { return originalPath; } // Simulate fetch response -function createFetchResponse(data) { +function createFetchResponse(data: unknown): Response { return { ok: true, status: 200, @@ -223,7 +218,7 @@ function createFetchResponse(data) { }; } // Simulate fetch response -function createFetchBadResponse(data) { +function createFetchBadResponse(data: unknown): Response { return { ok: false, status: 404, From 30e6e1f00ce6a0e80ac9042109bd7029785f4241 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Fri, 31 Oct 2025 00:37:29 -0400 Subject: [PATCH 002/101] [Dev] Add `.zed` to `gitignore` --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c74ad9ed5c3..e45ff602303 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ build # Editor directories and files (excluding `extensions.json` for devcontainer) *.code-workspace .vscode/* +.zed !.vscode/extensions.json !.vscode/spdx.code-snippets .idea @@ -50,6 +51,5 @@ coverage /dependency-graph.svg /.vs - # Script outputs -./*.csv \ No newline at end of file +./*.csv From 58f42bc5c2a0351f934b1c9eb0346d828a12e9a8 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Sat, 1 Nov 2025 15:55:06 -0500 Subject: [PATCH 003/101] Fix formatting in pokemon-evolutions.ts No functional changes, just matching how all other evolutions are formatted in the file. --- src/data/balance/pokemon-evolutions.ts | 96 +++++++++++++++++++------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index b33f690e04d..04c3660d2eb 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -655,10 +655,18 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, null), new SpeciesEvolution(SpeciesId.GALLADE, 1, EvolutionItem.DAWN_STONE, {key: EvoCondKey.GENDER, gender: Gender.MALE}, [30, 30, 30]), ], - [SpeciesId.SURSKIT]: [new SpeciesEvolution(SpeciesId.MASQUERAIN, 22, null, null)], - [SpeciesId.SHROOMISH]: [new SpeciesEvolution(SpeciesId.BRELOOM, 23, null, null)], - [SpeciesId.SLAKOTH]: [new SpeciesEvolution(SpeciesId.VIGOROTH, 18, null, null)], - [SpeciesId.VIGOROTH]: [new SpeciesEvolution(SpeciesId.SLAKING, 36, null, null)], + [SpeciesId.SURSKIT]: [ + new SpeciesEvolution(SpeciesId.MASQUERAIN, 22, null, null) + ], + [SpeciesId.SHROOMISH]: [ + new SpeciesEvolution(SpeciesId.BRELOOM, 23, null, null) + ], + [SpeciesId.SLAKOTH]: [ + new SpeciesEvolution(SpeciesId.VIGOROTH, 18, null, null) + ], + [SpeciesId.VIGOROTH]: [ + new SpeciesEvolution(SpeciesId.SLAKING, 36, null, null) + ], [SpeciesId.NINCADA]: [ new SpeciesEvolution(SpeciesId.NINJASK, 20, null, null), new SpeciesEvolution(SpeciesId.SHEDINJA, 20, null, {key: EvoCondKey.SHEDINJA}) @@ -736,26 +744,66 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(SpeciesId.GLALIE, 42, null, null), new SpeciesEvolution(SpeciesId.FROSLASS, 1, EvolutionItem.DAWN_STONE, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}, [42, 42, 42]), ], - [SpeciesId.SPHEAL]: [new SpeciesEvolution(SpeciesId.SEALEO, 32, null, null)], - [SpeciesId.SEALEO]: [new SpeciesEvolution(SpeciesId.WALREIN, 44, null, null)], - [SpeciesId.BAGON]: [new SpeciesEvolution(SpeciesId.SHELGON, 30, null, null)], - [SpeciesId.SHELGON]: [new SpeciesEvolution(SpeciesId.SALAMENCE, 50, null, null)], - [SpeciesId.BELDUM]: [new SpeciesEvolution(SpeciesId.METANG, 20, null, null)], - [SpeciesId.METANG]: [new SpeciesEvolution(SpeciesId.METAGROSS, 45, null, null)], - [SpeciesId.TURTWIG]: [new SpeciesEvolution(SpeciesId.GROTLE, 18, null, null)], - [SpeciesId.GROTLE]: [new SpeciesEvolution(SpeciesId.TORTERRA, 32, null, null)], - [SpeciesId.CHIMCHAR]: [new SpeciesEvolution(SpeciesId.MONFERNO, 14, null, null)], - [SpeciesId.MONFERNO]: [new SpeciesEvolution(SpeciesId.INFERNAPE, 36, null, null)], - [SpeciesId.PIPLUP]: [new SpeciesEvolution(SpeciesId.PRINPLUP, 16, null, null)], - [SpeciesId.PRINPLUP]: [new SpeciesEvolution(SpeciesId.EMPOLEON, 36, null, null)], - [SpeciesId.STARLY]: [new SpeciesEvolution(SpeciesId.STARAVIA, 14, null, null)], - [SpeciesId.STARAVIA]: [new SpeciesEvolution(SpeciesId.STARAPTOR, 34, null, null)], - [SpeciesId.BIDOOF]: [new SpeciesEvolution(SpeciesId.BIBAREL, 15, null, null)], - [SpeciesId.KRICKETOT]: [new SpeciesEvolution(SpeciesId.KRICKETUNE, 10, null, null)], - [SpeciesId.SHINX]: [new SpeciesEvolution(SpeciesId.LUXIO, 15, null, null)], - [SpeciesId.LUXIO]: [new SpeciesEvolution(SpeciesId.LUXRAY, 30, null, null)], - [SpeciesId.CRANIDOS]: [new SpeciesEvolution(SpeciesId.RAMPARDOS, 30, null, null)], - [SpeciesId.SHIELDON]: [new SpeciesEvolution(SpeciesId.BASTIODON, 30, null, null)], + [SpeciesId.SPHEAL]: [ + new SpeciesEvolution(SpeciesId.SEALEO, 32, null, null) + ], + [SpeciesId.SEALEO]: [ + new SpeciesEvolution(SpeciesId.WALREIN, 44, null, null) + ], + [SpeciesId.BAGON]: [ + new SpeciesEvolution(SpeciesId.SHELGON, 30, null, null) + ], + [SpeciesId.SHELGON]: [ + new SpeciesEvolution(SpeciesId.SALAMENCE, 50, null, null) + ], + [SpeciesId.BELDUM]: [ + new SpeciesEvolution(SpeciesId.METANG, 20, null, null) + ], + [SpeciesId.METANG]: [ + new SpeciesEvolution(SpeciesId.METAGROSS, 45, null, null) + ], + [SpeciesId.TURTWIG]: [ + new SpeciesEvolution(SpeciesId.GROTLE, 18, null, null) + ], + [SpeciesId.GROTLE]: [ + new SpeciesEvolution(SpeciesId.TORTERRA, 32, null, null) + ], + [SpeciesId.CHIMCHAR]: [ + new SpeciesEvolution(SpeciesId.MONFERNO, 14, null, null) + ], + [SpeciesId.MONFERNO]: [ + new SpeciesEvolution(SpeciesId.INFERNAPE, 36, null, null) + ], + [SpeciesId.PIPLUP]: [ + new SpeciesEvolution(SpeciesId.PRINPLUP, 16, null, null) + ], + [SpeciesId.PRINPLUP]: [ + new SpeciesEvolution(SpeciesId.EMPOLEON, 36, null, null) + ], + [SpeciesId.STARLY]: [ + new SpeciesEvolution(SpeciesId.STARAVIA, 14, null, null) + ], + [SpeciesId.STARAVIA]: [ + new SpeciesEvolution(SpeciesId.STARAPTOR, 34, null, null) + ], + [SpeciesId.BIDOOF]: [ + new SpeciesEvolution(SpeciesId.BIBAREL, 15, null, null) + ], + [SpeciesId.KRICKETOT]: [ + new SpeciesEvolution(SpeciesId.KRICKETUNE, 10, null, null) + ], + [SpeciesId.SHINX]: [ + new SpeciesEvolution(SpeciesId.LUXIO, 15, null, null) + ], + [SpeciesId.LUXIO]: [ + new SpeciesEvolution(SpeciesId.LUXRAY, 30, null, null) + ], + [SpeciesId.CRANIDOS]: [ + new SpeciesEvolution(SpeciesId.RAMPARDOS, 30, null, null) + ], + [SpeciesId.SHIELDON]: [ + new SpeciesEvolution(SpeciesId.BASTIODON, 30, null, null) + ], [SpeciesId.BURMY]: [ new SpeciesEvolution(SpeciesId.MOTHIM, 20, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}), new SpeciesEvolution(SpeciesId.WORMADAM, 20, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}) From 4e8b8862a19fe80a3302e851f26fd063ed6e2241 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 1 Nov 2025 18:51:30 -0400 Subject: [PATCH 004/101] [Dev] Add `biome:staged` command to lint currently staged files (#6737) * [Dev] Adde `biome:staged` command to lint currently staged files * Apply suggestion from @DayKev Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- lefthook.yml | 4 ++-- package.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index e5ed457a1b6..9b4be96056f 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -8,9 +8,9 @@ pre-commit: - rebase commands: biome-lint: - # Disable colors as certain IDEs don't support it in the output pane. + # Disable colors as certain IDEs (such as VSCode) don't support it in the output pane. # Summary mode looks decent in plain ASCII anyhow - run: pnpm exec biome check --write --colors=off --reporter=summary --staged --no-errors-on-unmatched --diagnostic-level=error + run: pnpm biome:staged --colors=off --reporter=summary stage_fixed: true ls-lint: run: pnpm exec ls-lint diff --git a/package.json b/package.json index e08e5a393a4..0730cc5197b 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "typecheck": "tsc --noEmit", "typecheck:scripts": "tsc -p scripts/jsconfig.json", "biome": "biome check --write --changed --no-errors-on-unmatched --diagnostic-level=error", + "biome:staged": "biome check --write --staged --no-errors-on-unmatched --diagnostic-level=error", "biome:all": "biome check --write --no-errors-on-unmatched --diagnostic-level=error", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "typedoc": "typedoc", From f41752c3f9b6a619b6958333d9dfe8688701ab1c Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:09:36 -0400 Subject: [PATCH 005/101] [Test] Fix test end log check mark (#6726) * [Test] Fix test end log check mark heavy check mark looks weird on more fonts than the normal one * Added variant selector codepoint to force rendering as thin version * Update test/test-utils/setup/test-end-log.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Changed checkmark back to normal one idfk how this works maaaan --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- test/test-utils/setup/test-end-log.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test-utils/setup/test-end-log.ts b/test/test-utils/setup/test-end-log.ts index 5be8299b124..a230cccca11 100644 --- a/test/test-utils/setup/test-end-log.ts +++ b/test/test-utils/setup/test-end-log.ts @@ -66,8 +66,8 @@ function getResultStr(result: RunnerTaskResult | undefined): string { const resultStr = result.state === "pass" - ? chalk.green.bold("✔ Passed") - : (result?.duration ?? 0) > 2 + ? chalk.green.bold("✓ Passed") + : (result?.duration ?? 0) > 20_000 ? chalk.cyan.bold("◴ Timed out") : chalk.red.bold("✗ Failed"); From 4b9ccf5408d323e0d626d55ece12933745d87de6 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:18:04 -0400 Subject: [PATCH 006/101] [Bug] Fix `NoTransformAbilityAttr` not doing anything (#6693) [Bug] Fix `NoTransformAbilityAttr` not doing anythin --- src/field/pokemon.ts | 3 +++ test/abilities/shields-down.test.ts | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 1d58f7de883..03521359ce4 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2200,6 +2200,9 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isFusion() && ability.hasAttr("NoFusionAbilityAbAttr")) { return false; } + if (this.isTransformed() && ability.hasAttr("NoTransformAbilityAbAttr")) { + return false; + } const arena = globalScene?.arena; if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.ignorable) { return false; diff --git a/test/abilities/shields-down.test.ts b/test/abilities/shields-down.test.ts index 77cec525442..ff07f2c9560 100644 --- a/test/abilities/shields-down.test.ts +++ b/test/abilities/shields-down.test.ts @@ -74,7 +74,7 @@ describe("Abilities - Shields Down", () => { minior.hp = 0; minior.status = new Status(StatusEffect.FAINT); - expect(minior.isFainted()).toBe(true); + expect(minior).toHaveFainted(); game.move.use(MoveId.SPLASH); await game.doKillOpponents(); @@ -176,15 +176,15 @@ describe("Abilities - Shields Down", () => { expect(game.field.getPlayerPokemon()).toHaveBattlerTag(BattlerTagType.CONFUSED); }); - // TODO: The `NoTransformAbilityAbAttr` attribute is not checked anywhere, so this test cannot pass. - // TODO: Move this to a transform test - it.todo("should not activate when transformed", async () => { + it("should not activate when transformed", async () => { game.override.enemyAbility(AbilityId.IMPOSTER); await game.classicMode.startBattle([SpeciesId.MINIOR]); game.move.use(MoveId.SPORE); await game.toEndOfTurn(); - expect(game.field.getEnemyPokemon()).toHaveStatusEffect(StatusEffect.SLEEP); + const karp = game.field.getEnemyPokemon(); + expect(karp).not.toHaveAbilityApplied(AbilityId.SHIELDS_DOWN); + expect(karp).toHaveStatusEffect(StatusEffect.SLEEP); }); }); From 6887de8809936ef012cc6c8d29bd9244915013a7 Mon Sep 17 00:00:00 2001 From: Ovdoes-dd <13681184556@163.com> Date: Sun, 2 Nov 2025 07:27:28 +0800 Subject: [PATCH 007/101] [Docs] Fix link path for overrides.ts in CONTRIBUTING.md (#6706) Fix link path for overrides.ts in CONTRIBUTING.md Updated link path for overrides.ts in manual testing section. Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d8f5468fbf..0756711d686 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,7 +103,7 @@ You've just made a change - how can you check if it works? You have two areas to > This will likely be your first stop. After making a change, you'll want to spin the game up and make sure everything is as you expect. To do this, you will need a way to manipulate the game to produce the situation you're looking to test. -[src/overrides.ts](../src/overrides.ts) contains overrides for most values you'll need to change for testing, controlled through the `overrides` object. +[src/overrides.ts](./src/overrides.ts) contains overrides for most values you'll need to change for testing, controlled through the `overrides` object. For example, here is how you could test a scenario where the player Pokemon has the ability Drought and the enemy Pokemon has the move Water Gun: ```typescript @@ -135,4 +135,4 @@ Most non-trivial changes (*especially bug fixes*) should come along with new tes > Some issues may require you to have unlocks on your save file which go beyond normal overrides. For this reason, the repository contains a [save file](../test/test-utils/saves/everything.psrv) with _everything_ unlocked (even ones not legitimately obtainable, like unimplemented variant shinies). 1. Start the game up locally and navigate to `Menu -> Manage Data -> Import Data` -2. Select [everything.prsv](test/test-utils/saves/everything.prsv) (`test/test-utils/saves/everything.prsv`) and confirm. \ No newline at end of file +2. Select [everything.prsv](test/test-utils/saves/everything.prsv) (`test/test-utils/saves/everything.prsv`) and confirm. From 301ad0e7c13875b91dac8cc5fa4339cf6a233941 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:35:49 -0700 Subject: [PATCH 008/101] [Dev] Remove `overrides.ts` from `.gitignore` (#6721) This didn't actually prevent changes to the file from being committed, but it did prevent Biome from formatting the file --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index e45ff602303..243d75097ef 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ assets/images/pokemon/icons/input/output/* assets/images/character/*/ src/data/battle-anim-raw-data*.ts src/data/battle-anim-data.ts -src/overrides.ts coverage /.vs From b2089012c1145d641bd6b8de9c0fb452ff52466c Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:41:54 -0700 Subject: [PATCH 009/101] [Bug] Show base moves and ability in run info instead of transformed ones (#6702) Show base moves and ability in run info Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> --- src/ui/handlers/run-info-ui-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/handlers/run-info-ui-handler.ts b/src/ui/handlers/run-info-ui-handler.ts index 574daddd30f..1fb32c296a0 100644 --- a/src/ui/handlers/run-info-ui-handler.ts +++ b/src/ui/handlers/run-info-ui-handler.ts @@ -774,7 +774,7 @@ export class RunInfoUiHandler extends UiHandler { abilityLabel = abilityLabel.charAt(0); } const pPassiveInfo = pokemon.passive ? passiveLabel + ": " + pokemon.getPassiveAbility().name : ""; - const pAbilityInfo = abilityLabel + ": " + pokemon.getAbility().name; + const pAbilityInfo = abilityLabel + ": " + pokemon.getAbility(true).name; // Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12. const lineSpacing = i18next.resolvedLanguage === "ja" ? 3 : 3; const pokeInfoText = addBBCodeTextObject(0, 0, pName, TextStyle.SUMMARY, { @@ -859,7 +859,7 @@ export class RunInfoUiHandler extends UiHandler { // Pokemon Moveset // Need to check if dynamically typed moves - const pokemonMoveset = pokemon.getMoveset(); + const pokemonMoveset = pokemon.getMoveset(true); const movesetContainer = globalScene.add.container(70, -29); const pokemonMoveBgs: Phaser.GameObjects.NineSlice[] = []; const pokemonMoveLabels: Phaser.GameObjects.Text[] = []; From d3088c1729ea5c89d9e85d60f21f72a2efc1b3c8 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:38:04 -0400 Subject: [PATCH 010/101] [Dev] Add more Biome rules (#6604) * Added `noBannedTypes` as a biome rule * Added `useShorthandAssign` rule * Added `useConsistentArrayType` * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/pokeball.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Apply Biome after merge --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- biome.jsonc | 14 ++++- src/battle-scene.ts | 4 +- src/data/battle-anims.ts | 6 +- src/data/challenge.ts | 2 +- src/data/dialogue.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 6 +- src/data/pokeball.ts | 3 +- src/data/pokemon-species.ts | 2 +- src/data/status-effect.ts | 2 +- src/field/anims.ts | 2 +- src/field/pokemon.ts | 32 +++++------ src/inputs-controller.ts | 6 +- src/modifier/modifier.ts | 4 +- src/overrides.ts | 4 +- src/plugins/i18n.ts | 4 +- src/system/settings/settings.ts | 2 +- src/ui/handlers/awaitable-ui-handler.ts | 5 +- src/ui/handlers/battle-message-ui-handler.ts | 4 +- src/ui/handlers/egg-gacha-ui-handler.ts | 2 +- src/ui/handlers/form-modal-ui-handler.ts | 3 +- src/ui/handlers/game-stats-ui-handler.ts | 5 +- src/ui/handlers/login-form-ui-handler.ts | 57 ++++++++++--------- src/ui/handlers/menu-ui-handler.ts | 2 +- src/ui/handlers/message-ui-handler.ts | 8 +-- src/ui/handlers/modifier-select-ui-handler.ts | 5 +- src/ui/handlers/party-ui-handler.ts | 2 +- src/ui/handlers/pokedex-page-ui-handler.ts | 2 +- src/ui/handlers/pokedex-scan-ui-handler.ts | 2 +- src/ui/handlers/pokedex-ui-handler.ts | 5 +- .../handlers/registration-form-ui-handler.ts | 2 +- src/ui/handlers/rename-form-ui-handler.ts | 2 +- src/ui/handlers/rename-run-ui-handler.ts | 2 +- .../handlers/save-slot-select-ui-handler.ts | 2 +- src/ui/handlers/starter-select-ui-handler.ts | 2 +- src/ui/handlers/summary-ui-handler.ts | 46 +++++++++++---- src/ui/handlers/test-dialogue-ui-handler.ts | 4 +- .../abstract-control-settings-ui-handler.ts | 6 +- .../settings/abstract-settings-ui-handler.ts | 5 +- src/ui/ui.ts | 4 +- test/abilities/dry-skin.test.ts | 2 +- test/abilities/volt-absorb.test.ts | 4 +- .../mocks/mocks-container/mock-container.ts | 1 + .../mocks/mocks-container/mock-text.ts | 6 +- 44 files changed, 162 insertions(+), 125 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 994720ef9e3..277edf89366 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -116,7 +116,17 @@ }, "useCollapsedIf": "error", "useCollapsedElseIf": "error", - + "useDeprecatedReason": "error", + "useConsistentArrayType": { + "level": "error", + "fix": "safe", + "options": {} + }, + "useShorthandAssign": { + "level": "error", + "fix": "safe", + "options": {} + }, "noSubstr": "error", "noYodaExpression": "error", "useForOf": "error", @@ -205,7 +215,7 @@ "noForEach": "off", // Foreach vs for of is not that simple. "noUselessSwitchCase": "off", // Explicit > Implicit "noUselessConstructor": "error", - "noBannedTypes": "warn", // TODO: Refactor and make this an error + "noBannedTypes": "error", "noThisInStatic": "error", "noUselessThisAlias": "error", "noUselessTernary": "error", diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a7679f4479f..b0199980981 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3625,9 +3625,9 @@ export class BattleScene extends SceneBase { // biome-ignore format: biome sucks at formatting this line for (const seenEncounterData of this.mysteryEncounterSaveData.encounteredEvents) { if (seenEncounterData.tier === MysteryEncounterTier.COMMON) { - tierWeights[0] = tierWeights[0] - 6; + tierWeights[0] -= 6; } else if (seenEncounterData.tier === MysteryEncounterTier.GREAT) { - tierWeights[1] = tierWeights[1] - 4; + tierWeights[1] -= 4; } } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index e1966805b40..fb9082d6ef6 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -821,7 +821,7 @@ export abstract class BattleAnim { frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2]) ) { - scaleX = scaleX * -1; + scaleX *= -1; } } break; @@ -835,7 +835,7 @@ export abstract class BattleAnim { } // biome-ignore lint/complexity/noBannedTypes: callback is used liberally - play(onSubstitute?: boolean, callback?: Function) { + play(onSubstitute?: boolean, callback?: () => void) { const isOppAnim = this.isOppAnim(); const user = isOppAnim ? this.target! : this.user!; const target = isOppAnim ? this.user! : this.target!; // TODO: These bangs are LITERALLY not correct at all @@ -1179,7 +1179,7 @@ export abstract class BattleAnim { frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, // biome-ignore lint/complexity/noBannedTypes: callback is used liberally - callback?: Function, + callback?: () => void, ) { const spriteCache: SpriteCache = { [AnimFrameTarget.GRAPHIC]: [], diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 67d0bf60a7f..22d48432376 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -933,7 +933,7 @@ export class FreshStartChallenge extends Challenge { } applyStarterModify(pokemon: Pokemon): boolean { - pokemon.abilityIndex = pokemon.abilityIndex % 2; // Always base ability, if you set it to hidden it wraps to first ability + pokemon.abilityIndex %= 2; // Always base ability, if you set it to hidden it wraps to first ability pokemon.passive = false; // Passive isn't unlocked let validMoves = pokemon.species .getLevelMoves() diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index 1d672397777..802615a8aef 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -10,7 +10,7 @@ export interface TrainerTypeMessages { } export interface TrainerTypeDialogue { - [key: number]: TrainerTypeMessages | Array; + [key: number]: TrainerTypeMessages | TrainerTypeMessages[]; } export function getTrainerTypeDialogue(): TrainerTypeDialogue { diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 8b28065dad1..644d03658f3 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -567,7 +567,7 @@ function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, ba bouncePower = bouncePower > 0.01 ? bouncePower * 0.5 : 0; if (bouncePower) { - bounceYOffset = bounceYOffset * bouncePower; + bounceYOffset *= bouncePower; globalScene.tweens.add({ targets: berrySprites, diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 64162a1b2ad..b21d25955ca 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -739,7 +739,7 @@ export function selectOptionThenPokemon( export function setEncounterRewards( customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], - preRewardsCallback?: Function, + preRewardsCallback?: () => void, ): void { globalScene.currentBattle.mysteryEncounter!.doEncounterRewards = () => { if (preRewardsCallback) { @@ -1172,8 +1172,8 @@ export function calculateMEAggregateStats(baseSpawnWeight: number): void { const tierWeights = [66, 40, 19, 3]; // Adjust tier weights by currently encountered events (pity system that lowers odds of multiple Common/Great) - tierWeights[0] = tierWeights[0] - 6 * numEncounters[0]; - tierWeights[1] = tierWeights[1] - 4 * numEncounters[1]; + tierWeights[0] -= 6 * numEncounters[0]; + tierWeights[1] -= 4 * numEncounters[1]; const totalWeight = tierWeights.reduce((a, b) => a + b); const tierValue = randSeedInt(totalWeight); diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 50ea5076aee..52241597dd7 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -115,8 +115,7 @@ export function doPokeballBounceAnim( y1: number, y2: number, baseBounceDuration: number, - // biome-ignore lint/complexity/noBannedTypes: TODO - callback: Function, + callback: () => void, isCritical = false, ) { let bouncePower = 1; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index fe8a80f7df1..5ee057704df 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -421,7 +421,7 @@ export abstract class PokemonSpeciesForm { case SpeciesId.BLOODMOON_URSALUNA: break; default: - speciesId = speciesId % 2000; + speciesId %= 2000; break; } } diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index 34cd2d87617..389a25b805c 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -158,7 +158,7 @@ export function getRandomStatus(statusA: Status | null, statusB: Status | null): * Gets all non volatile status effects * @returns A list containing all non volatile status effects */ -export function getNonVolatileStatusEffects(): Array { +export function getNonVolatileStatusEffects(): StatusEffect[] { return [ StatusEffect.POISON, StatusEffect.TOXIC, diff --git a/src/field/anims.ts b/src/field/anims.ts index 82aa7b7e894..e6ba014f4b8 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -134,7 +134,7 @@ function doFanOutParticle( } particle.x = x + sin(trigIndex, f * xSpeed); particle.y = y + cos(trigIndex, f * ySpeed); - trigIndex = trigIndex + angle; + trigIndex += angle; f++; }; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 03521359ce4..2801fef0ffa 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1847,21 +1847,21 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { * @returns An array of {@linkcode PokemonMove}, as described above. */ getMoveset(ignoreOverride = false): PokemonMove[] { - // Overrides moveset based on arrays specified in overrides.ts - let overrideArray: MoveId | Array = this.isPlayer() - ? Overrides.MOVESET_OVERRIDE - : Overrides.ENEMY_MOVESET_OVERRIDE; - overrideArray = coerceArray(overrideArray); - if (overrideArray.length > 0) { - if (!this.isPlayer()) { - this.moveset = []; - } - overrideArray.forEach((move: MoveId, index: number) => { - const ppUsed = this.moveset[index]?.ppUsed ?? 0; - this.moveset[index] = new PokemonMove(move, Math.min(ppUsed, allMoves[move].pp)); - }); + // Override moveset based on arrays specified in overrides.ts + const overrideArray = coerceArray(this.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.ENEMY_MOVESET_OVERRIDE); + if (overrideArray.length === 0) { + return !ignoreOverride && this.summonData.moveset ? this.summonData.moveset : this.moveset; } + if (!this.isPlayer()) { + this.moveset = []; + } + // TODO: Preserve PP used while the moveset override is active + overrideArray.forEach((move: MoveId, index: number) => { + const ppUsed = this.moveset[index]?.ppUsed ?? 0; + this.moveset[index] = new PokemonMove(move, Math.min(ppUsed, allMoves[move].pp)); + }); + return !ignoreOverride && this.summonData.moveset ? this.summonData.moveset : this.moveset; } @@ -2658,10 +2658,10 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { hpDiffRatio = 1 - hpRatio + (outspeed ? 0.2 : 0.1); } } else if (outspeed) { - hpDiffRatio = hpDiffRatio * 1.25; + hpDiffRatio *= 1.25; } else if (hpRatio > 0.2 && hpRatio <= 0.4) { // Might be considered to be switched because it's not in low enough health - hpDiffRatio = hpDiffRatio * 0.5; + hpDiffRatio *= 0.5; } return (atkScore + defScore) * Math.min(hpDiffRatio, 1); } @@ -3909,7 +3909,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } damage = Math.min(damage, this.hp); - this.hp = this.hp - damage; + this.hp -= damage; if (this.isFainted() && !ignoreFaintPhase) { globalScene.phaseManager.queueFaintPhase(this.getBattlerIndex(), preventEndure); this.destroySubstitute(); diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index 0207297fd58..afe374f24dd 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -70,7 +70,7 @@ const repeatInputDelayMillis = 250; * providing a unified interface for all input-related interactions. */ export class InputsController { - private gamepads: Array = []; + private gamepads: Phaser.Input.Gamepad.Gamepad[] = []; public events: Phaser.Events.EventEmitter; private buttonLock: Button[] = []; @@ -80,7 +80,7 @@ export class InputsController { public gamepadSupport = true; public selectedDevice; - private disconnectedGamepads: Array = []; + private disconnectedGamepads: string[] = []; public lastSource = "keyboard"; private inputInterval: NodeJS.Timeout[] = []; @@ -223,7 +223,7 @@ export class InputsController { * Retrieves the identifiers of all connected gamepads, excluding any that are currently marked as disconnected. * @returns Array An array of strings representing the IDs of the connected gamepads. */ - getGamepadsName(): Array { + getGamepadsName(): string[] { return this.gamepads.filter(g => !this.disconnectedGamepads.includes(g.id)).map(g => g.id); } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index cefe8c9367a..aa9c20996a7 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -479,7 +479,7 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier override apply(doubleBattleChance: NumberHolder): boolean { // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using randSeedInt // A double battle will initiate if the generated number is 0 - doubleBattleChance.value = doubleBattleChance.value / 4; + doubleBattleChance.value /= 4; return true; } @@ -2684,7 +2684,7 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier * @returns always `true` */ override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean { - moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount(); + moveAccuracy.value += this.accuracyAmount * this.getStackCount(); return true; } diff --git a/src/overrides.ts b/src/overrides.ts index 3f61196f0b4..cceb677d450 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -159,7 +159,7 @@ class DefaultOverrides { readonly HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null; readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly GENDER_OVERRIDE: Gender | null = null; - readonly MOVESET_OVERRIDE: MoveId | Array = []; + readonly MOVESET_OVERRIDE: MoveId | MoveId[] = []; readonly SHINY_OVERRIDE: boolean | null = null; readonly VARIANT_OVERRIDE: Variant | null = null; /** @@ -186,7 +186,7 @@ class DefaultOverrides { readonly ENEMY_HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null; readonly ENEMY_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly ENEMY_GENDER_OVERRIDE: Gender | null = null; - readonly ENEMY_MOVESET_OVERRIDE: MoveId | Array = []; + readonly ENEMY_MOVESET_OVERRIDE: MoveId | MoveId[] = []; readonly ENEMY_SHINY_OVERRIDE: boolean | null = null; readonly ENEMY_VARIANT_OVERRIDE: Variant | null = null; diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 11dd2467476..b41cf298186 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -11,7 +11,7 @@ import { namespaceMap } from "./utils-plugins"; interface LoadingFontFaceProperty { face: FontFace; extraOptions?: { [key: string]: any }; - only?: Array; + only?: string[]; } //#region Constants @@ -35,7 +35,7 @@ const rangesByLanguage = { ), }; -const fonts: Array = [ +const fonts: LoadingFontFaceProperty[] = [ // unicode (special character from PokePT) { face: new FontFace("emerald", "url(./fonts/PokePT_Wansung.woff2)", { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index e1843eb5c8e..d69028be696 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -199,7 +199,7 @@ for (let i = 0; i < 5; i++) { /** * All Settings not related to controls */ -export const Setting: Array = [ +export const Setting: Setting[] = [ { key: SettingKeys.Game_Speed, label: i18next.t("settings:gameSpeed"), diff --git a/src/ui/handlers/awaitable-ui-handler.ts b/src/ui/handlers/awaitable-ui-handler.ts index e8513b4acc1..51a8165c17f 100644 --- a/src/ui/handlers/awaitable-ui-handler.ts +++ b/src/ui/handlers/awaitable-ui-handler.ts @@ -1,11 +1,14 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import type { UiMode } from "#enums/ui-mode"; +import type { AnyFn } from "#types/type-helpers"; import { UiHandler } from "#ui/ui-handler"; +// TODO: Why does this class exist? export abstract class AwaitableUiHandler extends UiHandler { protected awaitingActionInput: boolean; - protected onActionInput: Function | null; + // TODO: Add strong typing for subclasses rather than using `AnyFn` + protected onActionInput: AnyFn | null; public tutorialActive = false; public tutorialOverlay: Phaser.GameObjects.Rectangle; diff --git a/src/ui/handlers/battle-message-ui-handler.ts b/src/ui/handlers/battle-message-ui-handler.ts index f845f22a730..263240a035d 100644 --- a/src/ui/handlers/battle-message-ui-handler.ts +++ b/src/ui/handlers/battle-message-ui-handler.ts @@ -171,7 +171,7 @@ export class BattleMessageUiHandler extends MessageUiHandler { showText( text: string, delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, @@ -184,7 +184,7 @@ export class BattleMessageUiHandler extends MessageUiHandler { text: string, name?: string, delay?: number | null, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/egg-gacha-ui-handler.ts b/src/ui/handlers/egg-gacha-ui-handler.ts index fcb4cb09538..2c9c9ea2c33 100644 --- a/src/ui/handlers/egg-gacha-ui-handler.ts +++ b/src/ui/handlers/egg-gacha-ui-handler.ts @@ -660,7 +660,7 @@ export class EggGachaUiHandler extends MessageUiHandler { showText( text: string, delay?: number, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/form-modal-ui-handler.ts b/src/ui/handlers/form-modal-ui-handler.ts index 19d09c172e2..a4771721995 100644 --- a/src/ui/handlers/form-modal-ui-handler.ts +++ b/src/ui/handlers/form-modal-ui-handler.ts @@ -1,7 +1,6 @@ import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; -import type { AnyFn } from "#types/type-helpers"; import type { ModalConfig } from "#ui/modal-ui-handler"; import { ModalUiHandler } from "#ui/modal-ui-handler"; import { addTextInputObject, addTextObject, getTextColor } from "#ui/text"; @@ -18,7 +17,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { protected inputContainers: Phaser.GameObjects.Container[] = []; protected inputs: InputText[] = []; protected errorMessage: Phaser.GameObjects.Text; - protected submitAction: AnyFn | undefined; + protected submitAction: (() => void) | undefined; protected cancelAction: (() => void) | undefined; protected tween: Phaser.Tweens.Tween | undefined; protected formLabels: Phaser.GameObjects.Text[] = []; diff --git a/src/ui/handlers/game-stats-ui-handler.ts b/src/ui/handlers/game-stats-ui-handler.ts index 30243008626..802bd982813 100644 --- a/src/ui/handlers/game-stats-ui-handler.ts +++ b/src/ui/handlers/game-stats-ui-handler.ts @@ -7,7 +7,6 @@ import { PlayerGender } from "#enums/player-gender"; import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; import type { GameData } from "#system/game-data"; -import type { AnyFn } from "#types/type-helpers"; import { addTextObject } from "#ui/text"; import { UiHandler } from "#ui/ui-handler"; import { addWindow } from "#ui/ui-theme"; @@ -244,7 +243,7 @@ export class GameStatsUiHandler extends UiHandler { private gameData: GameData; /** A callback invoked when {@linkcode clear} is called */ - private exitCallback?: AnyFn | undefined; + private exitCallback?: (() => void) | undefined; /** Whether the UI is single column mode */ private get singleCol(): boolean { @@ -402,7 +401,7 @@ export class GameStatsUiHandler extends UiHandler { this.gameStatsContainer.setVisible(false); } - show([username, data, callback]: [] | [username: string, data: GameData, callback?: AnyFn]): boolean { + show([username, data, callback]: [] | [username: string, data: GameData, callback?: () => void]): boolean { super.show([]); if (username != null && data != null) { diff --git a/src/ui/handlers/login-form-ui-handler.ts b/src/ui/handlers/login-form-ui-handler.ts index 034023d5789..6c4a7cfeaa6 100644 --- a/src/ui/handlers/login-form-ui-handler.ts +++ b/src/ui/handlers/login-form-ui-handler.ts @@ -165,35 +165,36 @@ export class LoginFormUiHandler extends FormModalUiHandler { const config = args[0] as ModalConfig; this.processExternalProvider(config); const originalLoginAction = this.submitAction; - this.submitAction = _ => { - if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { - // Prevent overlapping overrides on action modification - this.submitAction = originalLoginAction; - this.sanitizeInputs(); - globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); - const onFail = error => { - globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); - globalScene.ui.playError(); - }; - if (!this.inputs[0].text) { - return onFail(i18next.t("menu:emptyUsername")); - } - - const [usernameInput, passwordInput] = this.inputs; - - pokerogueApi.account - .login({ - username: usernameInput.text, - password: passwordInput.text, - }) - .then(error => { - if (!error && originalLoginAction) { - originalLoginAction(); - } else { - onFail(error); - } - }); + this.submitAction = () => { + if (globalScene.tweens.getTweensOf(this.modalContainer).length > 0) { + return; } + // Prevent overlapping overrides on action modification + this.submitAction = originalLoginAction; + this.sanitizeInputs(); + globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); + const onFail = error => { + globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.playError(); + }; + if (!this.inputs[0].text) { + return onFail(i18next.t("menu:emptyUsername")); + } + + const [usernameInput, passwordInput] = this.inputs; + + pokerogueApi.account + .login({ + username: usernameInput.text, + password: passwordInput.text, + }) + .then(error => { + if (!error && originalLoginAction) { + originalLoginAction(); + } else { + onFail(error); + } + }); }; return true; diff --git a/src/ui/handlers/menu-ui-handler.ts b/src/ui/handlers/menu-ui-handler.ts index 62e475aec16..9f9c2a44bad 100644 --- a/src/ui/handlers/menu-ui-handler.ts +++ b/src/ui/handlers/menu-ui-handler.ts @@ -781,7 +781,7 @@ export class MenuUiHandler extends MessageUiHandler { showText( text: string, delay?: number, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/message-ui-handler.ts b/src/ui/handlers/message-ui-handler.ts index 1deaca78493..c414cbe83ab 100644 --- a/src/ui/handlers/message-ui-handler.ts +++ b/src/ui/handlers/message-ui-handler.ts @@ -37,7 +37,7 @@ export abstract class MessageUiHandler extends AwaitableUiHandler { showText( text: string, delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, @@ -49,7 +49,7 @@ export abstract class MessageUiHandler extends AwaitableUiHandler { text: string, _name?: string, delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, @@ -60,7 +60,7 @@ export abstract class MessageUiHandler extends AwaitableUiHandler { private showTextInternal( text: string, delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, @@ -219,7 +219,7 @@ export abstract class MessageUiHandler extends AwaitableUiHandler { } } - showPrompt(callback?: Function | null, callbackDelay?: number | null) { + showPrompt(callback?: (() => void) | null, callbackDelay?: number | null) { const wrappedTextLines = this.message.runWordWrap(this.message.text).split(/\n/g); const textLinesCount = wrappedTextLines.length; const lastTextLine = wrappedTextLines.at(-1) ?? ""; diff --git a/src/ui/handlers/modifier-select-ui-handler.ts b/src/ui/handlers/modifier-select-ui-handler.ts index 95bc30fb97c..59f4bc00105 100644 --- a/src/ui/handlers/modifier-select-ui-handler.ts +++ b/src/ui/handlers/modifier-select-ui-handler.ts @@ -11,6 +11,7 @@ import { UiMode } from "#enums/ui-mode"; import { HealShopCostModifier, LockModifierTiersModifier, PokemonHeldItemModifier } from "#modifiers/modifier"; import type { ModifierTypeOption } from "#modifiers/modifier-type"; import { getPlayerShopModifierTypeOptionsForWave, TmModifierType } from "#modifiers/modifier-type"; +import type { ModifierSelectCallback } from "#phases/select-modifier-phase"; import { AwaitableUiHandler } from "#ui/awaitable-ui-handler"; import { MoveInfoOverlay } from "#ui/move-info-overlay"; import { addTextObject, getModifierTierTextTint, getTextColor, getTextStyleOptions } from "#ui/text"; @@ -34,6 +35,7 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { private lockRarityButtonText: Phaser.GameObjects.Text; private moveInfoOverlay: MoveInfoOverlay; private moveInfoOverlayActive = false; + protected declare onActionInput: ModifierSelectCallback | null; private rowCursor = 0; private player: boolean; @@ -424,7 +426,8 @@ export class ModifierSelectUiHandler extends AwaitableUiHandler { const originalOnActionInput = this.onActionInput; this.awaitingActionInput = false; this.onActionInput = null; - originalOnActionInput(-1); + // TODO: What is a good fallback to pass to this? + originalOnActionInput(-1, -1); this.moveInfoOverlayActive = this.moveInfoOverlay.active; this.moveInfoOverlay.setVisible(false); this.moveInfoOverlay.active = false; // don't clear here as we might need to restore the UI in case the user cancels the action diff --git a/src/ui/handlers/party-ui-handler.ts b/src/ui/handlers/party-ui-handler.ts index 6fbb4052aeb..9e30f68e0a2 100644 --- a/src/ui/handlers/party-ui-handler.ts +++ b/src/ui/handlers/party-ui-handler.ts @@ -1289,7 +1289,7 @@ export class PartyUiHandler extends MessageUiHandler { showText( text: string, delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, diff --git a/src/ui/handlers/pokedex-page-ui-handler.ts b/src/ui/handlers/pokedex-page-ui-handler.ts index c3be9f87d21..be11a752541 100644 --- a/src/ui/handlers/pokedex-page-ui-handler.ts +++ b/src/ui/handlers/pokedex-page-ui-handler.ts @@ -1085,7 +1085,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { showText( text: string, delay?: number, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/pokedex-scan-ui-handler.ts b/src/ui/handlers/pokedex-scan-ui-handler.ts index b046a3dab3d..9c013623c61 100644 --- a/src/ui/handlers/pokedex-scan-ui-handler.ts +++ b/src/ui/handlers/pokedex-scan-ui-handler.ts @@ -165,7 +165,7 @@ export class PokedexScanUiHandler extends FormModalUiHandler { } else { this.inputs[0].text = args[1]; } - this.submitAction = _ => { + this.submitAction = () => { if (ui.getMode() === UiMode.POKEDEX_SCAN) { this.sanitizeInputs(); const outputName = this.reducedKeys.includes(this.inputs[0].text) ? this.inputs[0].text : ""; diff --git a/src/ui/handlers/pokedex-ui-handler.ts b/src/ui/handlers/pokedex-ui-handler.ts index 33f8498d37c..b0afd88cf7a 100644 --- a/src/ui/handlers/pokedex-ui-handler.ts +++ b/src/ui/handlers/pokedex-ui-handler.ts @@ -34,7 +34,6 @@ import type { GameData } from "#system/game-data"; import { SettingKeyboard } from "#system/settings-keyboard"; import type { DexEntry } from "#types/dex-data"; import type { DexAttrProps, StarterAttributes } from "#types/save-data"; -import type { AnyFn } from "#types/type-helpers"; import type { OptionSelectConfig } from "#ui/abstract-option-select-ui-handler"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#ui/dropdown"; import { FilterBar } from "#ui/filter-bar"; @@ -239,7 +238,7 @@ export class PokedexUiHandler extends MessageUiHandler { private filteredIndices: SpeciesId[]; private gameData: GameData; - private exitCallback?: AnyFn; + private exitCallback?: () => void; private blockOpenPage = false; constructor() { @@ -793,7 +792,7 @@ export class PokedexUiHandler extends MessageUiHandler { showText( text: string, delay?: number, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/registration-form-ui-handler.ts b/src/ui/handlers/registration-form-ui-handler.ts index 2c8080d534d..e5220351497 100644 --- a/src/ui/handlers/registration-form-ui-handler.ts +++ b/src/ui/handlers/registration-form-ui-handler.ts @@ -76,7 +76,7 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { const config = args[0] as ModalConfig; const originalRegistrationAction = this.submitAction; - this.submitAction = _ => { + this.submitAction = () => { if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { // Prevent overlapping overrides on action modification this.submitAction = originalRegistrationAction; diff --git a/src/ui/handlers/rename-form-ui-handler.ts b/src/ui/handlers/rename-form-ui-handler.ts index 9da5b0e8554..a7da11844ce 100644 --- a/src/ui/handlers/rename-form-ui-handler.ts +++ b/src/ui/handlers/rename-form-ui-handler.ts @@ -42,7 +42,7 @@ export class RenameFormUiHandler extends FormModalUiHandler { } else { this.inputs[0].text = args[1]; } - this.submitAction = _ => { + this.submitAction = () => { this.sanitizeInputs(); const sanitizedName = btoa(unescape(encodeURIComponent(this.inputs[0].text))); config.buttonActions[0](sanitizedName); diff --git a/src/ui/handlers/rename-run-ui-handler.ts b/src/ui/handlers/rename-run-ui-handler.ts index 43094d3cf60..b0a62cd6acc 100644 --- a/src/ui/handlers/rename-run-ui-handler.ts +++ b/src/ui/handlers/rename-run-ui-handler.ts @@ -42,7 +42,7 @@ export class RenameRunFormUiHandler extends FormModalUiHandler { }); } const config = args[0] as ModalConfig; - this.submitAction = _ => { + this.submitAction = () => { this.sanitizeInputs(); const sanitizedName = btoa(encodeURIComponent(this.inputs[0].text)); config.buttonActions[0](sanitizedName); diff --git a/src/ui/handlers/save-slot-select-ui-handler.ts b/src/ui/handlers/save-slot-select-ui-handler.ts index 90bb6b1febf..4fc80bcf1ee 100644 --- a/src/ui/handlers/save-slot-select-ui-handler.ts +++ b/src/ui/handlers/save-slot-select-ui-handler.ts @@ -343,7 +343,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler { showText( text: string, delay?: number, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index c527b40dbff..e57173cf7f0 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -1334,7 +1334,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { showText( text: string, delay?: number, - callback?: Function, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/handlers/summary-ui-handler.ts b/src/ui/handlers/summary-ui-handler.ts index d659834f208..b93cee6e1d5 100644 --- a/src/ui/handlers/summary-ui-handler.ts +++ b/src/ui/handlers/summary-ui-handler.ts @@ -126,7 +126,7 @@ export class SummaryUiHandler extends UiHandler { private playerParty: boolean; /**This is set to false when checking the summary of a freshly caught Pokemon as it is not part of a player's party yet but still needs to display its items*/ private newMove: Move | null; - private moveSelectFunction: Function | null; + private moveSelectFunction: ((cursor: number) => void) | null; private transitioning: boolean; private statusVisible: boolean; private moveEffectsVisible: boolean; @@ -134,7 +134,7 @@ export class SummaryUiHandler extends UiHandler { private moveSelect: boolean; private moveCursor: number; private selectedMoveIndex: number; - private selectCallback: Function | null; + private selectCallback: ((cursor: number) => void) | null; constructor() { super(UiMode.SUMMARY); @@ -335,18 +335,44 @@ export class SummaryUiHandler extends UiHandler { return `summary_${Page[page].toLowerCase()}`; } - show(args: any[]): boolean { + show( + args: [ + pokemon: PlayerPokemon, + uiMode?: SummaryUiMode.DEFAULT, + startPage?: Page, + selectCallback?: (cursor: number) => void, + player?: boolean, + ], + ): boolean; + show( + args: [ + pokemon: PlayerPokemon, + uiMode: SummaryUiMode.LEARN_MOVE, + move?: Move, + moveSelectCallback?: (cursor: number) => void, + player?: boolean, + ], + ): boolean; + show( + args: [ + pokemon: PlayerPokemon, + uiMode?: SummaryUiMode, + startPage?: Page | Move, + callback?: (cursor: number) => void, + player?: boolean, + ], + ): boolean { super.show(args); /* args[] information * args[0] : the Pokemon displayed in the Summary-UI * args[1] : the summaryUiMode (defaults to 0) - * args[2] : the start page (defaults to Page.PROFILE) + * args[2] : the start page (defaults to Page.PROFILE), or the move being selected * args[3] : contains the function executed when the user exits out of Summary UI * args[4] : optional boolean used to determine if the Pokemon is part of the player's party or not (defaults to true, necessary for PR #2921 to display all relevant information) */ this.pokemon = args[0] as PlayerPokemon; - this.summaryUiMode = args.length > 1 ? (args[1] as SummaryUiMode) : SummaryUiMode.DEFAULT; + this.summaryUiMode = (args[1] as SummaryUiMode) ?? SummaryUiMode.DEFAULT; this.playerParty = args[4] ?? true; globalScene.ui.bringToTop(this.summaryContainer); @@ -486,17 +512,15 @@ export class SummaryUiHandler extends UiHandler { switch (this.summaryUiMode) { case SummaryUiMode.DEFAULT: { - const page = args.length < 2 ? Page.PROFILE : (args[2] as Page); + const page = (args[2] as Page) ?? Page.PROFILE; this.hideMoveEffect(true); this.setCursor(page); - if (args.length > 3) { - this.selectCallback = args[3]; - } + this.selectCallback = args[3] ?? null; break; } case SummaryUiMode.LEARN_MOVE: this.newMove = args[2] as Move; - this.moveSelectFunction = args[3] as Function; + this.moveSelectFunction = args[3] ?? null; this.showMoveEffect(true); this.setCursor(Page.MOVES); @@ -615,7 +639,7 @@ export class SummaryUiHandler extends UiHandler { if (this.selectCallback instanceof Function) { const selectCallback = this.selectCallback; this.selectCallback = null; - selectCallback(); + selectCallback(-1); } if (!fromPartyMode) { diff --git a/src/ui/handlers/test-dialogue-ui-handler.ts b/src/ui/handlers/test-dialogue-ui-handler.ts index 0bad8f6f0a9..b692377e415 100644 --- a/src/ui/handlers/test-dialogue-ui-handler.ts +++ b/src/ui/handlers/test-dialogue-ui-handler.ts @@ -12,7 +12,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { setup() { super.setup(); - const flattenKeys = (object?: any, topKey?: string, middleKey?: string[]): Array => { + const flattenKeys = (object?: any, topKey?: string, middleKey?: string[]): any[] => { return Object.keys(object ?? {}) .map((t, i) => { const value = Object.values(object)[i]; @@ -141,7 +141,7 @@ export class TestDialogueUiHandler extends FormModalUiHandler { } else { this.inputs[0].text = args[1]; } - this.submitAction = _ => { + this.submitAction = () => { if (ui.getMode() === UiMode.TEST_DIALOGUE) { this.sanitizeInputs(); const sanitizedName = btoa(unescape(encodeURIComponent(this.inputs[0].text))); diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 17812785d1e..fdb87bc30c8 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -24,7 +24,7 @@ export interface LayoutConfig { optionValueLabels: Phaser.GameObjects.Text[][]; optionCursors: number[]; keys: string[]; - bindingSettings: Array; + bindingSettings: string[]; } /** * Abstract class for handling UI elements related to control settings. @@ -51,10 +51,10 @@ export abstract class AbstractControlSettingsUiHandler extends UiHandler { protected inputsIcons: InputsIcons; protected navigationIcons: InputsIcons; // list all the setting keys used in the selected layout (because dualshock has more buttons than xbox) - protected keys: Array; + protected keys: string[]; // Store the specific settings related to key bindings for the current gamepad configuration. - protected bindingSettings: Array; + protected bindingSettings: string[]; protected setting; protected settingBlacklisted; diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 78c34a47c06..78315176df9 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -4,7 +4,6 @@ import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { SettingType } from "#system/settings"; import { Setting, SettingKeys } from "#system/settings"; -import type { AnyFn } from "#types/type-helpers"; import type { InputsIcons } from "#ui/abstract-control-settings-ui-handler"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { NavigationManager, NavigationMenu } from "#ui/navigation-menu"; @@ -40,7 +39,7 @@ export class AbstractSettingsUiHandler extends MessageUiHandler { protected rowsToDisplay: number; protected title: string; - protected settings: Array; + protected settings: Setting[]; protected localStorageKey: string; constructor(type: SettingType, mode: UiMode | null = null) { @@ -518,7 +517,7 @@ export class AbstractSettingsUiHandler extends MessageUiHandler { override showText( text: string, delay?: number, - callback?: AnyFn, + callback?: () => void, callbackDelay?: number, prompt?: boolean, promptDelay?: number, diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 76b07d7bfa5..29a3acd983e 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -277,7 +277,7 @@ export class UI extends Phaser.GameObjects.Container { showText( text: string, delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, callbackDelay?: number | null, prompt?: boolean | null, promptDelay?: number | null, @@ -316,7 +316,7 @@ export class UI extends Phaser.GameObjects.Container { keyOrText: string, name: string | undefined, delay: number | null = 0, - callback: Function, + callback: () => void, callbackDelay?: number, promptDelay?: number, ): void { diff --git a/test/abilities/dry-skin.test.ts b/test/abilities/dry-skin.test.ts index ad88c5aa377..47f8dd8fabc 100644 --- a/test/abilities/dry-skin.test.ts +++ b/test/abilities/dry-skin.test.ts @@ -148,7 +148,7 @@ describe("Abilities - Dry Skin", () => { const enemy = game.field.getEnemyPokemon(); game.move.select(MoveId.WATER_GUN); - enemy.hp = enemy.hp - 1; + enemy.hp -= 1; await game.phaseInterceptor.to("MoveEffectPhase"); await game.move.forceMiss(); diff --git a/test/abilities/volt-absorb.test.ts b/test/abilities/volt-absorb.test.ts index 9ba76028703..bf1098fc8ce 100644 --- a/test/abilities/volt-absorb.test.ts +++ b/test/abilities/volt-absorb.test.ts @@ -65,7 +65,7 @@ describe("Abilities - Volt Absorb", () => { const enemyPokemon = game.field.getEnemyPokemon(); game.move.select(MoveId.THUNDERBOLT); - enemyPokemon.hp = enemyPokemon.hp - 1; + enemyPokemon.hp -= 1; await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("MoveEffectPhase"); @@ -86,7 +86,7 @@ describe("Abilities - Volt Absorb", () => { const enemyPokemon = game.field.getEnemyPokemon(); game.move.select(MoveId.THUNDERBOLT); - enemyPokemon.hp = enemyPokemon.hp - 1; + enemyPokemon.hp -= 1; await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.phaseInterceptor.to("BerryPhase", false); diff --git a/test/test-utils/mocks/mocks-container/mock-container.ts b/test/test-utils/mocks/mocks-container/mock-container.ts index 0c99545a109..ef173114023 100644 --- a/test/test-utils/mocks/mocks-container/mock-container.ts +++ b/test/test-utils/mocks/mocks-container/mock-container.ts @@ -2,6 +2,7 @@ import type { MockGameObject } from "#test/test-utils/mocks/mock-game-object"; import type { MockTextureManager } from "#test/test-utils/mocks/mock-texture-manager"; import { coerceArray } from "#utils/array"; +// TODO: Make this implement Phaser.GameObjects.Container export class MockContainer implements MockGameObject { protected x: number; protected y: number; diff --git a/test/test-utils/mocks/mocks-container/mock-text.ts b/test/test-utils/mocks/mocks-container/mock-text.ts index 1216597fbe0..4856935fa5a 100644 --- a/test/test-utils/mocks/mocks-container/mock-text.ts +++ b/test/test-utils/mocks/mocks-container/mock-text.ts @@ -82,7 +82,7 @@ export class MockText implements MockGameObject { showText( text: string, _delay?: number | null, - callback?: Function | null, + callback?: (() => void) | null, _callbackDelay?: number | null, _prompt?: boolean | null, _promptDelay?: number | null, @@ -98,7 +98,7 @@ export class MockText implements MockGameObject { keyOrText: string, name: string, _delay: number | null, - callback: Function, + callback: () => void, _callbackDelay?: number, _promptDelay?: number, ) { @@ -354,7 +354,7 @@ export class MockText implements MockGameObject { } // biome-ignore lint/complexity/noBannedTypes: This matches the signature of the class this mocks - on(_event: string | symbol, _fn: Function, _context?: any) {} + on(_event: string | symbol, _fn: () => void, _context?: any) {} setActive(_active: boolean): this { return this; From 84dc143f74b43a849fc376aeb2faa2d2912947d8 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:51:35 -0400 Subject: [PATCH 011/101] [Misc] Improve type inference on `PositionalTagManager#addTag` (#6676) * Improve type inference on `PositionalTagManager#addTag` - Remove unused `AddPositionalTagAttr` * Improved tests * fixed type errors * Update move.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/moves/move.ts | 48 ++++++++--------- .../positional-tags/load-positional-tag.ts | 15 ++++-- .../positional-tags/positional-tag-manager.ts | 4 +- src/data/positional-tags/positional-tag.ts | 17 +++--- test/moves/delayed-attack.test.ts | 53 +++++++++---------- .../matchers/to-have-positional-tag.ts | 9 ++-- test/types/positional-tags.test-d.ts | 25 +++++---- 7 files changed, 87 insertions(+), 84 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 8637c65966b..32800b873f1 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -3384,7 +3384,10 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr { } } -export class OverrideMoveEffectAttr extends MoveAttr { +/** + * Abstract class used for `MoveAttr`s whose effect application can override normal move effect processing. + */ +abstract class OverrideMoveEffectAttr extends MoveAttr { /** This field does not exist at runtime and must not be used. * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. */ @@ -3404,41 +3407,37 @@ export class OverrideMoveEffectAttr extends MoveAttr { } } -/** Abstract class for moves that add {@linkcode PositionalTag}s to the field. */ -abstract class AddPositionalTagAttr extends OverrideMoveEffectAttr { - protected abstract readonly tagType: PositionalTagType; - - public override getCondition(): MoveConditionFunc { - // Check the arena if another similar positional tag is active and affecting the same slot - return (_user, target, move) => globalScene.arena.positionalTagManager.canAddTag(this.tagType, target.getBattlerIndex()) - } -} - /** * Attribute to implement delayed attacks, such as {@linkcode MoveId.FUTURE_SIGHT} or {@linkcode MoveId.DOOM_DESIRE}. * Delays the attack's effect with a {@linkcode DelayedAttackTag}, * activating against the given slot after the given turn count has elapsed. */ export class DelayedAttackAttr extends OverrideMoveEffectAttr { - public chargeAnim: ChargeAnim; - private chargeText: string; - /** - * @param chargeAnim - The {@linkcode ChargeAnim | charging animation} used for the move's charging phase. - * @param chargeKey - The `i18next` locales **key** to show when the delayed attack is used. + * The {@linkcode ChargeAnim | charging animation} used for the move's charging phase. + * + * Rendered public to allow for charge animation code to function + */ + public readonly chargeAnim: ChargeAnim; + /** + * The `i18next` locales key to show when the delayed attack is queued + * (**not** when it activates)! \ * In the displayed text, `{{pokemonName}}` will be populated with the user's name. */ + private readonly chargeKey: string; + constructor(chargeAnim: ChargeAnim, chargeKey: string) { super(); this.chargeAnim = chargeAnim; - this.chargeText = chargeKey; + this.chargeKey = chargeKey; } public override apply(user: Pokemon, target: Pokemon, move: Move, args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { const useMode = args[1]; if (useMode === MoveUseMode.DELAYED_ATTACK) { // don't trigger if already queueing an indirect attack + // TODO: There should be a cleaner way of doing this... return false; } @@ -3449,14 +3448,14 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, user)); globalScene.phaseManager.queueMessage( i18next.t( - this.chargeText, + this.chargeKey, { pokemonName: getPokemonNameWithAffix(user) } ) ) user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn}) - // Queue up an attack on the given slot. - globalScene.arena.positionalTagManager.addTag({ + // Queue up an attack on the given slot + globalScene.arena.positionalTagManager.addTag({ tagType: PositionalTagType.DELAYED_ATTACK, sourceId: user.id, targetIndex: target.getBattlerIndex(), @@ -3474,11 +3473,12 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { /** * Attribute to queue a {@linkcode WishTag} to activate in 2 turns. - * The tag whill heal + * The tag will heal whichever Pokemon remains in the given slot for 50% of the user's + * maximum HP. */ export class WishAttr extends MoveEffectAttr { - public override apply(user: Pokemon, target: Pokemon, _move: Move): boolean { - globalScene.arena.positionalTagManager.addTag({ + public override apply(user: Pokemon, target: Pokemon): boolean { + globalScene.arena.positionalTagManager.addTag({ tagType: PositionalTagType.WISH, healHp: toDmgValue(user.getMaxHp() / 2), targetIndex: target.getBattlerIndex(), @@ -3489,7 +3489,7 @@ export class WishAttr extends MoveEffectAttr { } public override getCondition(): MoveConditionFunc { - // Check the arena if another wish is active and affecting the same slot + // Check the arena if another similar move is active and affecting the same slot return (_user, target) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.WISH, target.getBattlerIndex()) } } diff --git a/src/data/positional-tags/load-positional-tag.ts b/src/data/positional-tags/load-positional-tag.ts index 90c889db0e9..c70bd80e72e 100644 --- a/src/data/positional-tags/load-positional-tag.ts +++ b/src/data/positional-tags/load-positional-tag.ts @@ -14,7 +14,7 @@ import type { ObjectValues } from "#types/type-helpers"; export function loadPositionalTag({ tagType, ...args -}: serializedPosTagMap[T]): posTagInstanceMap[T]; +}: toSerializedPosTag): posTagInstanceMap[T]; /** * Load the attributes of a {@linkcode PositionalTag}. * @param tag - The {@linkcode SerializedPositionalTag} to instantiate @@ -26,7 +26,7 @@ export function loadPositionalTag(tag: SerializedPositionalTag): PositionalTag; export function loadPositionalTag({ tagType, ...rest -}: serializedPosTagMap[T]): posTagInstanceMap[T] { +}: toSerializedPosTag): posTagInstanceMap[T] { // Note: We need 2 type assertions here: // 1 because TS doesn't narrow the type of TagClass correctly based on `T`. // It converts it into `new (DelayedAttackTag | WishTag) => DelayedAttackTag & WishTag` @@ -58,12 +58,19 @@ type posTagParamMap = { [k in PositionalTagType]: ConstructorParameters[0]; }; +/** + * Generic type to convert a {@linkcode PositionalTagType} into the serialized representation of its corresponding class instance. + * + * Used in place of a mapped type to work around Typescript deficiencies in function type signatures. + */ +export type toSerializedPosTag = posTagParamMap[T] & { readonly tagType: T }; + /** * Type mapping all positional tag types to their constructors' parameters, alongside the `tagType` selector. * Equivalent to their serialized representations. */ -export type serializedPosTagMap = { - [k in PositionalTagType]: posTagParamMap[k] & { tagType: k }; +type serializedPosTagMap = { + [k in PositionalTagType]: toSerializedPosTag; }; /** Union type containing all serialized {@linkcode PositionalTag}s. */ diff --git a/src/data/positional-tags/positional-tag-manager.ts b/src/data/positional-tags/positional-tag-manager.ts index 7bf4d4995c6..5925005ac70 100644 --- a/src/data/positional-tags/positional-tag-manager.ts +++ b/src/data/positional-tags/positional-tag-manager.ts @@ -1,4 +1,4 @@ -import { loadPositionalTag } from "#data/positional-tags/load-positional-tag"; +import { loadPositionalTag, type toSerializedPosTag } from "#data/positional-tags/load-positional-tag"; import type { PositionalTag } from "#data/positional-tags/positional-tag"; import type { BattlerIndex } from "#enums/battler-index"; import type { PositionalTagType } from "#enums/positional-tag-type"; @@ -16,7 +16,7 @@ export class PositionalTagManager { * @remarks * This function does not perform any checking if the added tag is valid. */ - public addTag(tag: Parameters>[0]): void { + public addTag(tag: toSerializedPosTag): void { this.tags.push(loadPositionalTag(tag)); } diff --git a/src/data/positional-tags/positional-tag.ts b/src/data/positional-tags/positional-tag.ts index a877b45b045..44675b528bd 100644 --- a/src/data/positional-tags/positional-tag.ts +++ b/src/data/positional-tags/positional-tag.ts @@ -20,7 +20,7 @@ import i18next from "i18next"; * and should refrain from adding extra serializable fields not contained in said interface. * This ensures that all tags truly "become" their respective interfaces when converted to and from JSON. */ -export interface PositionalTagBaseArgs { +interface PositionalTagBaseArgs { /** * The number of turns remaining until this tag's activation. \ * Decremented by 1 at the end of each turn until reaching 0, at which point it will @@ -30,16 +30,16 @@ export interface PositionalTagBaseArgs { /** * The {@linkcode BattlerIndex} targeted by this effect. */ - targetIndex: BattlerIndex; + readonly targetIndex: BattlerIndex; } /** * A {@linkcode PositionalTag} is a variant of an {@linkcode ArenaTag} that targets a single *slot* of the battlefield. - * Each tag can last one or more turns, triggering various effects on removal. + * Each tag can last one or more turns, triggering various effects on removal. \ * Multiple tags of the same kind can stack with one another, provided they are affecting different targets. */ export abstract class PositionalTag implements PositionalTagBaseArgs { - /** This tag's {@linkcode PositionalTagType | type} */ + /** This tag's {@linkcode PositionalTagType | type}. */ public abstract readonly tagType: PositionalTagType; // These arguments have to be public to implement the interface, but are functionally private // outside this and the tag manager. @@ -76,9 +76,9 @@ interface DelayedAttackArgs extends PositionalTagBaseArgs { /** * The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created this effect. */ - sourceId: number; + readonly sourceId: number; /** The {@linkcode MoveId} that created this attack. */ - sourceMove: MoveId; + readonly sourceMove: MoveId; } /** @@ -88,6 +88,7 @@ interface DelayedAttackArgs extends PositionalTagBaseArgs { */ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs { public override readonly tagType = PositionalTagType.DELAYED_ATTACK; + public readonly sourceMove: MoveId; public readonly sourceId: number; @@ -135,9 +136,9 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs /** Interface containing arguments used to construct a {@linkcode WishTag}. */ interface WishArgs extends PositionalTagBaseArgs { /** The amount of {@linkcode Stat.HP | HP} to heal; set to 50% of the user's max HP during move usage. */ - healHp: number; + readonly healHp: number; /** The name of the {@linkcode Pokemon} having created the tag. */ - pokemonName: string; + readonly pokemonName: string; } /** diff --git a/test/moves/delayed-attack.test.ts b/test/moves/delayed-attack.test.ts index e31c7f28e48..3c9dd540e1e 100644 --- a/test/moves/delayed-attack.test.ts +++ b/test/moves/delayed-attack.test.ts @@ -67,17 +67,6 @@ describe("Moves - Delayed Attacks", () => { } } - /** - * Expect that future sight is active with the specified number of attacks. - * @param numAttacks - The number of delayed attacks that should be queued; default `1` - */ - function expectFutureSightActive(numAttacks = 1) { - const delayedAttacks = game.scene.arena.positionalTagManager["tags"].filter( - t => t.tagType === PositionalTagType.DELAYED_ATTACK, - ); - expect(delayedAttacks).toHaveLength(numAttacks); - } - it.each<{ name: string; move: MoveId }>([ { name: "Future Sight", move: MoveId.FUTURE_SIGHT }, { name: "Doom Desire", move: MoveId.DOOM_DESIRE }, @@ -88,7 +77,12 @@ describe("Moves - Delayed Attacks", () => { game.move.use(move); await game.toNextTurn(); - expectFutureSightActive(); + expect(game).toHavePositionalTag({ + tagType: PositionalTagType.DELAYED_ATTACK, + sourceMove: move, + targetIndex: BattlerIndex.ENEMY, + turnCount: 2, + }); game.doSwitchPokemon(1); game.forceEnemyToSwitch(); @@ -96,7 +90,7 @@ describe("Moves - Delayed Attacks", () => { await passTurns(1); - expectFutureSightActive(0); + expect(game).not.toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); const enemy = game.field.getEnemyPokemon(); expect(enemy).not.toHaveFullHp(); expect(game).toHaveShownMessage( @@ -113,14 +107,14 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.FUTURE_SIGHT); await game.toNextTurn(); - expectFutureSightActive(); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); const bronzong = game.field.getPlayerPokemon(); expect(bronzong.getLastXMoves()[0].result).toBe(MoveResult.OTHER); game.move.use(MoveId.FUTURE_SIGHT); await game.toNextTurn(); - expectFutureSightActive(); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); expect(bronzong.getLastXMoves()[0].result).toBe(MoveResult.FAIL); }); @@ -131,13 +125,13 @@ describe("Moves - Delayed Attacks", () => { game.move.forceMetronomeMove(MoveId.FUTURE_SIGHT); await game.toNextTurn(); - expectFutureSightActive(); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); const enemy = game.field.getEnemyPokemon(); expect(enemy).toHaveFullHp(); await passTurns(2); - expectFutureSightActive(0); + expect(game).not.toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); expect(enemy).not.toHaveFullHp(); }); @@ -151,7 +145,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); await game.toEndOfTurn(); - expectFutureSightActive(2); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK, 2); expect(enemy1).toHaveFullHp(); expect(enemy2).toHaveFullHp(); expect(karp.getLastXMoves()[0].result).toBe(MoveResult.OTHER); @@ -159,6 +153,7 @@ describe("Moves - Delayed Attacks", () => { await passTurns(2); + expect(game).not.toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); expect(enemy1).not.toHaveFullHp(); expect(enemy2).not.toHaveFullHp(); }); @@ -179,7 +174,7 @@ describe("Moves - Delayed Attacks", () => { await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex())); await game.toNextTurn(); - expectFutureSightActive(4); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK, 4); // Lower speed to change turn order alomomola.setStatStage(Stat.SPD, 6); @@ -191,7 +186,7 @@ describe("Moves - Delayed Attacks", () => { await passTurns(2, false); // All attacks have concluded at this point, unshifting new `MoveEffectPhase`s to the queue. - expectFutureSightActive(0); + expect(game).not.toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); const MEPs = game.scene.phaseManager["phaseQueue"].findAll("MoveEffectPhase"); expect(MEPs).toHaveLength(4); @@ -208,7 +203,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2); await game.toNextTurn(); - expectFutureSightActive(1); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK, 1); // Milotic / Feebas // Karp game.doSwitchPokemon(2); @@ -246,7 +241,7 @@ describe("Moves - Delayed Attacks", () => { await game.toNextTurn(); expect(enemy2.isFainted()).toBe(true); - expectFutureSightActive(); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); expect(game).toHavePositionalTag({ tagType: PositionalTagType.DELAYED_ATTACK, @@ -273,7 +268,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); await game.toNextTurn(); - expectFutureSightActive(1); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK, 1); game.move.use(MoveId.SPLASH); await game.killPokemon(enemy2); @@ -282,7 +277,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.SPLASH); await game.toNextTurn(); - expectFutureSightActive(0); + expect(game).not.toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); expect(enemy1).toHaveFullHp(); expect(game).not.toHaveShownMessage( i18next.t("moveTriggers:tookMoveAttack", { @@ -303,7 +298,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.FUTURE_SIGHT, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2); await game.toNextTurn(); - expectFutureSightActive(1); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK, 1); game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); await game.toNextTurn(); @@ -341,7 +336,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.DOOM_DESIRE); await game.toNextTurn(); - expectFutureSightActive(); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); await passTurns(1); @@ -371,7 +366,7 @@ describe("Moves - Delayed Attacks", () => { game.move.use(MoveId.FUTURE_SIGHT); await game.toNextTurn(); - expectFutureSightActive(); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); await passTurns(1); @@ -401,7 +396,7 @@ describe("Moves - Delayed Attacks", () => { await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT); await game.toNextTurn(); - expectFutureSightActive(1); + expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK, 1); await passTurns(1); @@ -412,7 +407,7 @@ describe("Moves - Delayed Attacks", () => { }); await game.toEndOfTurn(); - expectFutureSightActive(0); + expect(game).not.toHavePositionalTag(PositionalTagType.DELAYED_ATTACK); }); // TODO: Implement and move to a power spot's test file diff --git a/test/test-utils/matchers/to-have-positional-tag.ts b/test/test-utils/matchers/to-have-positional-tag.ts index 448339d6a8d..21c9a0c034c 100644 --- a/test/test-utils/matchers/to-have-positional-tag.ts +++ b/test/test-utils/matchers/to-have-positional-tag.ts @@ -2,7 +2,7 @@ import type { GameManager } from "#test/test-utils/game-manager"; // biome-ignore-end lint/correctness/noUnusedImports: TSDoc -import type { serializedPosTagMap } from "#data/positional-tags/load-positional-tag"; +import type { toSerializedPosTag } from "#data/positional-tags/load-positional-tag"; import type { PositionalTagType } from "#enums/positional-tag-type"; import type { OneOther } from "#test/@types/test-helpers"; import { getOnelineDiffStr } from "#test/test-utils/string-utils"; @@ -10,9 +10,10 @@ import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils" import { toTitleCase } from "#utils/strings"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; -export type toHavePositionalTagOptions

= OneOther & { - tagType: P; -}; +/** + * Options type for {@linkcode toHavePositionalTag}. + */ +export type toHavePositionalTagOptions

= OneOther, "tagType">; /** * Matcher to check if the {@linkcode Arena} has a certain number of {@linkcode PositionalTag}s active. diff --git a/test/types/positional-tags.test-d.ts b/test/types/positional-tags.test-d.ts index a75cc291764..367d8d18ab8 100644 --- a/test/types/positional-tags.test-d.ts +++ b/test/types/positional-tags.test-d.ts @@ -1,28 +1,27 @@ -import type { SerializedPositionalTag, serializedPosTagMap } from "#data/positional-tags/load-positional-tag"; +import type { SerializedPositionalTag, toSerializedPosTag } from "#data/positional-tags/load-positional-tag"; import type { DelayedAttackTag, WishTag } from "#data/positional-tags/positional-tag"; import type { PositionalTagType } from "#enums/positional-tag-type"; -import type { Mutable, NonFunctionPropertiesRecursive } from "#types/type-helpers"; +import type { NonFunctionPropertiesRecursive } from "#types/type-helpers"; import { describe, expectTypeOf, it } from "vitest"; -// Needed to get around properties being readonly in certain classes -type NonFunctionMutable = Mutable>; - -describe("serializedPositionalTagMap", () => { - it("should contain representations of each tag's serialized form", () => { - expectTypeOf().branded.toEqualTypeOf< - NonFunctionMutable +describe("toSerializedPosTag", () => { + it("should map each class' tag type to their serialized forms", () => { + expectTypeOf>().branded.toEqualTypeOf< + NonFunctionPropertiesRecursive + >(); + expectTypeOf>().branded.toEqualTypeOf< + NonFunctionPropertiesRecursive >(); - expectTypeOf().branded.toEqualTypeOf>(); }); }); describe("SerializedPositionalTag", () => { - it("should accept a union of all serialized tag forms", () => { + it("should be a union of all serialized tag forms", () => { expectTypeOf().branded.toEqualTypeOf< - NonFunctionMutable | NonFunctionMutable + NonFunctionPropertiesRecursive | NonFunctionPropertiesRecursive >(); }); - it("should accept a union of all unserialized tag forms", () => { + it("should be extended by all unserialized tag forms", () => { expectTypeOf().toExtend(); expectTypeOf().toExtend(); }); From 6ef0af8489ca478c3a2e20d81d0816ff86629a11 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 8 Nov 2025 21:08:37 -0500 Subject: [PATCH 012/101] [Docs] Fix `Pokemon#hasAbility` referencing outdated enum name https://github.com/pagefaultgames/pokerogue/pull/6766 --- src/field/pokemon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 101db9307f3..1750dcae56f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2232,7 +2232,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { /** * Check whether a pokemon has the specified ability in effect, either as a normal or passive ability. * Accounts for all the various effects which can disable or modify abilities. - * @param ability - The {@linkcode Abilities | Ability} to check for + * @param ability - The {@linkcode AbilityId | Ability} to check for * @param canApply - Whether to check if the ability is currently active; default `true` * @param ignoreOverride - Whether to ignore any overrides caused by {@linkcode MoveId.TRANSFORM | Transform}; default `false` * @returns Whether this {@linkcode Pokemon} has the given ability From ba0ae0f7dbea730af90fb9f097e1622b76f2400d Mon Sep 17 00:00:00 2001 From: damocleas Date: Mon, 10 Nov 2025 11:58:50 -0500 Subject: [PATCH 013/101] update locales, update assets --- assets | 2 +- locales | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index 9d391bd666f..5f822ab50d5 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9d391bd666f339c31db3d48a9907139950c14d1e +Subproject commit 5f822ab50d55bac4c51d9e3061f9776ccdf01043 diff --git a/locales b/locales index b5b0d94eee7..f341227e4ca 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit b5b0d94eee7cbcf0e055f8074ca1ebedb920e59e +Subproject commit f341227e4ca8488bc1b199215d3d5effa9f2e41f From bf68f591612402515027624e0038c885fbc91df1 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Fri, 14 Nov 2025 05:34:36 +0100 Subject: [PATCH 014/101] [Docs] Fix logo on docs page (#6774) fix logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fac00b9bbac..2a567fd7aa1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ SPDX-FileCopyrightText: 2024-2025 Pagefault Games SPDX-License-Identifier: CC-BY-NC-SA-4.0 --> -

PokéRogue +
PokéRogue [![Discord Static Badge](https://img.shields.io/badge/Community_Discord-blurple?style=flat&logo=discord&logoSize=auto&labelColor=white&color=5865F2)](https://discord.gg/pokerogue) [![Docs Coverage Static Badge](https://pagefaultgames.github.io/pokerogue/beta/coverage.svg)](https://pagefaultgames.github.io/pokerogue/beta) From 4e080465b9a1bea6bc8e9f1a06c471abaf981964 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 14 Nov 2025 03:27:53 -0500 Subject: [PATCH 015/101] [Bug] Fix Ball Fetch activating on enemy Pokemon (#6777) --- src/data/abilities/ability.ts | 18 +++--- test/abilities/ball-fetch.test.ts | 92 +++++++++++++++++++++++++++++++ test/test-utils/game-manager.ts | 10 +++- 3 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 test/abilities/ball-fetch.test.ts diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 21a00e53aed..0036075f04a 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2707,6 +2707,7 @@ export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { private readonly turnCount: number; private readonly side?: ArenaTagSide; private readonly quiet?: boolean; + // TODO: This should not need to track the source ID in a tempvar private sourceId: number; constructor(showAbility: boolean, tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) { @@ -2741,6 +2742,7 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { } } +// TODO: This should be merged with message func export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { //Attr doesn't force pokemon name on the message private readonly message: string; @@ -2811,13 +2813,13 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { private readonly selfTarget: boolean; private readonly intimidate: boolean; - constructor(stats: readonly BattleStat[], stages: number, selfTarget?: boolean, intimidate?: boolean) { + constructor(stats: readonly BattleStat[], stages: number, selfTarget = false, intimidate = true) { super(true); this.stats = stats; this.stages = stages; - this.selfTarget = !!selfTarget; - this.intimidate = !!intimidate; + this.selfTarget = selfTarget; + this.intimidate = intimidate; } override apply({ pokemon, simulated }: AbAttrBaseParams): void { @@ -5012,25 +5014,26 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { */ export class FetchBallAbAttr extends PostTurnAbAttr { override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean { - return !simulated && globalScene.currentBattle.lastUsedPokeball != null && !!pokemon.isPlayer; + return !simulated && globalScene.currentBattle.lastUsedPokeball != null && pokemon.isPlayer(); } /** * Adds the last used Pokeball back into the player's inventory */ override apply({ pokemon }: AbAttrBaseParams): void { - const lastUsed = globalScene.currentBattle.lastUsedPokeball; - globalScene.pokeballCounts[lastUsed!]++; + const lastUsed = globalScene.currentBattle.lastUsedPokeball!; + globalScene.pokeballCounts[lastUsed]++; globalScene.currentBattle.lastUsedPokeball = null; globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - pokeballName: getPokeballName(lastUsed!), + pokeballName: getPokeballName(lastUsed), }), ); } } +// TODO: Remove this and just replace it with applying `PostSummonChangeTerrainAbAttr` again export class PostBiomeChangeAbAttr extends AbAttr { private declare readonly _: never; } @@ -5055,6 +5058,7 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { } } +// TODO: Remove this and just replace it with applying `PostSummonChangeTerrainAbAttr` again /** @sealed */ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { private readonly terrainType: TerrainType; diff --git a/test/abilities/ball-fetch.test.ts b/test/abilities/ball-fetch.test.ts new file mode 100644 index 00000000000..e322cc1d568 --- /dev/null +++ b/test/abilities/ball-fetch.test.ts @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { getPokemonNameWithAffix } from "#app/messages"; +import { getPokeballName } from "#data/pokeball"; +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { PokeballType } from "#enums/pokeball"; +import { SpeciesId } from "#enums/species-id"; +import { GameManager } from "#test/test-utils/game-manager"; +import i18next from "i18next"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +// NB: These tests pass when done locally, but we currently have no mechanism to make catches fail +// due to battle scene RNG overrides making ball shake checks always succeed. +// +// TODO: Enable suite once `AttemptCapturePhase` is made sane +describe.todo("Ability - Ball Fetch", () => { + 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(AbilityId.BALL_FETCH) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyMoveset(MoveId.SPLASH) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should restore the user's first failed ball throw at end of turn", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const karp = game.field.getEnemyPokemon(); + + vi.spyOn(karp.species, "catchRate", "get").mockReturnValue(0); + + game.doThrowPokeball(PokeballType.POKEBALL); + await game.toEndOfTurn(false); + + expect(game.scene.pokeballCounts[PokeballType.POKEBALL]).toBe(4); + + await game.toEndOfTurn(); + + expect(feebas).toHaveAbilityApplied(AbilityId.BALL_FETCH); + expect(game.scene.pokeballCounts[PokeballType.POKEBALL]).toBe(5); + expect(game).toHaveShownMessage( + i18next.t("abilityTriggers:fetchBall", { + pokemonNameWithAffix: getPokemonNameWithAffix(feebas), + pokeballName: getPokeballName(PokeballType.POKEBALL), + }), + ); + }); + + it("should not work on enemies", async () => { + game.override.ability(AbilityId.AIR_LOCK).enemyAbility(AbilityId.BALL_FETCH); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const karp = game.field.getEnemyPokemon(); + + vi.spyOn(karp.species, "catchRate", "get").mockReturnValue(0); + + game.doThrowPokeball(PokeballType.POKEBALL); + await game.toEndOfTurn(false); + + expect(game.scene.pokeballCounts[PokeballType.POKEBALL]).toBe(4); + + await game.toEndOfTurn(); + + // did nothing; still at 4 balls + expect(karp).not.toHaveAbilityApplied(AbilityId.BALL_FETCH); + expect(game.scene.pokeballCounts[PokeballType.POKEBALL]).toBe(4); + }); +}); diff --git a/test/test-utils/game-manager.ts b/test/test-utils/game-manager.ts index aab7ebabb2e..1106c22c13f 100644 --- a/test/test-utils/game-manager.ts +++ b/test/test-utils/game-manager.ts @@ -372,9 +372,13 @@ export class GameManager { console.log("==================[New Turn]=================="); } - /** Transition to the {@linkcode TurnEndPhase | end of the current turn}. */ - async toEndOfTurn() { - await this.phaseInterceptor.to("TurnEndPhase"); + /** + * Transition to the {@linkcode TurnEndPhase | end of the current turn}. + * @param endTurn - Whether to run the `TurnEndPhase` or not; default `true` + * @returns A Promise that resolves once the current turn has ended. + */ + async toEndOfTurn(endTurn = true): Promise { + await this.phaseInterceptor.to("TurnEndPhase", endTurn); console.log("==================[End of Turn]=================="); } From 4b86cfc0ee2f980095e1cb17e8b755f3dad592ca Mon Sep 17 00:00:00 2001 From: damocleas Date: Fri, 14 Nov 2025 18:48:16 -0500 Subject: [PATCH 016/101] Update assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 5f822ab50d5..a718a52c205 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 5f822ab50d55bac4c51d9e3061f9776ccdf01043 +Subproject commit a718a52c20568802c2f03a355a2b784789849960 From 0a1cad48141ffa4adb670d1b3be711fa550ade36 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 15 Nov 2025 03:20:50 -0500 Subject: [PATCH 017/101] [Test] Fix incorrect path join logic in `test-end-log`.ts (#6756) * [Test] Fix incorrect path join logic in `test-end-log`.ts * Update test-end-log.ts * remove unused import * Apply Biome --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- test/test-utils/setup/test-end-log.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/test-utils/setup/test-end-log.ts b/test/test-utils/setup/test-end-log.ts index a230cccca11..7f92081e0e1 100644 --- a/test/test-utils/setup/test-end-log.ts +++ b/test/test-utils/setup/test-end-log.ts @@ -1,13 +1,13 @@ /** * Code to add markers to the beginning and end of tests. * Intended for use with {@linkcode CustomDefaultReporter}, and placed inside test hooks - * (rather than as part of the reporter) to ensure Vitest waits for the log messages to be printed. + * (rather than as part of the reporter) to ensure Vitest waits for the log messages to be printed before beginning subsequent cases. * @module */ // biome-ignore lint/correctness/noUnusedImports: TSDoc import type CustomDefaultReporter from "#test/test-utils/reporters/custom-default-reporter"; -import { basename, join, relative } from "path"; +import { join, relative } from "path"; import chalk from "chalk"; import type { RunnerTask, RunnerTaskResult, RunnerTestCase } from "vitest"; @@ -18,7 +18,12 @@ const TEST_END_BARRIER = chalk.bold.hex("#ff7c7cff")("=================="); const TEST_NAME_COLOR = "#008886ff" as const; const VITEST_PINK_COLOR = "#c162de" as const; -const testRoot = join(import.meta.dirname, "..", "..", ".."); +/** + * The root directory of the project, used when constructing relative paths. + * @privateRemarks + * Will have to be altered if this file is moved! + */ +const rootDir = join(import.meta.dirname, "..", "..", ".."); /** * Log the testfile name and path upon a case starting. \ @@ -46,17 +51,18 @@ export function logTestEnd(task: RunnerTestCase): void { Name: ${chalk.hex(TEST_NAME_COLOR)(getTestName(task))} Result: ${resultStr}${durationStr} File: ${chalk.hex("#d29b0eff")( - getPathFromTest(task.file.filepath) + (task.location ? `:${task.location.line}:${task.location.column}` : ""), + // Formatting used to allow for IDE Ctrl+click shortcuts + getRelativePath(task.file.filepath) + (task.location ? `:${task.location.line}:${task.location.column}` : ""), )}`); } /** - * Get the path of the current test file relative to the `test` directory. + * Get the path of the current test file relative to the project root. * @param abs - The absolute path to the file - * @returns The relative path with `test/` appended to it. + * @returns The path relative to the project root folder. */ -function getPathFromTest(abs: string): string { - return join(basename(testRoot), relative(testRoot, abs)); +function getRelativePath(abs: string): string { + return relative(rootDir, abs); } function getResultStr(result: RunnerTaskResult | undefined): string { From 349b552eb15ba92922d9fcca5c4cd1f5b4c54f50 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 15 Nov 2025 06:29:51 -0500 Subject: [PATCH 018/101] [Docs] Update documentation on `Ability` class getters (#6635) * [Docs] Update documentation on `Ability` class getters * Fix inverted conditions on partial/unimplemented * Update ability.ts * Fix leading whitespace in tsdoc * Update ability.ts * Update src/data/abilities/ability.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/abilities/ability.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/abilities/ability.ts | 82 +++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 0036075f04a..f29e5eada8c 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -71,32 +71,40 @@ import { inSpeedOrder } from "#utils/speed-order-generator"; import { toCamelCase } from "#utils/strings"; import i18next from "i18next"; +//#region Bit sets /** Bit set for an ability's `bypass faint` flag */ -const AB_FLAG_BYPASS_FAINT = 1; +const AB_FLAG_BYPASS_FAINT = 1 << 0; /** Bit set for an ability's `ignorable` flag */ -const AB_FLAG_IGNORABLE = 2; +const AB_FLAG_IGNORABLE = 1 << 1; /** Bit set for an ability's `suppressable` flag */ -const AB_FLAG_UNSUPPRESSABLE = 4; +const AB_FLAG_UNSUPPRESSABLE = 1 << 2; /** Bit set for an ability's `uncopiable` flag */ -const AB_FLAG_UNCOPIABLE = 8; +const AB_FLAG_UNCOPIABLE = 1 << 3; /** Bit set for an ability's `unreplaceable` flag */ -const AB_FLAG_UNREPLACEABLE = 16; +const AB_FLAG_UNREPLACEABLE = 1 << 4; /** Bit set for an ability's `unimplemented` flag */ -const AB_FLAG_UNIMPLEMENTED = 32; +const AB_FLAG_UNIMPLEMENTED = 1 << 5; /** Bit set for an ability's `partial` flag */ -const AB_FLAG_PARTIAL = 64; - -/** Bits set for a swappable ability */ +const AB_FLAG_PARTIAL = 1 << 6; +/** Bit set for an unswappable ability */ const AB_FLAG_UNSWAPPABLE = AB_FLAG_UNCOPIABLE | AB_FLAG_UNREPLACEABLE; +//#endregion Bit sets + +/** + * An Ability is a class representing the various Abilities Pokemon may have. \ + * Each has one or more {@linkcode AbAttr | attributes} that can apply independently + * of one another. + */ export class Ability { /** The ability's unique identifier */ public readonly id: AbilityId; - /** Key used to localize the ability's name */ + /** The locales key for the ability's name. */ private readonly i18nKey: string; - /** The localized ability name + /** + * The localized ability name. * @remarks - * Includes The (P) or (N) suffix, if the ability is partial/unimplemented + * Includes the `"(P)"` or `"(N)"` suffix if the ability is partial/unimplemented */ public get name(): string { if (this.id === AbilityId.NONE) { @@ -124,35 +132,65 @@ export class Ability { } return i18next.t(`ability:${this.i18nKey}.description`); } - /** Whether the retains its effects through a faint */ + + /** + * Whether this ability can activate even if the user faints. + * @remarks + * If `true`, the ability will also activate when revived via Reviver Seed. + */ public get bypassFaint(): boolean { return (this.flags & AB_FLAG_BYPASS_FAINT) !== 0; } - /** Whether the ability is ignorable by mold breaker like effects */ + /** + * Whether this ability can be ignored by effects like + * {@linkcode MoveId.SUNSTEEL_STRIKE | Sunsteel Strike} or {@linkcode AbilityId.MOLD_BREAKER | Mold Breaker}. + */ public get ignorable(): boolean { return (this.flags & AB_FLAG_IGNORABLE) !== 0; } - /** Whether the ability can be suppressed by gastro acid and neutralizing gas */ + /** + * Whether this ability can be suppressed by effects like + * {@linkcode MoveId.GASTRO_ACID | Gastro Acid} or {@linkcode AbilityId.NEUTRALIZING_GAS | Neutralizing Gas}. + */ public get suppressable(): boolean { return !(this.flags & AB_FLAG_UNSUPPRESSABLE); } - /** Whether the ability can be copied, such as via trace */ + /** + * Whether this ability can be copied by effects like + * {@linkcode MoveId.ROLE_PLAY | Role Play} or {@linkcode AbilityId.TRACE | Trace}. + */ public get copiable(): boolean { return !(this.flags & AB_FLAG_UNCOPIABLE); } - /** Whether the ability can be replaced, such as via entrainment */ + /** + * Whether this ability can be replaced by effects like + * {@linkcode MoveId.SIMPLE_BEAM | Simple Beam} or {@linkcode MoveId.ENTRAINMENT | Entrainment}. + */ public get replaceable(): boolean { return !(this.flags & AB_FLAG_UNREPLACEABLE); } - /** Whether the ability is partially implemented. Mutually exclusive with {@linkcode unimplemented} */ + /** + * Whether this ability is partially implemented. + * @remarks + * Mutually exclusive with {@linkcode unimplemented} + */ public get partial(): boolean { return (this.flags & AB_FLAG_PARTIAL) !== 0; } - /** Whether the ability is unimplemented. Mutually exclusive with {@linkcode partial} */ + /** + * Whether this ability is unimplemented. + * @remarks + * Mutually exclusive with {@linkcode partial} + */ public get unimplemented(): boolean { return (this.flags & AB_FLAG_UNIMPLEMENTED) !== 0; } - /** Whether this ability can be swapped via moves like skill swap */ + /** + * Whether this ability can be swapped via effects like {@linkcode MoveId.SKILL_SWAP | Skill Swap}. + * @remarks + * Logically equivalent to `this.copiable && this.replaceable`, albeit slightly faster + * due to using a pre-computed bitmask. + */ public get swappable(): boolean { return !(this.flags & AB_FLAG_UNSWAPPABLE); } @@ -238,8 +276,8 @@ class AbBuilder { * @param args - The arguments needed to instantiate the given class. * @returns `this` */ - attr>(AttrType: T, ...args: ConstructorParameters): this { - const attr = new AttrType(...args); + attr>(attrType: T, ...args: ConstructorParameters): this { + const attr = new attrType(...args); this.attrs.push(attr); return this; From bb86b649d8cd284ecc8767d183d157adefd67ccc Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Sat, 15 Nov 2025 19:14:48 +0100 Subject: [PATCH 019/101] [i18n] Fix message for unimplemented moves (#6780) Fix message for unimplemented moves --- src/data/moves/pokemon-move.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/moves/pokemon-move.ts b/src/data/moves/pokemon-move.ts index bccb842945e..1d8f4d8e823 100644 --- a/src/data/moves/pokemon-move.ts +++ b/src/data/moves/pokemon-move.ts @@ -55,7 +55,7 @@ export class PokemonMove { // TODO: Add Sky Drop's 1 turn stall if (this.moveId === MoveId.NONE || move.name.endsWith(" (N)")) { - return [false, i18next.t("battle:moveNotImplemented", moveName.replace(" (N)", ""))]; + return [false, i18next.t("battle:moveNotImplemented", { moveName: moveName.replace(" (N)", "") })]; } if (!ignorePp && move.pp !== -1 && this.ppUsed >= this.getMovePp()) { From 3c7242d747b56416d6353cb67e303c76363db0c9 Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:56:51 +0100 Subject: [PATCH 020/101] [Bug][Balance] Actual correct minimum waves for Legends in Daily Mode (#6786) Added true for ignoreCurveChanges Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> --- src/field/arena.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/field/arena.ts b/src/field/arena.ts index b4d993314c4..dc209226211 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -184,7 +184,7 @@ export class Arena { ret = getPokemonSpecies(species!); if (ret.subLegendary || ret.legendary || ret.mythical) { - const waveDifficulty = globalScene.gameMode.getWaveForDifficulty(waveIndex); + const waveDifficulty = globalScene.gameMode.getWaveForDifficulty(waveIndex, true); if (ret.baseTotal >= 660) { regen = waveDifficulty < 80; // Wave 50+ in daily (however, max Daily wave is 50 currently so not possible) } else { From 8381fa16fd6a26045c12581019d84a7499304f72 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:35:32 +0100 Subject: [PATCH 021/101] [UI/UX] Fix confirm message overlap on run history import https://github.com/pagefaultgames/pokerogue/pull/6794 --- src/ui/handlers/menu-ui-handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/handlers/menu-ui-handler.ts b/src/ui/handlers/menu-ui-handler.ts index 9f9c2a44bad..71224e1bcc5 100644 --- a/src/ui/handlers/menu-ui-handler.ts +++ b/src/ui/handlers/menu-ui-handler.ts @@ -278,6 +278,7 @@ export class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:importRunHistory"), handler: () => { + ui.revertMode(); globalScene.gameData.importData(GameDataType.RUN_HISTORY); return true; }, From d0ddcaa7a3a9dd262577abf60e40dcc8834faf7b Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:38:02 -0600 Subject: [PATCH 022/101] [Dev] Enable linting of `move.ts` and update Biome to 2.3.2 https://github.com/pagefaultgames/pokerogue/pull/6688 * Update Biome to 2.2.7 and enable linting of `move.ts` * Apply safe fixes * Apply unsafe fixes * Apply `noUnusedFunctionParameters` * Update submodules to correct commit * Apply Biome after merge * Update Biome to 2.3.2 * Fix formatting of `initMoves` * Fix a bit more formatting * Apply Biome --- biome.jsonc | 8 +- package.json | 2 +- pnpm-lock.yaml | 74 +- src/data/moves/move.ts | 4103 ++++++++++++++++++++++++---------------- 4 files changed, 2480 insertions(+), 1707 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 277edf89366..27ce10b8629 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -5,7 +5,7 @@ */ { - "$schema": "https://biomejs.dev/schemas/2.2.5/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -35,8 +35,6 @@ // TODO: lint css and html? "!**/*.css", "!**/*.html", - // TODO: enable linting this file - "!**/src/data/moves/move.ts", // this file is too big "!**/src/data/balance/tm-species-map.ts" ] @@ -204,7 +202,8 @@ "noDocumentCookie": "off", // Firefox has minimal support for the "Cookie Store API" "noConstantBinaryExpressions": "error", "noTsIgnore": "error", - "useIterableCallbackReturn": "warn" // TODO: Refactor and change to error + "useIterableCallbackReturn": "warn", // TODO: Refactor and change to error + "noNonNullAssertedOptionalChain": "warn" // TODO: Refactor and change to error }, "complexity": { "useWhile": "error", @@ -233,7 +232,6 @@ "options": { "max": 7 } }, "noShadow": "warn", // TODO: refactor and make "error" - "noNonNullAssertedOptionalChain": "warn", // TODO: refactor and make "error" "noDuplicateDependencies": "error", "noImportCycles": "error", // TODO: Change to error once promises are used properly diff --git a/package.json b/package.json index 59d0a5f877a..9550c0a9fc7 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "update-submodules:remote": "pnpm update-locales:remote && pnpm update-assets:remote" }, "devDependencies": { - "@biomejs/biome": "2.2.5", + "@biomejs/biome": "2.3.2", "@ls-lint/ls-lint": "2.3.1", "@types/crypto-js": "^4.2.0", "@types/jsdom": "^27.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 133ee8b85b7..c93f37b5f0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,8 +46,8 @@ importers: version: 1.80.16(graphology-types@0.24.8) devDependencies: '@biomejs/biome': - specifier: 2.2.5 - version: 2.2.5 + specifier: 2.3.2 + version: 2.3.2 '@ls-lint/ls-lint': specifier: 2.3.1 version: 2.3.1 @@ -200,55 +200,55 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.2.5': - resolution: {integrity: sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw==} + '@biomejs/biome@2.3.2': + resolution: {integrity: sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.2.5': - resolution: {integrity: sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ==} + '@biomejs/cli-darwin-arm64@2.3.2': + resolution: {integrity: sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.2.5': - resolution: {integrity: sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg==} + '@biomejs/cli-darwin-x64@2.3.2': + resolution: {integrity: sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.2.5': - resolution: {integrity: sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw==} + '@biomejs/cli-linux-arm64-musl@2.3.2': + resolution: {integrity: sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.2.5': - resolution: {integrity: sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ==} + '@biomejs/cli-linux-arm64@2.3.2': + resolution: {integrity: sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.2.5': - resolution: {integrity: sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ==} + '@biomejs/cli-linux-x64-musl@2.3.2': + resolution: {integrity: sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.2.5': - resolution: {integrity: sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew==} + '@biomejs/cli-linux-x64@2.3.2': + resolution: {integrity: sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.2.5': - resolution: {integrity: sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg==} + '@biomejs/cli-win32-arm64@2.3.2': + resolution: {integrity: sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.2.5': - resolution: {integrity: sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw==} + '@biomejs/cli-win32-x64@2.3.2': + resolution: {integrity: sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -2175,39 +2175,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@biomejs/biome@2.2.5': + '@biomejs/biome@2.3.2': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.2.5 - '@biomejs/cli-darwin-x64': 2.2.5 - '@biomejs/cli-linux-arm64': 2.2.5 - '@biomejs/cli-linux-arm64-musl': 2.2.5 - '@biomejs/cli-linux-x64': 2.2.5 - '@biomejs/cli-linux-x64-musl': 2.2.5 - '@biomejs/cli-win32-arm64': 2.2.5 - '@biomejs/cli-win32-x64': 2.2.5 + '@biomejs/cli-darwin-arm64': 2.3.2 + '@biomejs/cli-darwin-x64': 2.3.2 + '@biomejs/cli-linux-arm64': 2.3.2 + '@biomejs/cli-linux-arm64-musl': 2.3.2 + '@biomejs/cli-linux-x64': 2.3.2 + '@biomejs/cli-linux-x64-musl': 2.3.2 + '@biomejs/cli-win32-arm64': 2.3.2 + '@biomejs/cli-win32-x64': 2.3.2 - '@biomejs/cli-darwin-arm64@2.2.5': + '@biomejs/cli-darwin-arm64@2.3.2': optional: true - '@biomejs/cli-darwin-x64@2.2.5': + '@biomejs/cli-darwin-x64@2.3.2': optional: true - '@biomejs/cli-linux-arm64-musl@2.2.5': + '@biomejs/cli-linux-arm64-musl@2.3.2': optional: true - '@biomejs/cli-linux-arm64@2.2.5': + '@biomejs/cli-linux-arm64@2.3.2': optional: true - '@biomejs/cli-linux-x64-musl@2.2.5': + '@biomejs/cli-linux-x64-musl@2.3.2': optional: true - '@biomejs/cli-linux-x64@2.2.5': + '@biomejs/cli-linux-x64@2.3.2': optional: true - '@biomejs/cli-win32-arm64@2.2.5': + '@biomejs/cli-win32-arm64@2.3.2': optional: true - '@biomejs/cli-win32-x64@2.2.5': + '@biomejs/cli-win32-x64@2.3.2': optional: true '@bundled-es-modules/cookie@2.0.1': diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index cbdfe780382..8b5e80792bd 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1,8 +1,5 @@ -import type { BattlerTag } from "#data/battler-tags"; -import { AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "#abilities/ability"; -import { - applyAbAttrs -} from "#abilities/apply-ab-attrs"; +import type { AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "#abilities/ability"; +import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { loggedInUser } from "#app/account"; import type { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; @@ -19,19 +16,14 @@ import { ShellTrapTag, StockpilingTag, SubstituteTag, - SupremeOverlordTag, + type SupremeOverlordTag, TrappedTag, TypeBoostTag, } from "#data/battler-tags"; import { getBerryEffectFunc } from "#data/berry"; import { allAbilities, allMoves } from "#data/data-lists"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers"; -import { DelayedAttackTag } from "#data/positional-tags/positional-tag"; -import { - getNonVolatileStatusEffects, - getStatusEffectHealText, - isNonVolatileStatusEffect, -} from "#data/status-effect"; +import { getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect } from "#data/status-effect"; import { TerrainType } from "#data/terrain"; import { getTypeDamageMultiplier } from "#data/type"; import { AbilityId } from "#enums/ability-id"; @@ -47,10 +39,12 @@ import { FieldPosition } from "#enums/field-position"; import { HitResult } from "#enums/hit-result"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { ChargeAnim } from "#enums/move-anims-common"; -import { MoveCategory, MoveDamageCategory } from "#enums/move-category"; +import { MoveCategory, type MoveDamageCategory } from "#enums/move-category"; import { MoveEffectTrigger } from "#enums/move-effect-trigger"; import { MoveFlags } from "#enums/move-flags"; import { MoveId } from "#enums/move-id"; +import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; +import { MovePriorityInBracket } from "#enums/move-priority-in-bracket"; import { MoveResult } from "#enums/move-result"; import { MoveTarget } from "#enums/move-target"; import { isVirtual, MoveUseMode } from "#enums/move-use-mode"; @@ -58,13 +52,7 @@ import { MultiHitType } from "#enums/multi-hit-type"; import { MAX_POKEMON_TYPE, PokemonType } from "#enums/pokemon-type"; import { PositionalTagType } from "#enums/positional-tag-type"; import { SpeciesId } from "#enums/species-id"; -import { - BATTLE_STATS, - type BattleStat, - type EffectiveStat, - getStatKey, - Stat, -} from "#enums/stat"; +import { BATTLE_STATS, type BattleStat, type EffectiveStat, getStatKey, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { SwitchType } from "#enums/switch-type"; import { WeatherType } from "#enums/weather-type"; @@ -79,41 +67,66 @@ import { PreserveBerryModifier, } from "#modifiers/modifier"; import { applyMoveAttrs } from "#moves/apply-attrs"; -import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves"; -import { consecutiveUseRestriction, counterAttackConditionBoth, counterAttackConditionPhysical, counterAttackConditionSpecial, failAgainstFinalBossCondition, FailIfInsufficientHpCondition, failIfTargetNotAttackingCondition, failTeleportCondition, FirstMoveCondition, gravityUseRestriction, lastResortCondition, MoveCondition, MoveRestriction, upperHandCondition } from "#moves/move-condition"; +import { + invalidAssistMoves, + invalidCopycatMoves, + invalidMetronomeMoves, + invalidMirrorMoveMoves, + invalidSketchMoves, + invalidSleepTalkMoves, +} from "#moves/invalid-moves"; +import { + consecutiveUseRestriction, + counterAttackConditionBoth, + counterAttackConditionPhysical, + counterAttackConditionSpecial, + FailIfInsufficientHpCondition, + FirstMoveCondition, + failAgainstFinalBossCondition, + failIfTargetNotAttackingCondition, + failTeleportCondition, + gravityUseRestriction, + lastResortCondition, + MoveCondition, + MoveRestriction, + upperHandCondition, +} from "#moves/move-condition"; import { frenzyMissFunc, getCounterAttackTarget, getMoveTargets } from "#moves/move-utils"; import { PokemonMove } from "#moves/pokemon-move"; -import { MovePhase } from "#phases/move-phase"; -import { PokemonHealPhase } from "#phases/pokemon-heal-phase"; +import type { MovePhase } from "#phases/move-phase"; +import type { Constructor } from "#types/common"; import type { Localizable } from "#types/locales"; -import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveClassMap, MoveKindString, MoveMessageFunc } from "#types/move-types"; +import type { + ChargingMove, + MoveAttrMap, + MoveAttrString, + MoveClassMap, + MoveKindString, + MoveMessageFunc, +} from "#types/move-types"; import type { TurnMove } from "#types/turn-move"; import type { AbstractConstructor } from "#types/type-helpers"; +import { coerceArray } from "#utils/array"; import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, NumberHolder, randSeedFloat, randSeedInt, randSeedItem, toDmgValue } from "#utils/common"; -import type { Constructor } from "#types/common"; -import { coerceArray } from "#utils/array"; import { getEnumValues } from "#utils/enums"; -import { areAllies } from "#utils/pokemon-utils"; +import { areAllies, canSpeciesTera, willTerastallize } from "#utils/pokemon-utils"; +import { inSpeedOrder } from "#utils/speed-order-generator"; import { toCamelCase, toTitleCase } from "#utils/strings"; import i18next from "i18next"; -import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier"; -import { inSpeedOrder } from "#utils/speed-order-generator"; -import { canSpeciesTera, willTerastallize } from "#utils/pokemon-utils"; -import { MovePriorityInBracket } from "#enums/move-priority-in-bracket"; /** * A function used to conditionally determine execution of a given {@linkcode MoveAttr}. * Conventionally returns `true` for success and `false` for failure. -*/ + */ export type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; export abstract class Move implements Localizable { public id: MoveId; public name: string; - private _type: PokemonType; - private _category: MoveCategory; + private readonly _type: PokemonType; + private readonly _category: MoveCategory; public moveTarget: MoveTarget; public power: number; public accuracy: number; @@ -133,11 +146,11 @@ export abstract class Move implements Localizable { * * Different from {@linkcode restrictions}, which are checked when the move is selected */ - private conditions: MoveCondition[] = []; + private readonly conditions: MoveCondition[] = []; /** * Move failure conditions that occur during the second sequence (after move message but before the move is recorded as the last move used) */ - private conditionsSeq2: MoveCondition[] = []; + private readonly conditionsSeq2: MoveCondition[] = []; /** * Move failure conditions that occur during the third sequence (after accuracy and before move effects). * @@ -174,29 +187,40 @@ export abstract class Move implements Localizable { * * @see {@link https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-54#post-8548957} */ - private conditionsSeq3: MoveCondition[] = []; + private readonly conditionsSeq3: MoveCondition[] = []; /** * Conditions that must be false for a move to be able to be selected. * * @remarks Different from {@linkcode conditions}, which is checked when the move is invoked */ - private restrictions: MoveRestriction[] = []; + private readonly restrictions: MoveRestriction[] = []; /** The move's {@linkcode MoveFlags} */ - private flags: number = 0; - private nameAppend: string = ""; + private flags = 0; + private nameAppend = ""; /** * Check if the move is of the given subclass without requiring `instanceof`. - * - * ! Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those, - * use {@linkcode isChargingMove} instead. + * @remarks + * Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. + * For those, use {@linkcode isChargingMove} instead. * * @param moveKind - The string name of the move to check against * @returns Whether this move is of the provided type. */ public abstract is(moveKind: K): this is MoveClassMap[K]; - constructor(id: MoveId, type: PokemonType, category: MoveCategory, defaultMoveTarget: MoveTarget, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { + constructor( + id: MoveId, + type: PokemonType, + category: MoveCategory, + defaultMoveTarget: MoveTarget, + power: number, + accuracy: number, + pp: number, + chance: number, + priority: number, + generation: number, + ) { this.id = id; this._type = type; this._category = category; @@ -226,11 +250,11 @@ export abstract class Move implements Localizable { } localize(): void { - const i18nKey = toCamelCase(MoveId[this.id]) + const i18nKey = toCamelCase(MoveId[this.id]); if (this.id === MoveId.NONE) { this.name = ""; - this.effect = "" + this.effect = ""; return; } @@ -243,7 +267,7 @@ export abstract class Move implements Localizable { * @param attrType - The name of a {@linkcode MoveAttr} to search for * @returns An array containing all attributes matching `attrType`, or an empty array if none match. */ - getAttrs(attrType: T): (MoveAttrMap[T])[] { + getAttrs(attrType: T): MoveAttrMap[T][] { const targetAttr = MoveAttrs[attrType]; if (!targetAttr) { return []; @@ -262,7 +286,7 @@ export abstract class Move implements Localizable { if (!targetAttr) { return false; } - return this.attrs.some((attr) => attr instanceof targetAttr); + return this.attrs.some(attr => attr instanceof targetAttr); } /** @@ -416,18 +440,18 @@ export abstract class Move implements Localizable { * @returns Whether this Move will hit the target's Substitute (assuming one exists). */ hitsSubstitute(user: Pokemon, target?: Pokemon): boolean { - if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget) - || !target?.getTag(BattlerTagType.SUBSTITUTE)) { + if ( + [MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.moveTarget) + || !target?.getTag(BattlerTagType.SUBSTITUTE) + ) { return false; } const bypassed = new BooleanHolder(false); // TODO: Allow this to be simulated - applyAbAttrs("InfiltratorAbAttr", {pokemon: user, bypassed}); + applyAbAttrs("InfiltratorAbAttr", { pokemon: user, bypassed }); - return !bypassed.value - && !this.hasFlag(MoveFlags.SOUND_BASED) - && !this.hasFlag(MoveFlags.IGNORE_SUBSTITUTE); + return !bypassed.value && !this.hasFlag(MoveFlags.SOUND_BASED) && !this.hasFlag(MoveFlags.IGNORE_SUBSTITUTE); } /** @@ -467,7 +491,12 @@ export abstract class Move implements Localizable { * @param conditionSeq - The sequence number where the failure check occurs; default `4` * @returns `this` for method chaining */ - public restriction(restriction: UserMoveConditionFunc, i18nkey: string, alsoCondition?: boolean, conditionSeq?: number): this; + public restriction( + restriction: UserMoveConditionFunc, + i18nkey: string, + alsoCondition?: boolean, + conditionSeq?: number, + ): this; /** * Adds a restriction condition to this move. * The move will not be selectable if at least 1 of its restrictions is met. @@ -499,7 +528,7 @@ export abstract class Move implements Localizable { break; default: conditionArray = this.conditions; - } + } conditionArray.push(new MoveCondition((user, _, move) => !restriction(user, move))); } @@ -563,7 +592,7 @@ export abstract class Move implements Localizable { * @param setFlag - Whether the move should make contact; default `true` * @returns `this` */ - makesContact(setFlag: boolean = true): this { + makesContact(setFlag = true): this { this.setFlag(MoveFlags.MAKES_CONTACT, setFlag); return this; } @@ -786,7 +815,12 @@ export abstract class Move implements Localizable { * @returns boolean * @see {@linkcode hasFlag} */ - doesFlagEffectApply({ flag, user, target, isFollowUp = false }: { + doesFlagEffectApply({ + flag, + user, + target, + isFollowUp = false, + }: { flag: MoveFlags; user: Pokemon; target?: Pokemon; @@ -802,7 +836,7 @@ export abstract class Move implements Localizable { case MoveFlags.IGNORE_ABILITIES: if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) { const abilityEffectsIgnored = new BooleanHolder(false); - applyAbAttrs("MoveAbilityBypassAbAttr", {pokemon: user, cancelled: abilityEffectsIgnored, move: this}); + applyAbAttrs("MoveAbilityBypassAbAttr", { pokemon: user, cancelled: abilityEffectsIgnored, move: this }); if (abilityEffectsIgnored.value) { return true; } @@ -811,18 +845,22 @@ export abstract class Move implements Localizable { } return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp; case MoveFlags.IGNORE_PROTECT: - if (user.hasAbilityWithAttr("IgnoreProtectOnContactAbAttr") - && this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) { + if ( + user.hasAbilityWithAttr("IgnoreProtectOnContactAbAttr") + && this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user }) + ) { return true; } break; case MoveFlags.REFLECTABLE: // If the target is not semi-invulnerable and either has magic coat active or an unignored magic bounce ability if ( - target?.getTag(SemiInvulnerableTag) || - !(target?.getTag(BattlerTagType.MAGIC_COAT) || - (!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && - target?.hasAbilityWithAttr("ReflectStatusMoveAbAttr"))) + target?.getTag(SemiInvulnerableTag) + || !( + target?.getTag(BattlerTagType.MAGIC_COAT) + || (!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) + && target?.hasAbilityWithAttr("ReflectStatusMoveAbAttr")) + ) ) { return false; } @@ -840,7 +878,7 @@ export abstract class Move implements Localizable { * @param sequence - The sequence number where the condition check occurs, or `-1` to check all; defaults to 4. Pass -1 to check all * @returns boolean: false if any of the apply()'s return false, else true */ - applyConditions(user: Pokemon, target: Pokemon, sequence: -1 | 2 | 3 | 4 = 4): boolean { + applyConditions(user: Pokemon, target: Pokemon, sequence: -1 | 2 | 3 | 4 = 4): boolean { let conditionsArray: MoveCondition[]; switch (sequence) { case -1: @@ -859,7 +897,6 @@ export abstract class Move implements Localizable { return conditionsArray.every(cond => cond.apply(user, target, this)); } - /** * Determine whether the move is restricted from being selected due to its own requirements. * @@ -933,7 +970,9 @@ export abstract class Move implements Localizable { for (const attr of this.attrs) { // conditionals to check if the move is self targeting (if so then you are applying the move to yourself, not the target) - score += attr.getTargetBenefitScore(user, !attr.selfTarget ? target : user, move) * (target !== user && attr.selfTarget ? -1 : 1); + score += + attr.getTargetBenefitScore(user, attr.selfTarget ? user : target, move) + * (target !== user && attr.selfTarget ? -1 : 1); } return score; @@ -946,11 +985,17 @@ export abstract class Move implements Localizable { * @param target {@linkcode Pokemon} The Pokémon being targeted by the move. * @returns The calculated accuracy of the move. */ - calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) { + calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated = false) { const moveAccuracy = new NumberHolder(this.accuracy); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy); - applyAbAttrs("WonderSkinAbAttr", {pokemon: target, opponent: user, move: this, simulated, accuracy: moveAccuracy}); + applyAbAttrs("WonderSkinAbAttr", { + pokemon: target, + opponent: user, + move: this, + simulated, + accuracy: moveAccuracy, + }); if (moveAccuracy.value === -1) { return moveAccuracy.value; @@ -984,7 +1029,7 @@ export abstract class Move implements Localizable { * @param target {@linkcode Pokemon} The Pokémon being targeted by the move. * @returns The calculated power of the move. */ - calculateBattlePower(source: Pokemon, target: Pokemon, simulated: boolean = false): number { + calculateBattlePower(source: Pokemon, target: Pokemon, simulated = false): number { if (this.category === MoveCategory.STATUS) { return -1; } @@ -996,7 +1041,14 @@ export abstract class Move implements Localizable { const typeChangeMovePowerMultiplier = new NumberHolder(1); const typeChangeHolder = new NumberHolder(this.type); - applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier}); + applyAbAttrs("MoveTypeChangeAbAttr", { + pokemon: source, + opponent: target, + move: this, + simulated: true, + moveType: typeChangeHolder, + power: typeChangeMovePowerMultiplier, + }); const abAttrParams: PreAttackModifyPowerAbAttrParams = { pokemon: source, @@ -1004,46 +1056,54 @@ export abstract class Move implements Localizable { simulated, power, move: this, - } + }; applyAbAttrs("VariableMovePowerAbAttr", abAttrParams); const ally = source.getAlly(); if (ally != null) { - applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally}); + applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", { ...abAttrParams, pokemon: ally }); } // Non-priority, single-hit moves of the user's Tera Type are always a bare minimum of 60 power const sourceTeraType = source.getTeraType(); - if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if ( + source.isTerastallized + && sourceTeraType === this.type + && power.value < 60 + && this.priority <= 0 + && !this.hasAttr("MultiHitAttr") + && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id) + ) { power.value = 60; } const fieldAuras = new Set( - globalScene.getField(true) - .map((p) => p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => { + globalScene.getField(true).flatMap(p => + p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => { const condition = attr.getCondition(); - return (!condition || condition(p)); - })) - .flat(), + return !condition || condition(p); + }), + ), ); for (const aura of fieldAuras) { // TODO: Refactor the fieldAura attribute so that its apply method is not directly called - aura.apply({pokemon: source, simulated, opponent: target, move: this, power}); + aura.apply({ pokemon: source, simulated, opponent: target, move: this, power }); } for (const p of source.getAlliesGenerator()) { - applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power}); + applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", { pokemon: p, opponent: target, move: this, simulated, power }); } power.value *= typeChangeMovePowerMultiplier.value; - const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === typeChangeHolder.value) as TypeBoostTag; + const typeBoost = source.findTag( + t => t instanceof TypeBoostTag && t.boostedType === typeChangeHolder.value, + ) as TypeBoostTag; if (typeBoost) { power.value *= typeBoost.boostValue; } - if (!this.hasAttr("TypelessAttr")) { globalScene.arena.applyTags(WeakenMoveTypeTag, typeChangeHolder.value, power); globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power); @@ -1058,10 +1118,10 @@ export abstract class Move implements Localizable { return power.value; } - getPriority(user: Pokemon, simulated: boolean = true) { + getPriority(user: Pokemon, simulated = true) { const priority = new NumberHolder(this.priority); applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority); - applyAbAttrs("ChangeMovePriorityAbAttr", {pokemon: user, simulated, move: this, priority}); + applyAbAttrs("ChangeMovePriorityAbAttr", { pokemon: user, simulated, move: this, priority }); return priority.value; } @@ -1071,7 +1131,12 @@ export abstract class Move implements Localizable { return MovePriorityInBracket.FIRST; } const modifierHolder = new NumberHolder(MovePriorityInBracket.NORMAL); - applyAbAttrs("ChangeMovePriorityInBracketAbAttr", { pokemon: user, simulated, move: this, priority: modifierHolder }); + applyAbAttrs("ChangeMovePriorityInBracketAbAttr", { + pokemon: user, + simulated, + move: this, + priority: modifierHolder, + }); return modifierHolder.value; } @@ -1120,48 +1185,39 @@ export abstract class Move implements Localizable { * Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond} * and {@linkcode PokemonMultiHitModifier | Multi Lens}. * @param user - The {@linkcode Pokemon} using the move - * @param restrictSpread - Whether the enhancing effect should ignore multi-target moves; default `false` + * @param restrictSpread - (Default `false`) Whether the enhancing effect should ignore multi-target moves + * @param target - (Optional) The targeted pokemon, used for Pollen Puff * @returns Whether this Move can be given additional strikes. */ // TODO: Remove target parameter used solely to circumvent Pollen Puff shenanigans - the entire move needs to be fixed anyhow - public canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread: boolean = false, target?: Pokemon | null): boolean { - // Multi-strike enhancers... - - // ...cannot enhance charging or 2-turn moves + public canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread = false, target?: Pokemon | null): boolean { if (this.isChargingMove()) { return false; } - - // ...cannot enhance moves hitting multiple targets unless specified + const { targets, multiple } = getMoveTargets(user, this.id); if (restrictSpread && multiple && targets.length > 1) { return false; - }; + } - // ...cannot enhance status moves, including ally-targeting Pollen Puff if ( - this.category === MoveCategory.STATUS - || (target != null && user.getMoveCategory(target, this) === MoveCategory.STATUS)) { + this.category === MoveCategory.STATUS + || (target != null && user.getMoveCategory(target, this) === MoveCategory.STATUS) + ) { return false; } - // ...cannot enhance multi-hit or sacrificial moves - const exceptAttrs: MoveAttrString[] = [ - "MultiHitAttr", - "SacrificialAttr", - "SacrificialAttrOnHit" - ]; + const exceptAttrs: readonly MoveAttrString[] = ["MultiHitAttr", "SacrificialAttr", "SacrificialAttrOnHit"]; if (exceptAttrs.some(attr => this.hasAttr(attr))) { return false; } - // ...and cannot enhance these specific moves - const exceptMoves: MoveId[] = [ + const exceptMoves: readonly MoveId[] = [ MoveId.FLING, MoveId.UPROAR, MoveId.ROLLOUT, MoveId.ICE_BALL, - MoveId.ENDEAVOR + MoveId.ENDEAVOR, ]; if (exceptMoves.includes(this.id)) { return false; @@ -1172,14 +1228,23 @@ export abstract class Move implements Localizable { } export class AttackMove extends Move { - /** This field does not exist at runtime and must not be used. + /** + * This field does not exist at runtime and must not be used. * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. */ - declare private _: never; - override is(moveKind: K): this is MoveClassMap[K] { - return moveKind === "AttackMove"; - } - constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { + private declare _: never; + + constructor( + id: MoveId, + type: PokemonType, + category: MoveCategory, + power: number, + accuracy: number, + pp: number, + chance: number, + priority: number, + generation: number, + ) { super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation); // > All damaging Fire-type moves can... thaw a frozen target, regardless of whether or not they have a chance to burn. @@ -1189,6 +1254,10 @@ export class AttackMove extends Move { } } + public override is(moveKind: K): this is MoveClassMap[K] { + return moveKind === "AttackMove"; + } + /** * Compute the benefit score of this move based on the offensive stat used and the move's power. * @param user The Pokemon using the move @@ -1203,7 +1272,8 @@ export class AttackMove extends Move { const effectiveness = target.getAttackTypeEffectiveness(this.type, user, undefined, undefined, this); attackScore = Math.pow(effectiveness - 1, 2) * (effectiveness < 1 ? -2 : 2); - const [ thisStat, offStat ]: EffectiveStat[] = this.category === MoveCategory.PHYSICAL ? [ Stat.ATK, Stat.SPATK ] : [ Stat.SPATK, Stat.ATK ]; + const [thisStat, offStat]: EffectiveStat[] = + this.category === MoveCategory.PHYSICAL ? [Stat.ATK, Stat.SPATK] : [Stat.SPATK, Stat.ATK]; const statHolder = new NumberHolder(user.getEffectiveStat(thisStat, target)); const offStatValue = user.getEffectiveStat(offStat, target); applyMoveAttrs("VariableAtkAttr", user, target, move, statHolder); @@ -1227,8 +1297,16 @@ export class StatusMove extends Move { /** This field does not exist at runtime and must not be used. * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. */ - declare private _: never; - constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) { + private declare _: never; + constructor( + id: MoveId, + type: PokemonType, + accuracy: number, + pp: number, + chance: number, + priority: number, + generation: number, + ) { super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation); } @@ -1241,8 +1319,16 @@ export class SelfStatusMove extends Move { /** This field does not exist at runtime and must not be used. * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. */ - declare private _: never; - constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) { + private declare _: never; + constructor( + id: MoveId, + type: PokemonType, + accuracy: number, + pp: number, + chance: number, + priority: number, + generation: number, + ) { super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation); } @@ -1251,9 +1337,9 @@ export class SelfStatusMove extends Move { } } -type SubMove = AbstractConstructor +type SubMove = AbstractConstructor; -function ChargeMove(Base: TBase, nameAppend: string) { +function ChargeMove(Base: TBase, _nameAppend: string) { // NB: This cannot be made into a oneline return abstract class Charging extends Base { /** The animation to play during the move's charging phase */ @@ -1269,9 +1355,9 @@ function ChargeMove(Base: TBase, nameAppend: string) { } /** - * Sets the text to be displayed during this move's charging phase. - * References to the user Pokemon should be written as "{USER}", and - * references to the target Pokemon should be written as "{TARGET}". + * Sets the text to be displayed during this move's charging phase. \ + * References to the user Pokemon should be written as `"{USER}"`, and + * references to the target Pokemon should be written as `"{TARGET}"`. * @param chargeText the text to set * @returns this {@linkcode Move} (for chaining API purposes) */ @@ -1286,9 +1372,10 @@ function ChargeMove(Base: TBase, nameAppend: string) { * @param target the {@linkcode Pokemon} targeted by this move (optional) */ showChargeText(user: Pokemon, target?: Pokemon): void { - globalScene.phaseManager.queueMessage(this._chargeText - .replace("{USER}", getPokemonNameWithAffix(user)) - .replace("{TARGET}", getPokemonNameWithAffix(target)) + globalScene.phaseManager.queueMessage( + this._chargeText + .replace("{USER}", getPokemonNameWithAffix(user)) + .replace("{TARGET}", getPokemonNameWithAffix(target)), ); } @@ -1298,7 +1385,7 @@ function ChargeMove(Base: TBase, nameAppend: string) { * @returns Array of attributes that match `attrType`, or an empty array if * no matches are found. */ - getChargeAttrs(attrType: T): (MoveAttrMap[T])[] { + getChargeAttrs(attrType: T): MoveAttrMap[T][] { const targetAttr = MoveAttrs[attrType]; if (!targetAttr) { return []; @@ -1316,7 +1403,7 @@ function ChargeMove(Base: TBase, nameAppend: string) { if (!targetAttr) { return false; } - return this.chargeAttrs.some((attr) => attr instanceof targetAttr); + return this.chargeAttrs.some(attr => attr instanceof targetAttr); } /** @@ -1331,7 +1418,7 @@ function ChargeMove(Base: TBase, nameAppend: string) { return this; } - }; + } return Charging; } @@ -1359,7 +1446,7 @@ export abstract class MoveAttr { return this instanceof targetAttr; } - constructor(selfTarget: boolean = false) { + constructor(selfTarget = false) { this.selfTarget = selfTarget; } @@ -1373,7 +1460,7 @@ export abstract class MoveAttr { * @param args Set of unique arguments needed by this attribute * @returns true if application of the ability succeeds */ - apply(user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): boolean { + apply(_user: Pokemon | null, _target: Pokemon | null, _move: Move, _args: any[]): boolean { return true; } @@ -1393,7 +1480,7 @@ export abstract class MoveAttr { * @param move {@linkcode Move} with this attribute * @returns the string representing failure of this {@linkcode Move} */ - getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined { + getFailedText(_user: Pokemon, _target: Pokemon, _move: Move): string | undefined { return; } @@ -1402,7 +1489,7 @@ export abstract class MoveAttr { * @see {@linkcode EnemyPokemon.getNextMove} * @virtual */ - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { return 0; } @@ -1411,7 +1498,7 @@ export abstract class MoveAttr { * @see {@linkcode EnemyPokemon.getNextMove} * @virtual */ - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { return 0; } } @@ -1451,7 +1538,7 @@ export class MoveEffectAttr extends MoveAttr { * Defines when this effect should trigger in the move's effect order. * @defaultValue {@linkcode MoveEffectTrigger.POST_APPLY} */ - public get trigger () { + public get trigger() { return this.options?.trigger ?? MoveEffectTrigger.POST_APPLY; } @@ -1460,7 +1547,7 @@ export class MoveEffectAttr extends MoveAttr { * multi-hit moves. * @defaultValue `false` */ - public get firstHitOnly () { + public get firstHitOnly() { return this.options?.firstHitOnly ?? false; } @@ -1469,7 +1556,7 @@ export class MoveEffectAttr extends MoveAttr { * multi-hit moves. * @defaultValue `false` */ - public get lastHitOnly () { + public get lastHitOnly() { return this.options?.lastHitOnly ?? false; } @@ -1478,7 +1565,7 @@ export class MoveEffectAttr extends MoveAttr { * for the first time when targeting multiple {@linkcode Pokemon}. * @defaultValue `false` */ - public get firstTargetOnly () { + public get firstTargetOnly() { return this.options?.firstTargetOnly ?? false; } @@ -1486,7 +1573,7 @@ export class MoveEffectAttr extends MoveAttr { * If defined, overrides the move's base chance for this * secondary effect to trigger. */ - public get effectChanceOverride () { + public get effectChanceOverride() { return this.options?.effectChanceOverride; } @@ -1498,13 +1585,13 @@ export class MoveEffectAttr extends MoveAttr { * @param user - The {@linkcode Pokemon} using the move * @param target - The {@linkcode Pokemon} being targeted by the move, or {@linkcode user} if the move is * {@linkcode selfTarget | self-targeting} - * @param move - The {@linkcode Move} being used + * @param _move - The {@linkcode Move} being used * @param _args - Set of unique arguments needed by this attribute * @returns `true` if basic application of this `MoveAttr`s effects should be possible */ // TODO: Decouple this check from the `apply` step // TODO: Make non-damaging moves fail by default if none of their attributes can apply - canApply(user: Pokemon, target: Pokemon, move: Move, _args?: any[]) { + canApply(user: Pokemon, target: Pokemon, _move: Move, _args?: any[]) { return !(this.selfTarget ? user : target).isFainted(); } @@ -1522,10 +1609,15 @@ export class MoveEffectAttr extends MoveAttr { * @param selfEffect `true` if move targets user. * @returns Move effect chance value. */ - getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { + getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: boolean, showAbility?: boolean): number { const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance); - applyAbAttrs("MoveEffectChanceMultiplierAbAttr", {pokemon: user, simulated: !showAbility, chance: moveChance, move}); + applyAbAttrs("MoveEffectChanceMultiplierAbAttr", { + pokemon: user, + simulated: !showAbility, + chance: moveChance, + move, + }); if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) { const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; @@ -1533,7 +1625,7 @@ export class MoveEffectAttr extends MoveAttr { } if (!selfEffect) { - applyAbAttrs("IgnoreMoveEffectsAbAttr", {pokemon: target, move, simulated: !showAbility, chance: moveChance}); + applyAbAttrs("IgnoreMoveEffectsAbAttr", { pokemon: target, move, simulated: !showAbility, chance: moveChance }); } return moveChance.value; } @@ -1556,7 +1648,7 @@ export class MoveHeaderAttr extends MoveAttr { */ export class MessageHeaderAttr extends MoveHeaderAttr { /** The message to display, or a function producing one. */ - private message: string | MoveMessageFunc; + private readonly message: string | MoveMessageFunc; constructor(message: string | MoveMessageFunc) { super(); @@ -1564,9 +1656,7 @@ export class MessageHeaderAttr extends MoveHeaderAttr { } apply(user: Pokemon, target: Pokemon, move: Move): boolean { - const message = typeof this.message === "string" - ? this.message - : this.message(user, target, move); + const message = typeof this.message === "string" ? this.message : this.message(user, target, move); if (message) { globalScene.phaseManager.queueMessage(message); @@ -1581,14 +1671,14 @@ export class MessageHeaderAttr extends MoveHeaderAttr { * @see {@linkcode MoveHeaderAttr} */ export class AddBattlerTagHeaderAttr extends MoveHeaderAttr { - private tagType: BattlerTagType; + private readonly tagType: BattlerTagType; constructor(tagType: BattlerTagType) { super(); this.tagType = tagType; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { user.addTag(this.tagType); return true; } @@ -1614,7 +1704,7 @@ export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr { */ export class PreMoveMessageAttr extends MoveAttr { /** The message to display or a function returning one */ - private message: string | MoveMessageFunc; + private readonly message: string | MoveMessageFunc; /** * Create a new {@linkcode PreMoveMessageAttr} to display a message before move execution. @@ -1629,9 +1719,7 @@ export class PreMoveMessageAttr extends MoveAttr { } apply(user: Pokemon, target: Pokemon, move: Move): boolean { - const message = typeof this.message === "function" - ? this.message(user, target, move) - : this.message; + const message = typeof this.message === "function" ? this.message(user, target, move) : this.message; // TODO: Consider changing if/when MoveAttr `apply` return values become significant if (message) { @@ -1674,8 +1762,8 @@ export class PreUseInterruptAttr extends MoveAttr { } currentPhase.cancel(); globalScene.phaseManager.queueMessage( - typeof this.message === "string" ? this.message : this.message(user, target, move) - ) + typeof this.message === "string" ? this.message : this.message(user, target, move), + ); return true; } @@ -1687,9 +1775,7 @@ export class PreUseInterruptAttr extends MoveAttr { */ override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined { if (this.message && this.conditionFunc(user, target, move)) { - return typeof this.message === "string" - ? this.message - : this.message(user, target, move); + return typeof this.message === "string" ? this.message : this.message(user, target, move); } } } @@ -1698,10 +1784,10 @@ export class PreUseInterruptAttr extends MoveAttr { * Attribute for Status moves that take attack type effectiveness * into consideration (i.e. {@linkcode https://bulbapedia.bulbagarden.net/wiki/Thunder_Wave_(move) | Thunder Wave}) */ -export class RespectAttackTypeImmunityAttr extends MoveAttr { } +export class RespectAttackTypeImmunityAttr extends MoveAttr {} export class IgnoreOpponentStatStagesAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as BooleanHolder).value = true; return true; @@ -1709,31 +1795,31 @@ export class IgnoreOpponentStatStagesAttr extends MoveAttr { } export class HighCritAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value++; return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { return 3; } } export class CritOnlyAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as BooleanHolder).value = true; return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { return 5; } } export class FixedDamageAttr extends MoveAttr { - private damage: number; + private readonly damage: number; constructor(damage: number) { super(); @@ -1747,7 +1833,7 @@ export class FixedDamageAttr extends MoveAttr { return true; } - getDamage(user: Pokemon, target: Pokemon, move: Move): number { + getDamage(_user: Pokemon, _target: Pokemon, _move: Move): number { return this.damage; } } @@ -1757,7 +1843,7 @@ export class UserHpDamageAttr extends FixedDamageAttr { super(0); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = user.hp; return true; @@ -1774,9 +1860,13 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { super(0); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { // first, determine if the hit is coming from multi lens or not - const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; + const lensCount = + user + .getHeldItems() + .find(i => i instanceof PokemonMultiHitModifier) + ?.getStackCount() ?? 0; if (lensCount <= 0) { // no multi lenses; we can just halve the target's hp and call it a day (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); @@ -1785,6 +1875,11 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { // figure out what hit # we're on switch (user.turnData.hitCount - user.turnData.hitsLeft) { + case lensCount + 1: + // parental bond added hit; calc damage as normal + (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); + return true; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional? case 0: // first hit of move; update initialHp tracker this.initialHp = target.hp; @@ -1792,15 +1887,11 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { // multi lens added hit; use initialHp tracker to ensure correct damage (args[0] as NumberHolder).value = toDmgValue(this.initialHp / 2); return true; - case lensCount + 1: - // parental bond added hit; calc damage as normal - (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); - return true; } } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return target.getHpRatio() > 0.5 ? Math.floor(((target.getHpRatio() - 0.5) * -24) + 4) : -20; + getTargetBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { + return target.getHpRatio() > 0.5 ? Math.floor((target.getHpRatio() - 0.5) * -24 + 4) : -20; } } @@ -1809,14 +1900,14 @@ export class MatchHpAttr extends FixedDamageAttr { super(0); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = target.hp - user.hp; return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => user.hp <= target.hp; + return (user, target, _move) => user.hp <= target.hp; } // TODO @@ -1827,8 +1918,8 @@ export class MatchHpAttr extends FixedDamageAttr { export class CounterDamageAttr extends FixedDamageAttr { /** The damage category of counter attacks to process, or `undefined` for either */ - private moveFilter?: MoveDamageCategory; - private multiplier: number; + private readonly moveFilter?: MoveDamageCategory; + private readonly multiplier: number; /** * @param multiplier - The damage multiplier to apply to the total damage received @@ -1840,15 +1931,16 @@ export class CounterDamageAttr extends FixedDamageAttr { this.multiplier = multiplier; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const damage = user.turnData.attacksReceived.find(ar => { - const category = allMoves[ar.move].category; - return ( - category !== MoveCategory.STATUS - && !areAllies(user.getBattlerIndex(), ar.sourceBattlerIndex) - && (this.moveFilter === undefined || category === this.moveFilter) - ) - })?.damage ?? 0; + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { + const damage = + user.turnData.attacksReceived.find(ar => { + const category = allMoves[ar.move].category; + return ( + category !== MoveCategory.STATUS + && !areAllies(user.getBattlerIndex(), ar.sourceBattlerIndex) + && (this.moveFilter === undefined || category === this.moveFilter) + ); + })?.damage ?? 0; (args[0] as NumberHolder).value = toDmgValue(damage * this.multiplier); return true; } @@ -1858,8 +1950,8 @@ export class CounterDamageAttr extends FixedDamageAttr { * Attribute for counter-like moves to redirect the move to a different target */ export class CounterRedirectAttr extends MoveAttr { - declare private moveFilter?: MoveDamageCategory; - constructor(moveFilter? : MoveDamageCategory) { + private declare moveFilter?: MoveDamageCategory; + constructor(moveFilter?: MoveDamageCategory) { super(); if (moveFilter !== undefined) { this.moveFilter = moveFilter; @@ -1873,15 +1965,13 @@ export class CounterRedirectAttr extends MoveAttr { * @param move - The move being used * @param args - args[0] holds the battler index of the target that the move will be redirected to */ - override apply(user: Pokemon, target: Pokemon | null, move: Move, args: [NumberHolder, ...any[]]): boolean { + override apply(user: Pokemon, _target: Pokemon | null, _move: Move, args: [NumberHolder, ...any[]]): boolean { const desiredTarget = getCounterAttackTarget(user, this.moveFilter); if (desiredTarget !== null && desiredTarget !== BattlerIndex.ATTACKER) { // check if the target is still alive - if ( - globalScene.currentBattle.double && - !globalScene.getField()[desiredTarget]?.isActive(true) - ) { - const targetField = desiredTarget >= BattlerIndex.ENEMY ? globalScene.getEnemyField() : globalScene.getPlayerField(); + if (globalScene.currentBattle.double && !globalScene.getField()[desiredTarget]?.isActive(true)) { + const targetField = + desiredTarget >= BattlerIndex.ENEMY ? globalScene.getEnemyField() : globalScene.getPlayerField(); args[0].value = targetField.find(p => p.hp > 0)?.getBattlerIndex() ?? BattlerIndex.ATTACKER; } else { args[0].value = desiredTarget; @@ -1897,7 +1987,7 @@ export class LevelDamageAttr extends FixedDamageAttr { super(0); } - getDamage(user: Pokemon, target: Pokemon, move: Move): number { + getDamage(user: Pokemon, _target: Pokemon, _move: Move): number { return user.level; } } @@ -1907,7 +1997,7 @@ export class RandomLevelDamageAttr extends FixedDamageAttr { super(0); } - getDamage(user: Pokemon, target: Pokemon, move: Move): number { + getDamage(user: Pokemon, _target: Pokemon, _move: Move): number { return toDmgValue(user.level * (user.randBattleSeedIntRange(50, 150) * 0.01)); } } @@ -1920,17 +2010,17 @@ export class ModifiedDamageAttr extends MoveAttr { return true; } - getModifiedDamage(user: Pokemon, target: Pokemon, move: Move, damage: number): number { + getModifiedDamage(_user: Pokemon, _target: Pokemon, _move: Move, damage: number): number { return damage; } } export class SurviveDamageAttr extends ModifiedDamageAttr { - getModifiedDamage(user: Pokemon, target: Pokemon, move: Move, damage: number): number { + getModifiedDamage(_user: Pokemon, target: Pokemon, _move: Move, damage: number): number { return Math.min(damage, target.hp - 1); } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { return target.hp > 1 ? 0 : -20; } } @@ -1940,18 +2030,16 @@ export class SurviveDamageAttr extends ModifiedDamageAttr { */ export class MessageAttr extends MoveEffectAttr { /** The message to display, either as a string or a function returning one. */ - private message: string | MoveMessageFunc; + private readonly message: string | MoveMessageFunc; constructor(message: string | MoveMessageFunc, options?: MoveEffectAttrOptions) { // TODO: Do we need to respect `selfTarget` if we're just displaying text? - super(false, options) + super(false, options); this.message = message; } override apply(user: Pokemon, target: Pokemon, move: Move): boolean { - const message = typeof this.message === "function" - ? this.message(user, target, move) - : this.message; + const message = typeof this.message === "function" ? this.message(user, target, move) : this.message; // TODO: Consider changing if/when MoveAttr `apply` return values become significant if (message) { @@ -1963,11 +2051,11 @@ export class MessageAttr extends MoveEffectAttr { } export class RecoilAttr extends MoveEffectAttr { - private useHp: boolean; - private damageRatio: number; - private unblockable: boolean; + private readonly useHp: boolean; + private readonly damageRatio: number; + private readonly unblockable: boolean; - constructor(useHp: boolean = false, damageRatio: number = 0.25, unblockable: boolean = false) { + constructor(useHp = false, damageRatio = 0.25, unblockable = false) { super(true, { lastHitOnly: true }); this.useHp = useHp; @@ -1982,7 +2070,7 @@ export class RecoilAttr extends MoveEffectAttr { const cancelled = new BooleanHolder(false); if (!this.unblockable) { - const abAttrParams: AbAttrParamsWithCancel = {pokemon: user, cancelled}; + const abAttrParams: AbAttrParamsWithCancel = { pokemon: user, cancelled }; applyAbAttrs("BlockRecoilDamageAttr", abAttrParams); applyAbAttrs("BlockNonDirectDamageAbAttr", abAttrParams); } @@ -1992,11 +2080,14 @@ export class RecoilAttr extends MoveEffectAttr { } // Chloroblast and Struggle should not deal recoil damage if the move was not successful - if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result ?? MoveResult.FAIL)) { + if ( + this.useHp + && [MoveResult.FAIL, MoveResult.MISS].includes(user.getLastXMoves(1)[0]?.result ?? MoveResult.FAIL) + ) { return false; } - const damageValue = (!this.useHp ? user.turnData.totalDamageDealt : user.getMaxHp()) * this.damageRatio; + const damageValue = (this.useHp ? user.getMaxHp() : user.turnData.totalDamageDealt) * this.damageRatio; const minValue = user.turnData.totalDamageDealt ? 1 : 0; const recoilDamage = toDmgValue(damageValue, minValue); if (!recoilDamage) { @@ -2008,18 +2099,19 @@ export class RecoilAttr extends MoveEffectAttr { } user.damageAndUpdate(recoilDamage, { result: HitResult.INDIRECT, ignoreSegments: true }); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) }), + ); user.turnData.damageTaken += recoilDamage; return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return Math.floor((move.power / 5) / -4); + getUserBenefitScore(_user: Pokemon, _target: Pokemon, move: Move): number { + return Math.floor(move.power / 5 / -4); } } - /** * Attribute used for moves which self KO the user regardless if the move hits a target */ @@ -2035,10 +2127,10 @@ export class SacrificialAttr extends MoveEffectAttr { * @param move {@linkcode Move} with this attribute * @param args N/A * @returns true if the function succeeds - **/ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + */ + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { user.damageAndUpdate(user.hp, { result: HitResult.INDIRECT, ignoreSegments: true }); - user.turnData.damageTaken += user.hp; + user.turnData.damageTaken += user.hp; return true; } @@ -2066,7 +2158,7 @@ export class SacrificialAttrOnHit extends MoveEffectAttr { * @param move {@linkcode Move} with this attribute * @param args N/A * @returns true if the function succeeds - **/ + */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // If the move fails to hit a target, then the user does not faint and the function returns false if (!super.apply(user, target, move, args)) { @@ -2111,10 +2203,12 @@ export class HalfSacrificialAttr extends MoveEffectAttr { const cancelled = new BooleanHolder(false); // Check to see if the Pokemon has an ability that blocks non-direct damage - applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled}); + applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled }); if (!cancelled.value) { user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) }), + ); // Queue recoil message } return true; } @@ -2123,7 +2217,9 @@ export class HalfSacrificialAttr extends MoveEffectAttr { if (user.isBoss()) { return -10; } - return Math.ceil(((1 - user.getHpRatio() / 2) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5)); + return Math.ceil( + ((1 - user.getHpRatio() / 2) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5), + ); } } @@ -2132,9 +2228,9 @@ export class HalfSacrificialAttr extends MoveEffectAttr { */ export class AddSubstituteAttr extends MoveEffectAttr { /** The ratio of the user's max HP that is required to apply this effect */ - private hpCost: number; + private readonly hpCost: number; /** Whether the damage taken should be rounded up (Shed Tail rounds up) */ - private roundUp: boolean; + private readonly roundUp: boolean; constructor(hpCost: number, roundUp: boolean) { super(true); @@ -2156,13 +2252,15 @@ export class AddSubstituteAttr extends MoveEffectAttr { return false; } - const damageTaken = this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost); + const damageTaken = this.roundUp + ? Math.ceil(user.getMaxHp() * this.hpCost) + : Math.floor(user.getMaxHp() * this.hpCost); user.damageAndUpdate(damageTaken, { result: HitResult.INDIRECT, ignoreSegments: true, ignoreFaintPhase: true }); user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id); return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { if (user.isBoss()) { return -10; } @@ -2170,7 +2268,10 @@ export class AddSubstituteAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, _target, _move) => !user.getTag(SubstituteTag) && user.hp > (this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost)) && user.getMaxHp() > 1; + return (user, _target, _move) => + !user.getTag(SubstituteTag) + && user.hp > (this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost)) + && user.getMaxHp() > 1; } /** @@ -2181,7 +2282,8 @@ export class AddSubstituteAttr extends MoveEffectAttr { getFailedText(user: Pokemon, _target: Pokemon, _move: Move): string | undefined { if (user.getTag(SubstituteTag)) { return i18next.t("moveTriggers:substituteOnOverlap", { pokemonName: getPokemonNameWithAffix(user) }); - } else if (user.hp <= Math.floor(user.getMaxHp() / 4) || user.getMaxHp() === 1) { + } + if (user.hp <= Math.floor(user.getMaxHp() / 4) || user.getMaxHp() === 1) { return i18next.t("moveTriggers:substituteNotEnoughHp"); } } @@ -2191,14 +2293,16 @@ export class AddSubstituteAttr extends MoveEffectAttr { * Heals the user or target by {@linkcode healRatio} depending on the value of {@linkcode selfTarget} */ export class HealAttr extends MoveEffectAttr { - constructor( - /** The percentage of {@linkcode Stat.HP} to heal. */ - private healRatio: number, - /** Whether to display a healing animation when healing the target; default `false` */ - private showAnim = false, - selfTarget = true - ) { + /** The percentage of {@linkcode Stat.HP} to heal. */ + private readonly healRatio: number; + /** Whether to display a healing animation when healing the target; default `false` */ + private readonly showAnim: boolean; + + constructor(healRatio: number, showAnim = false, selfTarget = true) { super(selfTarget); + + this.healRatio = healRatio; + this.showAnim = showAnim; } override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { @@ -2211,12 +2315,18 @@ export class HealAttr extends MoveEffectAttr { * This heals the target and shows the appropriate message. */ protected addHealPhase(target: Pokemon, healRatio: number) { - globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), - toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim); + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + target.getBattlerIndex(), + toDmgValue(target.getMaxHp() * healRatio), + i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), + true, + !this.showAnim, + ); } override getTargetBenefitScore(user: Pokemon, target: Pokemon, _move: Move): number { - const score = ((1 - (this.selfTarget ? user : target).getHpRatio()) * 20) - this.healRatio * 10; + const score = (1 - (this.selfTarget ? user : target).getHpRatio()) * 20 - this.healRatio * 10; return Math.round(score / (1 - this.healRatio / 2)); } @@ -2232,9 +2342,11 @@ export class HealAttr extends MoveEffectAttr { // TOOD: Fix this in PR#6276 const phaseManager = globalScene.phaseManager; if (phaseManager.getCurrentPhase().is("MovePhase")) { - phaseManager.queueMessage(i18next.t("battle:hpIsFull", { - pokemonName: getPokemonNameWithAffix(healedPokemon), - })) + phaseManager.queueMessage( + i18next.t("battle:hpIsFull", { + pokemonName: getPokemonNameWithAffix(healedPokemon), + }), + ); } return false; } @@ -2247,7 +2359,7 @@ export class HealAttr extends MoveEffectAttr { * Used for {@linkcode MoveId.REST}. */ export class RestAttr extends HealAttr { - private duration: number; + private readonly duration: number; constructor(duration: number) { super(1, true); @@ -2255,24 +2367,29 @@ export class RestAttr extends HealAttr { } override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const wasSet = user.trySetStatus(StatusEffect.SLEEP, user, this.duration, null, true, true, - i18next.t("moveTriggers:restBecameHealthy", { - pokemonName: getPokemonNameWithAffix(user), - })); + const wasSet = user.trySetStatus( + StatusEffect.SLEEP, + user, + this.duration, + null, + true, + true, + i18next.t("moveTriggers:restBecameHealthy", { + pokemonName: getPokemonNameWithAffix(user), + }), + ); return wasSet && super.apply(user, target, move, args); } override addHealPhase(user: Pokemon): void { - globalScene.phaseManager.unshiftNew("PokemonHealPhase", user.getBattlerIndex(), user.getMaxHp(), null) + globalScene.phaseManager.unshiftNew("PokemonHealPhase", user.getBattlerIndex(), user.getMaxHp(), null); } // TODO: change after HealAttr is changed to fail move override getCondition(): MoveConditionFunc { return (user, target, move) => - super.canApply(user, target, move, []) - // Intentionally suppress messages here as we display generic fail msg - // TODO: This might have order-of-operation jank - && user.canSetStatus(StatusEffect.SLEEP, true, true, user) + super.canApply(user, target, move, []) // Intentionally suppress messages here as we display generic fail msg // TODO: This might have order-of-operation jank + && user.canSetStatus(StatusEffect.SLEEP, true, true, user); } } @@ -2281,9 +2398,9 @@ export class RestAttr extends HealAttr { */ export class PartyStatusCureAttr extends MoveEffectAttr { /** Message to display after using move */ - private message: string | null; + private readonly message: string | null; /** Skips mons with this ability, ie. Soundproof */ - private abilityCondition: AbilityId; + private readonly abilityCondition: AbilityId; constructor(message: string | null, abilityCondition: AbilityId) { super(); @@ -2293,10 +2410,10 @@ export class PartyStatusCureAttr extends MoveEffectAttr { } //The same as MoveEffectAttr.canApply, except it doesn't check for the target's HP. - canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { + canApply(user: Pokemon, target: Pokemon, move: Move, _args: any[]) { const isTargetValid = - (this.selfTarget && user.hp && !user.getTag(BattlerTagType.FRENZY)) || - (!this.selfTarget && (!target.getTag(BattlerTagType.PROTECTED) || move.hasFlag(MoveFlags.IGNORE_PROTECT))); + (this.selfTarget && user.hp && !user.getTag(BattlerTagType.FRENZY)) + || (!this.selfTarget && (!target.getTag(BattlerTagType.PROTECTED) || move.hasFlag(MoveFlags.IGNORE_PROTECT))); return !!isTargetValid; } @@ -2305,7 +2422,9 @@ export class PartyStatusCureAttr extends MoveEffectAttr { return false; } const partyPokemon = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); - partyPokemon.forEach(p => this.cureStatus(p, user.id)); + for (const p of partyPokemon) { + this.cureStatus(p, user.id); + } if (this.message) { globalScene.phaseManager.queueMessage(this.message); @@ -2320,16 +2439,25 @@ export class PartyStatusCureAttr extends MoveEffectAttr { * @param userId The ID of the (move) {@linkcode Pokemon | user}. */ public cureStatus(pokemon: Pokemon, userId: number) { - if (!pokemon.isOnField() || pokemon.id === userId) { // user always cures its own status, regardless of ability + if (!pokemon.isOnField() || pokemon.id === userId) { + // user always cures its own status, regardless of ability pokemon.resetStatus(false); pokemon.updateInfo(); - } else if (!pokemon.hasAbility(this.abilityCondition)) { + } else if (pokemon.hasAbility(this.abilityCondition)) { + // TODO: Ability displays should be handled by the ability + globalScene.phaseManager.queueAbilityDisplay( + pokemon, + pokemon.getPassiveAbility()?.id === this.abilityCondition, + true, + ); + globalScene.phaseManager.queueAbilityDisplay( + pokemon, + pokemon.getPassiveAbility()?.id === this.abilityCondition, + false, + ); + } else { pokemon.resetStatus(); pokemon.updateInfo(); - } else { - // TODO: Ability displays should be handled by the ability - globalScene.phaseManager.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, true); - globalScene.phaseManager.queueAbilityDisplay(pokemon, pokemon.getPassiveAbility()?.id === this.abilityCondition, false); } } } @@ -2353,23 +2481,25 @@ export class FlameBurstAttr extends MoveEffectAttr { * @param args - n/a * @returns A boolean indicating whether the effect was successfully applied. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { const targetAlly = target.getAlly(); const cancelled = new BooleanHolder(false); if (targetAlly != null) { - applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: targetAlly, cancelled}); + applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: targetAlly, cancelled }); } if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { return false; } - targetAlly.damageAndUpdate(Math.max(1, Math.floor(1 / 16 * targetAlly.getMaxHp())), { result: HitResult.INDIRECT }); + targetAlly.damageAndUpdate(Math.max(1, Math.floor((1 / 16) * targetAlly.getMaxHp())), { + result: HitResult.INDIRECT, + }); return true; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { return target.getAlly() != null ? -5 : 0; } } @@ -2403,12 +2533,13 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { return true; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { return -20; } getCondition(): MoveConditionFunc { - return (user, _target, _move) => globalScene.getPlayerParty().filter(p => p.isActive()).length > globalScene.currentBattle.getBattlerCount(); + return (_user, _target, _move) => + globalScene.getPlayerParty().filter(p => p.isActive()).length > globalScene.currentBattle.getBattlerCount(); } } @@ -2432,7 +2563,7 @@ export class IgnoreWeatherTypeDebuffAttr extends MoveAttr { * @param args [0] {@linkcode NumberHolder} for arenaAttackTypeMultiplier * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const weatherModifier = args[0] as NumberHolder; //If the type-based attack power modifier due to weather (e.g. Water moves in Sun) is below 1, set it to 1 if (globalScene.arena.weather?.weatherType === this.weather) { @@ -2447,7 +2578,7 @@ export abstract class WeatherHealAttr extends HealAttr { super(0.5); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { let healRatio = 0.5; if (!globalScene.arena.weather?.isEffectSuppressed()) { const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; @@ -2496,13 +2627,19 @@ export class SandHealAttr extends WeatherHealAttr { */ export class BoostHealAttr extends HealAttr { /** Healing received when {@linkcode condition} is false */ - private normalHealRatio: number; + private readonly normalHealRatio: number; /** Healing received when {@linkcode condition} is true */ - private boostedHealRatio: number; + private readonly boostedHealRatio: number; /** The lambda expression to check against when boosting the healing value */ - private condition?: MoveConditionFunc; + private readonly condition?: MoveConditionFunc; - constructor(normalHealRatio: number = 0.5, boostedHealRatio: number = 2 / 3, showAnim?: boolean, selfTarget?: boolean, condition?: MoveConditionFunc) { + constructor( + normalHealRatio = 0.5, + boostedHealRatio: number = 2 / 3, + showAnim?: boolean, + selfTarget?: boolean, + condition?: MoveConditionFunc, + ) { super(normalHealRatio, showAnim, selfTarget); this.normalHealRatio = normalHealRatio; this.boostedHealRatio = boostedHealRatio; @@ -2516,8 +2653,10 @@ export class BoostHealAttr extends HealAttr { * @param args N/A * @returns true if the move was successful */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const healRatio: number = (this.condition ? this.condition(user, target, move) : false) ? this.boostedHealRatio : this.normalHealRatio; + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { + const healRatio: number = (this.condition ? this.condition(user, target, move) : false) + ? this.boostedHealRatio + : this.normalHealRatio; this.addHealPhase(target, healRatio); return true; } @@ -2546,8 +2685,8 @@ export class HealOnAllyAttr extends HealAttr { */ // TODO: Make Strength Sap its own attribute that extends off of this one export class HitHealAttr extends MoveEffectAttr { - private healRatio: number; - private healStat: EffectiveStat | null; + private readonly healRatio: number; + private readonly healStat: EffectiveStat | null; constructor(healRatio?: number | null, healStat?: EffectiveStat) { super(true); @@ -2565,7 +2704,7 @@ export class HitHealAttr extends MoveEffectAttr { * @param args N/A * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { if (target.hasAbilityWithAttr("ReverseDrainAbAttr")) { return false; } @@ -2592,13 +2731,15 @@ export class HitHealAttr extends MoveEffectAttr { getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { if (this.healStat) { const healAmount = target.getEffectiveStat(this.healStat); - return Math.floor(Math.max(0, (Math.min(1, (healAmount + user.hp) / user.getMaxHp() - 0.33))) / user.getHpRatio()); + return Math.floor(Math.max(0, Math.min(1, (healAmount + user.hp) / user.getMaxHp() - 0.33)) / user.getHpRatio()); } - return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * (move.power / 4)); + return Math.floor(Math.max(1 - user.getHpRatio() - 0.33, 0) * (move.power / 4)); } public getHealAmount(user: Pokemon, target: Pokemon): number { - return (this.healStat) ? target.getEffectiveStat(this.healStat) : toDmgValue(user.turnData.singleHitDamageDealt * this.healRatio); + return this.healStat + ? target.getEffectiveStat(this.healStat) + : toDmgValue(user.turnData.singleHitDamageDealt * this.healRatio); } } @@ -2609,11 +2750,11 @@ export class HitHealAttr extends MoveEffectAttr { */ export class IncrementMovePriorityAttr extends MoveAttr { /** The condition for a move's priority being incremented */ - private moveIncrementFunc: (pokemon: Pokemon, target:Pokemon, move: Move) => boolean; + private readonly moveIncrementFunc: (pokemon: Pokemon, target: Pokemon, move: Move) => boolean; /** The amount to increment priority by, if condition passes. */ - private increaseAmount: number; + private readonly increaseAmount: number; - constructor(moveIncrementFunc: (pokemon: Pokemon, target:Pokemon, move: Move) => boolean, increaseAmount = 1) { + constructor(moveIncrementFunc: (pokemon: Pokemon, target: Pokemon, move: Move) => boolean, increaseAmount = 1) { super(); this.moveIncrementFunc = moveIncrementFunc; @@ -2681,7 +2822,7 @@ export class MultiHitAttr extends MoveAttr { return true; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { return -5; } @@ -2693,22 +2834,22 @@ export class MultiHitAttr extends MoveAttr { * @param target {@linkcode Pokemon} targeted by the attack * @returns The number of hits this attack should deal */ - getHitCount(user: Pokemon, target: Pokemon): number { + getHitCount(user: Pokemon, _target: Pokemon): number { switch (this.multiHitType) { - case MultiHitType._2_TO_5: - { + case MultiHitType._2_TO_5: { const rand = user.randBattleSeedInt(20); const hitValue = new NumberHolder(rand); - applyAbAttrs("MaxMultiHitAbAttr", {pokemon: user, hits: hitValue}); + applyAbAttrs("MaxMultiHitAbAttr", { pokemon: user, hits: hitValue }); if (hitValue.value >= 13) { return 2; - } else if (hitValue.value >= 6) { - return 3; - } else if (hitValue.value >= 3) { - return 4; - } else { - return 5; } + if (hitValue.value >= 6) { + return 3; + } + if (hitValue.value >= 3) { + return 4; + } + return 5; } case MultiHitType._2: return 2; @@ -2716,12 +2857,16 @@ export class MultiHitAttr extends MoveAttr { return 3; case MultiHitType._10: return 10; - case MultiHitType.BEAT_UP: + case MultiHitType.BEAT_UP: { const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); // No status means the ally pokemon can contribute to Beat Up return party.reduce((total, pokemon) => { - return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + return ( + total + + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1) + ); }, 0); + } } } @@ -2734,7 +2879,14 @@ export class MultiHitAttr extends MoveAttr { * @param maxMultiHit - Whether the move should always hit the maximum number of times, e.g. due to {@linkcode AbilityId.SKILL_LINK | Skill Link} (default: `false`) * @param ignoreAcc - `true` if the move should ignore accuracy checks, e.g. due to {@linkcode AbilityId.NO_GUARD | No Guard} (default: `false`) */ - calculateExpectedHitCount(move: Move, { ignoreAcc = false, maxMultiHit = false, partySize = 1 }: {ignoreAcc?: boolean, maxMultiHit?: boolean, partySize?: number} = {}): number { + calculateExpectedHitCount( + move: Move, + { + ignoreAcc = false, + maxMultiHit = false, + partySize = 1, + }: { ignoreAcc?: boolean; maxMultiHit?: boolean; partySize?: number } = {}, + ): number { let expectedHits: number; switch (this.multiHitType) { case MultiHitType._2_TO_5: @@ -2760,22 +2912,26 @@ export class MultiHitAttr extends MoveAttr { const acc = move.accuracy / 100; if (move.hasFlag(MoveFlags.CHECK_ALL_HITS) && !maxMultiHit) { // N.B. No moves should be the _2_TO_5 variant and have the CHECK_ALL_HITS flag. - return acc * (1 - Math.pow(acc, expectedHits)) / (1 - acc); + return (acc * (1 - Math.pow(acc, expectedHits))) / (1 - acc); } - return expectedHits *= acc; + return (expectedHits *= acc); } } export class ChangeMultiHitTypeAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { //const hitType = args[0] as Utils.NumberHolder; return false; } } export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (user.species.speciesId === SpeciesId.GRENINJA && user.hasAbility(AbilityId.BATTLE_BOND) && user.formIndex === 2) { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { + if ( + user.species.speciesId === SpeciesId.GRENINJA + && user.hasAbility(AbilityId.BATTLE_BOND) + && user.formIndex === 2 + ) { (args[0] as NumberHolder).value = MultiHitType._3; return true; } @@ -2792,7 +2948,7 @@ export class StatusEffectAttr extends MoveEffectAttr { this.effect = effect; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const statusCheck = moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance; if (!statusCheck) { @@ -2802,10 +2958,8 @@ export class StatusEffectAttr extends MoveEffectAttr { // non-status moves don't play sound effects for failures const quiet = move.category !== MoveCategory.STATUS; - if ( - target.trySetStatus(this.effect, user, undefined, null, false, quiet) - ) { - applyAbAttrs("ConfusionOnStatusEffectAbAttr", {pokemon: user, opponent: target, move, effect: this.effect}); + if (target.trySetStatus(this.effect, user, undefined, null, false, quiet)) { + applyAbAttrs("ConfusionOnStatusEffectAbAttr", { pokemon: user, opponent: target, move, effect: this.effect }); return true; } return false; @@ -2840,7 +2994,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false); - const score = (moveChance < 0) ? -10 : Math.floor(moveChance * -0.1); + const score = moveChance < 0 ? -10 : Math.floor(moveChance * -0.1); const pokemon = this.selfTarget ? user : target; return !pokemon.status && pokemon.canSetStatus(this.effect, true, false, user) ? score : 0; @@ -2861,8 +3015,8 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { * @returns - Whether the effect was successfully applied to the target. */ apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { - const statusToApply = user.status?.effect ?? - (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : StatusEffect.NONE); + const statusToApply = + user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : StatusEffect.NONE); // Bang is justified as condition func returns early if no status is found if (!target.trySetStatus(statusToApply, user)) { @@ -2872,7 +3026,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { if (user.status) { // Add tag to user to heal its status effect after the move ends (unless we have comatose); // occurs after move use to ensure correct Synchronize timing - user.addTag(BattlerTagType.PSYCHO_SHIFT) + user.addTag(BattlerTagType.PSYCHO_SHIFT); } return true; @@ -2884,17 +3038,17 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { return false; } - const statusToApply = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : StatusEffect.NONE); + const statusToApply = + user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : StatusEffect.NONE); return !!statusToApply && target.canSetStatus(statusToApply, false, false, user); - } + }; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(user: Pokemon, target: Pokemon, _move: Move): number { const statusToApply = - user.status?.effect ?? - (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : StatusEffect.NONE); + user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : StatusEffect.NONE); - // TODO: Give this a positive user benefit score + // TODO: Give this a positive user benefit score return !target.status?.effect && statusToApply && target.canSetStatus(statusToApply, true, false, user) ? -10 : 0; } } @@ -2904,49 +3058,63 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { * Used for {@linkcode MoveId.THIEF} and {@linkcode MoveId.COVET}. */ export class StealHeldItemChanceAttr extends MoveEffectAttr { - private chance: number; + private readonly chance: number; constructor(chance: number) { super(false); this.chance = chance; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { const rand = randSeedFloat(); if (rand > this.chance) { return false; } - const heldItems = this.getTargetHeldItems(target).filter((i) => i.isTransferable); - if (!heldItems.length) { + const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable); + if (heldItems.length === 0) { return false; } - const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; - const highestItemTier = heldItems.map((m) => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? - const tierHeldItems = heldItems.filter((m) => m.type.getOrInferTier(poolType) === highestItemTier); + const poolType = target.isPlayer() + ? ModifierPoolType.PLAYER + : target.hasTrainer() + ? ModifierPoolType.TRAINER + : ModifierPoolType.WILD; + const highestItemTier = heldItems + .map(m => m.type.getOrInferTier(poolType)) + .reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? + const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); const stolenItem = tierHeldItems[user.randBattleSeedInt(tierHeldItems.length)]; if (!globalScene.tryTransferHeldItemModifier(stolenItem, user, false)) { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:stoleItem", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + itemName: stolenItem.type.name, + }), + ); return true; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier[]; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { const heldItems = this.getTargetHeldItems(target); - return heldItems.length ? 5 : 0; + return heldItems.length > 0 ? 5 : 0; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { const heldItems = this.getTargetHeldItems(target); - return heldItems.length ? -5 : 0; + return heldItems.length > 0 ? -5 : 0; } } @@ -2958,11 +3126,10 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { * "If the Pokémon is knocked out by the attack, Sticky Hold does not protect the held item." */ export class RemoveHeldItemAttr extends MoveEffectAttr { - /** Optional restriction for item pool to berries only; i.e. Incinerate */ - private berriesOnly: boolean; + private readonly berriesOnly: boolean; - constructor(berriesOnly: boolean = false) { + constructor(berriesOnly = false) { super(false); this.berriesOnly = berriesOnly; } @@ -2975,15 +3142,16 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { * @param args N/A * @returns `true` if an item was able to be removed */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { + if (!this.berriesOnly && target.isPlayer()) { + // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia) return false; } // Check for abilities that block item theft // TODO: This should not trigger if the target would faint beforehand const cancelled = new BooleanHolder(false); - applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled}); + applyAbAttrs("BlockItemTheftAbAttr", { pokemon: target, cancelled }); if (cancelled.value) { return false; @@ -2997,7 +3165,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer()); } - if (!heldItems.length) { + if (heldItems.length === 0) { return false; } @@ -3008,27 +3176,41 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { globalScene.updateModifiers(target.isPlayer()); if (this.berriesOnly) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:incineratedItem", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + itemName: removedItem.type.name, + }), + ); } else { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:knockedOffItem", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + itemName: removedItem.type.name, + }), + ); } return true; } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers( + m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, + target.isPlayer(), + ) as PokemonHeldItemModifier[]; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { const heldItems = this.getTargetHeldItems(target); - return heldItems.length ? 5 : 0; + return heldItems.length > 0 ? 5 : 0; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { const heldItems = this.getTargetHeldItems(target); - return heldItems.length ? -5 : 0; + return heldItems.length > 0 ? -5 : 0; } } @@ -3037,6 +3219,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { */ export class EatBerryAttr extends MoveEffectAttr { protected chosenBerry: BerryModifier; + // biome-ignore lint/complexity/noUselessConstructor: this removes the `options` param from the superclass constructor(selfTarget: boolean) { super(selfTarget); } @@ -3077,8 +3260,10 @@ export class EatBerryAttr extends MoveEffectAttr { } getTargetHeldBerries(target: Pokemon): BerryModifier[] { - return globalScene.findModifiers(m => m instanceof BerryModifier - && (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[]; + return globalScene.findModifiers( + m => m instanceof BerryModifier && (m as BerryModifier).pokemonId === target.id, + target.isPlayer(), + ) as BerryModifier[]; } reduceBerryModifier(target: Pokemon) { @@ -3088,7 +3273,6 @@ export class EatBerryAttr extends MoveEffectAttr { globalScene.updateModifiers(target.isPlayer()); } - /** * Internal function to apply berry effects. * @@ -3097,11 +3281,11 @@ export class EatBerryAttr extends MoveEffectAttr { * @param updateHarvest - Whether to prevent harvest from tracking berries; * defaults to whether `consumer` equals `berryOwner` (i.e. consuming own berry). */ - protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { - // consumer eats berry, owner triggers unburden and similar effects + protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { + // consumer eats berry, owner triggers unburden and similar effects getBerryEffectFunc(this.chosenBerry.berryType)(consumer); - applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner}); - applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer}); + applyAbAttrs("PostItemLostAbAttr", { pokemon: berryOwner }); + applyAbAttrs("HealFromBerryUseAbAttr", { pokemon: consumer }); consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest); } } @@ -3123,10 +3307,10 @@ export class StealEatBerryAttr extends EatBerryAttr { * @param args N/A * @returns `true` if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { // check for abilities that block item theft const cancelled = new BooleanHolder(false); - applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled}); + applyAbAttrs("BlockItemTheftAbAttr", { pokemon: target, cancelled }); if (cancelled.value === true) { return false; } @@ -3140,8 +3324,12 @@ export class StealEatBerryAttr extends EatBerryAttr { // pick a random berry and eat it this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; - applyAbAttrs("PostItemLostAbAttr", {pokemon: target}); - const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); + applyAbAttrs("PostItemLostAbAttr", { pokemon: target }); + const message = i18next.t("battle:stealEatBerry", { + pokemonName: user.name, + targetName: target.name, + berryName: this.chosenBerry.type.name, + }); globalScene.phaseManager.queueMessage(message); this.reduceBerryModifier(target); this.eatBerry(user, target); @@ -3179,13 +3367,19 @@ export class HealStatusEffectAttr extends MoveEffectAttr { // Special edge case for shield dust blocking Sparkling Aria curing burn const moveTargets = getMoveTargets(user, move.id); - if (target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) { + if ( + target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") + && move.id === MoveId.SPARKLING_ARIA + && moveTargets.targets.length === 1 + ) { return false; } const pokemon = this.selfTarget ? user : target; if (pokemon.status && this.effects.includes(pokemon.status.effect)) { - globalScene.phaseManager.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), + ); pokemon.resetStatus(); pokemon.updateInfo(); @@ -3199,7 +3393,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { return this.effects.includes(effect); } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { return user.status ? 10 : 0; } } @@ -3211,13 +3405,13 @@ export class HealStatusEffectAttr extends MoveEffectAttr { */ // TODO: Give this `userSleptOrComatoseCondition` by default export class BypassSleepAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: [BooleanHolder, ...any[]]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: [BooleanHolder, ...any[]]): boolean { const bypassSleep = args[0]; if (bypassSleep.value) { return false; } bypassSleep.value = true; - return true + return true; } /** @@ -3226,7 +3420,7 @@ export class BypassSleepAttr extends MoveAttr { * @param target * @param move */ - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { return user.status?.effect === StatusEffect.SLEEP ? 200 : -10; } } @@ -3243,7 +3437,7 @@ export class BypassBurnDamageReductionAttr extends MoveAttr { * @param args [0] {@linkcode BooleanHolder} for burnDamageReductionCancelled * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as BooleanHolder).value = true; return true; @@ -3251,7 +3445,7 @@ export class BypassBurnDamageReductionAttr extends MoveAttr { } export class WeatherChangeAttr extends MoveEffectAttr { - private weatherType: WeatherType; + private readonly weatherType: WeatherType; constructor(weatherType: WeatherType) { super(); @@ -3259,17 +3453,19 @@ export class WeatherChangeAttr extends MoveEffectAttr { this.weatherType = weatherType; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return globalScene.arena.trySetWeather(this.weatherType, user); } getCondition(): MoveConditionFunc { - return (user, target, move) => !globalScene.arena.weather || (globalScene.arena.weather.weatherType !== this.weatherType && !globalScene.arena.weather.isImmutable()); + return (_user, _target, _move) => + !globalScene.arena.weather + || (globalScene.arena.weather.weatherType !== this.weatherType && !globalScene.arena.weather.isImmutable()); } } export class ClearWeatherAttr extends MoveEffectAttr { - private weatherType: WeatherType; + private readonly weatherType: WeatherType; constructor(weatherType: WeatherType) { super(); @@ -3277,7 +3473,7 @@ export class ClearWeatherAttr extends MoveEffectAttr { this.weatherType = weatherType; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { if (globalScene.arena.weather?.weatherType === this.weatherType) { return globalScene.arena.trySetWeather(WeatherType.NONE, user); } @@ -3287,7 +3483,7 @@ export class ClearWeatherAttr extends MoveEffectAttr { } export class TerrainChangeAttr extends MoveEffectAttr { - private terrainType: TerrainType; + private readonly terrainType: TerrainType; constructor(terrainType: TerrainType) { super(); @@ -3295,32 +3491,29 @@ export class TerrainChangeAttr extends MoveEffectAttr { this.terrainType = terrainType; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return globalScene.arena.trySetTerrain(this.terrainType, true, user); } getCondition(): MoveConditionFunc { - return (user, target, move) => !globalScene.arena.terrain || (globalScene.arena.terrain.terrainType !== this.terrainType); + return (_user, _target, _move) => + !globalScene.arena.terrain || globalScene.arena.terrain.terrainType !== this.terrainType; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { // TODO: Expand on this return globalScene.arena.terrain ? 0 : 6; } } export class ClearTerrainAttr extends MoveEffectAttr { - constructor() { - super(); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return globalScene.arena.trySetTerrain(TerrainType.NONE, true, user); } } export class OneHitKOAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { if (target.isBossImmune()) { return false; } @@ -3331,9 +3524,9 @@ export class OneHitKOAttr extends MoveAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => { + return (user, target, _move) => { const cancelled = new BooleanHolder(false); - applyAbAttrs("BlockOneHitKOAbAttr", {pokemon: target, cancelled}); + applyAbAttrs("BlockOneHitKOAbAttr", { pokemon: target, cancelled }); return !cancelled.value && user.level >= target.level; }; } @@ -3361,7 +3554,7 @@ export class InstantChargeAttr extends MoveAttr { * - `[0]` a {@linkcode BooleanHolder | BooleanHolder} for the "instant charge" flag * @returns `true` if the instant charge condition is met; `false` otherwise. */ - override apply(user: Pokemon, target: Pokemon | null, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon | null, move: Move, args: any[]): boolean { const instantCharge = args[0]; if (!(instantCharge instanceof BooleanHolder)) { return false; @@ -3381,15 +3574,13 @@ export class InstantChargeAttr extends MoveAttr { */ export class WeatherInstantChargeAttr extends InstantChargeAttr { constructor(weatherTypes: WeatherType[]) { - super((user, move) => { + super((_user, _move) => { const currentWeather = globalScene.arena.weather; if (currentWeather?.weatherType == null) { return false; - } else { - return !currentWeather?.isEffectSuppressed() - && weatherTypes.includes(currentWeather?.weatherType); } + return !currentWeather?.isEffectSuppressed() && weatherTypes.includes(currentWeather?.weatherType); }); } } @@ -3401,7 +3592,7 @@ abstract class OverrideMoveEffectAttr extends MoveAttr { /** This field does not exist at runtime and must not be used. * Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called. */ - declare private _: never; + private declare _: never; /** * Apply the move attribute to override other effects of this move. * @param user - The {@linkcode Pokemon} using the move @@ -3412,7 +3603,12 @@ abstract class OverrideMoveEffectAttr extends MoveAttr { * `[1]`: The {@linkcode MoveUseMode} dictating how this move was used. * @returns `true` if the move effect was successfully overridden. */ - public override apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { + public override apply( + _user: Pokemon, + _target: Pokemon, + _move: Move, + _args: [overridden: BooleanHolder, useMode: MoveUseMode], + ): boolean { return true; } } @@ -3443,7 +3639,12 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { this.chargeKey = chargeKey; } - public override apply(user: Pokemon, target: Pokemon, move: Move, args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { + public override apply( + user: Pokemon, + target: Pokemon, + move: Move, + args: [overridden: BooleanHolder, useMode: MoveUseMode], + ): boolean { const useMode = args[1]; if (useMode === MoveUseMode.DELAYED_ATTACK) { // don't trigger if already queueing an indirect attack @@ -3456,28 +3657,30 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { // Display the move animation to foresee an attack globalScene.phaseManager.unshiftNew("MoveAnimPhase", new MoveChargeAnim(this.chargeAnim, move.id, user)); - globalScene.phaseManager.queueMessage( - i18next.t( - this.chargeKey, - { pokemonName: getPokemonNameWithAffix(user) } - ) - ) + globalScene.phaseManager.queueMessage(i18next.t(this.chargeKey, { pokemonName: getPokemonNameWithAffix(user) })); - user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn}) + user.pushMoveHistory({ + move: move.id, + targets: [target.getBattlerIndex()], + result: MoveResult.OTHER, + useMode, + turn: globalScene.currentBattle.turn, + }); // Queue up an attack on the given slot globalScene.arena.positionalTagManager.addTag({ tagType: PositionalTagType.DELAYED_ATTACK, sourceId: user.id, targetIndex: target.getBattlerIndex(), sourceMove: move.id, - turnCount: 3 - }) + turnCount: 3, + }); return true; } public override getCondition(): MoveConditionFunc { // Check the arena if another similar attack is active and affecting the same slot - return (_user, target) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.DELAYED_ATTACK, target.getBattlerIndex()) + return (_user, target) => + globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.DELAYED_ATTACK, target.getBattlerIndex()); } } @@ -3500,7 +3703,8 @@ export class WishAttr extends MoveEffectAttr { public override getCondition(): MoveConditionFunc { // Check the arena if another similar move is active and affecting the same slot - return (_user, target) => globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.WISH, target.getBattlerIndex()) + return (_user, target) => + globalScene.arena.positionalTagManager.canAddTag(PositionalTagType.WISH, target.getBattlerIndex()); } } @@ -3523,7 +3727,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { * effects should be overridden this turn. * @returns `true` if base move effects were overridden; `false` otherwise */ - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { if (user.turnData.combiningPledge) { // "The two moves have become one!\nIt's a combined move!" globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:combiningPledge")); @@ -3532,17 +3736,21 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { const overridden = args[0] as BooleanHolder; - const allyMovePhase = globalScene.phaseManager.getMovePhase((phase) => phase.pokemon.isPlayer() === user.isPlayer()); + const allyMovePhase = globalScene.phaseManager.getMovePhase(phase => phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { const allyMove = allyMovePhase.move.getMove(); if (allyMove !== move && allyMove.hasAttr("AwaitCombinedPledgeAttr")) { - [ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id); + for (const p of [user, allyMovePhase.pokemon]) { + p.turnData.combiningPledge = move.id; + } // "{userPokemonName} is waiting for {allyPokemonName}'s move..." - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:awaitingPledge", { - userPokemonName: getPokemonNameWithAffix(user), - allyPokemonName: getPokemonNameWithAffix(allyMovePhase.pokemon) - })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:awaitingPledge", { + userPokemonName: getPokemonNameWithAffix(user), + allyPokemonName: getPokemonNameWithAffix(allyMovePhase.pokemon), + }), + ); // Move the ally's MovePhase (if needed) so that the ally moves next globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === user.getAlly()); @@ -3561,9 +3769,9 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { */ interface StatStageChangeAttrOptions extends MoveEffectAttrOptions { /** If defined, needs to be met in order for the stat change to apply */ - condition?: MoveConditionFunc, + condition?: MoveConditionFunc; /** `true` to display a message */ - showMessage?: boolean + showMessage?: boolean; } /** @@ -3594,7 +3802,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { * The condition required for the stat stage change to apply. * Defaults to `null` (i.e. no condition required). */ - private get condition () { + private get condition() { return this.options?.condition ?? null; } @@ -3602,7 +3810,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { * `true` to display a message for the stat change. * @defaultValue `true` */ - private get showMessage () { + private get showMessage() { return this.options?.showMessage ?? true; } @@ -3622,7 +3830,14 @@ export class StatStageChangeAttr extends MoveEffectAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); if (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) { const stages = this.getLevels(user); - globalScene.phaseManager.unshiftNew("StatStageChangePhase", (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage); + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + (this.selfTarget ? user : target).getBattlerIndex(), + this.selfTarget, + this.stats, + stages, + this.showMessage, + ); return true; } @@ -3633,7 +3848,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { return this.stages; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(user: Pokemon, target: Pokemon, _move: Move): number { let ret = 0; const moveLevels = this.getLevels(user); for (const stat of this.stats) { @@ -3648,29 +3863,41 @@ export class StatStageChangeAttr extends MoveEffectAttr { switch (stat) { case Stat.ATK: if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); + noEffect = !user.getMoveset().find(m => { + const mv = m.getMove(); + return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL; + }); } break; case Stat.DEF: if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); + noEffect = !user.getMoveset().find(m => { + const mv = m.getMove(); + return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL; + }); } break; case Stat.SPATK: if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); + noEffect = !user.getMoveset().find(m => { + const mv = m.getMove(); + return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL; + }); } break; case Stat.SPDEF: if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} ); + noEffect = !user.getMoveset().find(m => { + const mv = m.getMove(); + return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL; + }); } break; } if (noEffect) { continue; } - ret += (levels * 4) + (levels > 0 ? -2 : 2); + ret += levels * 4 + (levels > 0 ? -2 : 2); } return ret; } @@ -3718,18 +3945,18 @@ export class SecretPowerAttr extends MoveEffectAttr { private determineTerrainEffect(terrain: TerrainType): MoveEffectAttr { let secondaryEffect: MoveEffectAttr; switch (terrain) { - case TerrainType.ELECTRIC: - default: - secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); - break; case TerrainType.MISTY: - secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.SPATK], -1, false); break; case TerrainType.GRASSY: secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); break; case TerrainType.PSYCHIC: - secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.SPD], -1, false); + break; + case TerrainType.ELECTRIC: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); break; } return secondaryEffect; @@ -3767,7 +3994,7 @@ export class SecretPowerAttr extends MoveEffectAttr { case BiomeId.MOUNTAIN: case BiomeId.TEMPLE: case BiomeId.RUINS: - secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.SPD], -1, false); break; case BiomeId.ICE_CAVE: case BiomeId.SNOWY_FOREST: @@ -3777,19 +4004,19 @@ export class SecretPowerAttr extends MoveEffectAttr { secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false); break; case BiomeId.FAIRY_CAVE: - secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.SPATK], -1, false); break; case BiomeId.DESERT: case BiomeId.CONSTRUCTION_SITE: case BiomeId.BEACH: case BiomeId.ISLAND: case BiomeId.BADLANDS: - secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.ACC], -1, false); break; case BiomeId.SEA: case BiomeId.LAKE: case BiomeId.SEABED: - secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.ATK], -1, false); break; case BiomeId.CAVE: case BiomeId.WASTELAND: @@ -3799,7 +4026,7 @@ export class SecretPowerAttr extends MoveEffectAttr { secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true); break; case BiomeId.END: - secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false); + secondaryEffect = new StatStageChangeAttr([Stat.DEF], -1, false); break; case BiomeId.TOWN: case BiomeId.METROPOLIS: @@ -3817,12 +4044,19 @@ export class SecretPowerAttr extends MoveEffectAttr { } export class PostVictoryStatStageChangeAttr extends MoveAttr { - private stats: BattleStat[]; - private stages: number; - private condition?: MoveConditionFunc; - private showMessage: boolean; + private readonly stats: BattleStat[]; + private readonly stages: number; + private readonly condition?: MoveConditionFunc; + private readonly showMessage: boolean; - constructor(stats: BattleStat[], stages: number, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) { + constructor( + stats: BattleStat[], + stages: number, + _selfTarget?: boolean, + condition?: MoveConditionFunc, + showMessage = true, + _firstHitOnly = false, + ) { super(); this.stats = stats; this.stages = stages; @@ -3839,15 +4073,17 @@ export class PostVictoryStatStageChangeAttr extends MoveAttr { } export class AcupressureStatStageChangeAttr extends MoveEffectAttr { - constructor() { - super(); - } - - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const randStats = BATTLE_STATS.filter((s) => target.getStatStage(s) < 6); + override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { + const randStats = BATTLE_STATS.filter(s => target.getStatStage(s) < 6); if (randStats.length > 0) { - const boostStat = [ randStats[user.randBattleSeedInt(randStats.length)] ]; - globalScene.phaseManager.unshiftNew("StatStageChangePhase", target.getBattlerIndex(), this.selfTarget, boostStat, 2); + const boostStat = [randStats[user.randBattleSeedInt(randStats.length)]]; + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + target.getBattlerIndex(), + this.selfTarget, + boostStat, + 2, + ); return true; } return false; @@ -3856,10 +4092,10 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr { export class GrowthStatStageChangeAttr extends StatStageChangeAttr { constructor() { - super([ Stat.ATK, Stat.SPATK ], 1, true); + super([Stat.ATK, Stat.SPATK], 1, true); } - getLevels(user: Pokemon): number { + getLevels(_user: Pokemon): number { if (!globalScene.arena.weather?.isEffectSuppressed()) { const weatherType = globalScene.arena.weather?.weatherType; if (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN) { @@ -3871,10 +4107,15 @@ export class GrowthStatStageChangeAttr extends StatStageChangeAttr { } export class CutHpStatStageBoostAttr extends StatStageChangeAttr { - private cutRatio: number; - private messageCallback: ((user: Pokemon) => void) | undefined; + private readonly cutRatio: number; + private readonly messageCallback: ((user: Pokemon) => void) | undefined; - constructor(stat: BattleStat[], levels: number, cutRatio: number, messageCallback?: ((user: Pokemon) => void) | undefined) { + constructor( + stat: BattleStat[], + levels: number, + cutRatio: number, + messageCallback?: ((user: Pokemon) => void) | undefined, + ) { super(stat, levels, true); this.cutRatio = cutRatio; @@ -3905,7 +4146,7 @@ export class OrderUpStatBoostAttr extends MoveEffectAttr { super(true); } - override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, _move: Move, _args?: any[]): boolean { const commandedTag = user.getTag(CommandedTag); if (!commandedTag) { return false; @@ -3924,7 +4165,13 @@ export class OrderUpStatBoostAttr extends MoveEffectAttr { break; } - globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1); + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + user.getBattlerIndex(), + this.selfTarget, + [increasedStat], + 1, + ); return true; } } @@ -3947,7 +4194,12 @@ export class CopyStatsAttr extends MoveEffectAttr { } target.updateInfo(); user.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedStatChanges", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:copiedStatChanges", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + }), + ); return true; } @@ -3966,14 +4218,16 @@ export class InvertStatsAttr extends MoveEffectAttr { target.updateInfo(); user.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) }), + ); return true; } } export class ResetStatsAttr extends MoveEffectAttr { - private targetAllPokemon: boolean; + private readonly targetAllPokemon: boolean; constructor(targetAllPokemon: boolean) { super(); this.targetAllPokemon = targetAllPokemon; @@ -3983,11 +4237,16 @@ export class ResetStatsAttr extends MoveEffectAttr { if (this.targetAllPokemon) { // Target all pokemon on the field when Freezy Frost or Haze are used const activePokemon = globalScene.getField(true); - activePokemon.forEach((p) => this.resetStats(p)); + for (const p of activePokemon) { + this.resetStats(p); + } globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:statEliminated")); - } else { // Affects only the single target when Clear Smog is used + } else { + // Affects only the single target when Clear Smog is used this.resetStats(target); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }), + ); } return true; } @@ -4006,7 +4265,7 @@ export class ResetStatsAttr extends MoveEffectAttr { */ export class SwapStatStagesAttr extends MoveEffectAttr { /** The stat stages to be swapped between the user and the target */ - private stats: readonly BattleStat[]; + private readonly stats: readonly BattleStat[]; constructor(stats: readonly BattleStat[]) { super(); @@ -4023,7 +4282,7 @@ export class SwapStatStagesAttr extends MoveEffectAttr { * @param args N/A * @returns true if attribute application succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any []): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (super.apply(user, target, move, args)) { for (const s of this.stats) { const temp = user.getStatStage(s); @@ -4035,13 +4294,17 @@ export class SwapStatStagesAttr extends MoveEffectAttr { user.updateInfo(); if (this.stats.length === 7) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) }), + ); } else if (this.stats.length === 2) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", { - pokemonName: getPokemonNameWithAffix(user), - firstStat: i18next.t(getStatKey(this.stats[0])), - secondStat: i18next.t(getStatKey(this.stats[1])) - })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:switchedTwoStatChanges", { + pokemonName: getPokemonNameWithAffix(user), + firstStat: i18next.t(getStatKey(this.stats[0])), + secondStat: i18next.t(getStatKey(this.stats[1])), + }), + ); } return true; } @@ -4056,7 +4319,7 @@ export class HpSplitAttr extends MoveEffectAttr { } const hpValue = Math.floor((target.hp + user.hp) / 2); - [ user, target ].forEach((p) => { + [user, target].forEach(p => { if (p.hp < hpValue) { const healing = p.heal(hpValue - p.hp); if (healing) { @@ -4076,7 +4339,7 @@ export class HpSplitAttr extends MoveEffectAttr { } export class VariablePowerAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { //const power = args[0] as Utils.NumberHolder; return false; } @@ -4091,9 +4354,9 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { * @param args [0] {@linkcode NumberHolder} of power * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const ppMax = move.pp; - const ppUsed = user.moveset.find((m) => m.moveId === move.id)?.ppUsed ?? 0; + const ppUsed = user.moveset.find(m => m.moveId === move.id)?.ppUsed ?? 0; let ppRemains = ppMax - ppUsed; /** Reduce to 0 to avoid negative numbers if user has 1PP before attack and target has Ability.PRESSURE */ @@ -4125,7 +4388,7 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { } export class MovePowerMultiplierAttr extends VariablePowerAttr { - private powerMultiplierFunc: (user: Pokemon, target: Pokemon, move: Move) => number; + private readonly powerMultiplierFunc: (user: Pokemon, target: Pokemon, move: Move) => number; constructor(powerMultiplier: (user: Pokemon, target: Pokemon, move: Move) => number) { super(); @@ -4158,13 +4421,12 @@ const beatUpFunc = (user: Pokemon, allyIndex: number): number => { if (pokemon.id !== user.id && pokemon?.status && pokemon.status.effect !== StatusEffect.NONE) { continue; } - return (pokemon.species.getBaseStat(Stat.ATK) / 10) + 5; + return pokemon.species.getBaseStat(Stat.ATK) / 10 + 5; } return 0; }; export class BeatUpAttr extends VariablePowerAttr { - /** * Gets the next party member to contribute to a Beat Up hit, and calculates the base power for it. * @param user Pokemon that used the move @@ -4173,7 +4435,7 @@ export class BeatUpAttr extends VariablePowerAttr { * @param args N/A * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); @@ -4193,28 +4455,36 @@ export class BeatUpAttr extends VariablePowerAttr { * than using a seed offset */ function doublePowerChanceMessageFunc(chance: number) { - return (user: Pokemon, target: Pokemon, move: Move) => { - let message: string = ""; - globalScene.executeWithSeedOffset(() => { - const rand = randSeedInt(100); - if (rand < chance) { - message = i18next.t("moveTriggers:goingAllOutForAttack", { pokemonName: getPokemonNameWithAffix(user) }); - } - }, globalScene.currentBattle.turn << 6, globalScene.waveSeed); + return (user: Pokemon, _target: Pokemon, _move: Move) => { + let message = ""; + globalScene.executeWithSeedOffset( + () => { + const rand = randSeedInt(100); + if (rand < chance) { + message = i18next.t("moveTriggers:goingAllOutForAttack", { pokemonName: getPokemonNameWithAffix(user) }); + } + }, + globalScene.currentBattle.turn << 6, + globalScene.waveSeed, + ); return message; }; } export class DoublePowerChanceAttr extends VariablePowerAttr { - private chance: number; + private readonly chance: number; constructor(chance: number) { - super(false) - this.chance = chance + super(false); + this.chance = chance; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { let rand = 0; - globalScene.executeWithSeedOffset(() => rand = randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); + globalScene.executeWithSeedOffset( + () => (rand = randSeedInt(100)), + globalScene.currentBattle.turn << 6, + globalScene.waveSeed, + ); if (rand < this.chance) { const power = args[0] as NumberHolder; power.value *= 2; @@ -4227,20 +4497,18 @@ export class DoublePowerChanceAttr extends VariablePowerAttr { export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultiplierAttr { constructor(limit: number, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: MoveId[]) { - super((user: Pokemon, target: Pokemon, move: Move): number => { + super((user: Pokemon, _target: Pokemon, move: Move): number => { const moveHistory = user.getLastXMoves(limit + 1).slice(1); let count = 0; let turnMove: TurnMove | undefined; while ( - ( - (turnMove = moveHistory.shift())?.move === move.id - || (comboMoves.length && comboMoves.includes(turnMove?.move ?? MoveId.NONE)) - ) + ((turnMove = moveHistory.shift())?.move === move.id + || (comboMoves.length > 0 && comboMoves.includes(turnMove?.move ?? MoveId.NONE))) && (!resetOnFail || turnMove?.result === MoveResult.SUCCESS) ) { - if (count < (limit - 1)) { + if (count < limit - 1) { count++; } else if (resetOnLimit) { count = 0; @@ -4264,16 +4532,16 @@ export class ConsecutiveUseDoublePowerAttr extends ConsecutiveUsePowerMultiplier export class ConsecutiveUseMultiBasePowerAttr extends ConsecutiveUsePowerMultiplierAttr { getMultiplier(count: number): number { - return (count + 1); + return count + 1; } } export class WeightPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; const targetWeight = target.getWeight(); - const weightThresholds = [ 10, 25, 50, 100, 200 ]; + const weightThresholds = [10, 25, 50, 100, 200]; let w = 0; while (targetWeight >= weightThresholds[w]) { @@ -4290,7 +4558,7 @@ export class WeightPowerAttr extends VariablePowerAttr { /** * Attribute used for Electro Ball move. - **/ + */ export class ElectroBallPowerAttr extends VariablePowerAttr { /** * Move that deals more damage the faster {@linkcode Stat.SPD} @@ -4301,12 +4569,12 @@ export class ElectroBallPowerAttr extends VariablePowerAttr { * @param args N/A * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; const statRatio = target.getEffectiveStat(Stat.SPD) / user.getEffectiveStat(Stat.SPD); - const statThresholds = [ 0.25, 1 / 3, 0.5, 1, -1 ]; - const statThresholdPowers = [ 150, 120, 80, 60, 40 ]; + const statThresholds = [0.25, 1 / 3, 0.5, 1, -1]; + const statThresholdPowers = [150, 120, 80, 60, 40]; let w = 0; while (w < statThresholds.length - 1 && statRatio > statThresholds[w]) { @@ -4320,10 +4588,9 @@ export class ElectroBallPowerAttr extends VariablePowerAttr { } } - /** * Attribute used for Gyro Ball move. - **/ + */ export class GyroBallPowerAttr extends VariablePowerAttr { /** * Move that deals more damage the slower {@linkcode Stat.SPD} @@ -4334,7 +4601,7 @@ export class GyroBallPowerAttr extends VariablePowerAttr { * @param args N/A * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; const userSpeed = user.getEffectiveStat(Stat.SPD); if (userSpeed < 1) { @@ -4343,30 +4610,30 @@ export class GyroBallPowerAttr extends VariablePowerAttr { return true; } - power.value = Math.floor(Math.min(150, 25 * target.getEffectiveStat(Stat.SPD) / userSpeed + 1)); + power.value = Math.floor(Math.min(150, (25 * target.getEffectiveStat(Stat.SPD)) / userSpeed + 1)); return true; } } export class LowHpPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; const hpRatio = user.getHpRatio(); switch (true) { - case (hpRatio < 0.0417): + case hpRatio < 0.0417: power.value = 200; break; - case (hpRatio < 0.1042): + case hpRatio < 0.1042: power.value = 150; break; - case (hpRatio < 0.2083): + case hpRatio < 0.2083: power.value = 100; break; - case (hpRatio < 0.3542): + case hpRatio < 0.3542: power.value = 80; break; - case (hpRatio < 0.6875): + case hpRatio < 0.6875: power.value = 40; break; default: @@ -4379,7 +4646,7 @@ export class LowHpPowerAttr extends VariablePowerAttr { } export class CompareWeightPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; const userWeight = user.getWeight(); const targetWeight = target.getWeight(); @@ -4391,16 +4658,16 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { const relativeWeight = (targetWeight / userWeight) * 100; switch (true) { - case (relativeWeight < 20.01): + case relativeWeight < 20.01: power.value = 120; break; - case (relativeWeight < 25.01): + case relativeWeight < 25.01: power.value = 100; break; - case (relativeWeight < 33.35): + case relativeWeight < 33.35: power.value = 80; break; - case (relativeWeight < 50.01): + case relativeWeight < 50.01: power.value = 60; break; default: @@ -4413,7 +4680,7 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { } export class HpPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = toDmgValue(150 * user.getHpRatio()); return true; @@ -4441,7 +4708,7 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr { * @param args holds the base power of the move at args[0] * @returns true */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = toDmgValue(this.maxBasePower * target.getHpRatio()); return true; @@ -4449,7 +4716,7 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr { } export class TurnDamagedDoublePowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { if (user.turnData.attacksReceived.find(r => r.damage && r.sourceId === target.id)) { (args[0] as NumberHolder).value *= 2; return true; @@ -4459,35 +4726,43 @@ export class TurnDamagedDoublePowerAttr extends VariablePowerAttr { } } -const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { +const magnitudeMessageFunc = (_user: Pokemon, _target: Pokemon, _move: Move) => { let message: string; - globalScene.executeWithSeedOffset(() => { - const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ]; + globalScene.executeWithSeedOffset( + () => { + const magnitudeThresholds = [5, 15, 35, 65, 75, 95]; - const rand = randSeedInt(100); + const rand = randSeedInt(100); - let m = 0; - for (; m < magnitudeThresholds.length; m++) { - if (rand < magnitudeThresholds[m]) { - break; + let m = 0; + for (; m < magnitudeThresholds.length; m++) { + if (rand < magnitudeThresholds[m]) { + break; + } } - } - message = i18next.t("moveTriggers:magnitudeMessage", { magnitude: m + 4 }); - }, globalScene.currentBattle.turn << 6, globalScene.waveSeed); + message = i18next.t("moveTriggers:magnitudeMessage", { magnitude: m + 4 }); + }, + globalScene.currentBattle.turn << 6, + globalScene.waveSeed, + ); return message!; }; export class MagnitudePowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; - const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ]; - const magnitudePowers = [ 10, 30, 50, 70, 90, 100, 110, 150 ]; + const magnitudeThresholds = [5, 15, 35, 65, 75, 95]; + const magnitudePowers = [10, 30, 50, 70, 90, 100, 110, 150]; let rand: number; - globalScene.executeWithSeedOffset(() => rand = randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); + globalScene.executeWithSeedOffset( + () => (rand = randSeedInt(100)), + globalScene.currentBattle.turn << 6, + globalScene.waveSeed, + ); let m = 0; for (; m < magnitudeThresholds.length; m++) { @@ -4503,7 +4778,7 @@ export class MagnitudePowerAttr extends VariablePowerAttr { } export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { const power = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; @@ -4524,7 +4799,7 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { } export class FriendshipPowerAttr extends VariablePowerAttr { - private invert: boolean; + private readonly invert: boolean; constructor(invert?: boolean) { super(); @@ -4532,11 +4807,13 @@ export class FriendshipPowerAttr extends VariablePowerAttr { this.invert = !!invert; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; - const friendshipPower = Math.floor(Math.min(user.isPlayer() ? user.friendship : user.species.baseFriendship, 255) / 2.5); - power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1); + const friendshipPower = Math.floor( + Math.min(user.isPlayer() ? user.friendship : user.species.baseFriendship, 255) / 2.5, + ); + power.value = Math.max(this.invert ? 102 - friendshipPower : friendshipPower, 1); return true; } @@ -4548,7 +4825,7 @@ export class FriendshipPowerAttr extends VariablePowerAttr { * Self-inflicted confusion damage and hits taken by a Subsitute are ignored. */ export class RageFistPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { /* Reasons this works correctly: * Confusion calls user.damageAndUpdate() directly (no counter increment), * Substitute hits call user.damageAndUpdate() with a damage value of 0, also causing @@ -4560,7 +4837,6 @@ export class RageFistPowerAttr extends VariablePowerAttr { basePower.value = 50 * (1 + Math.min(hitCount, 6)); return true; } - } /** @@ -4569,14 +4845,13 @@ export class RageFistPowerAttr extends VariablePowerAttr { * @returns the amount of positive stats */ const countPositiveStatStages = (pokemon: Pokemon): number => { - return pokemon.getStatStages().reduce((total, stat) => (stat && stat > 0) ? total + stat : total, 0); + return pokemon.getStatStages().reduce((total, stat) => (stat && stat > 0 ? total + stat : total), 0); }; /** * Attribute that increases power based on the amount of positive stat stage increases. */ export class PositiveStatStagePowerAttr extends VariablePowerAttr { - /** * @param user The pokemon that is being used to calculate the amount of positive stats * @param target N/A @@ -4584,7 +4859,7 @@ export class PositiveStatStagePowerAttr extends VariablePowerAttr { * @param args The argument for VariablePowerAttr, accumulates and sets the amount of power multiplied by stats * @returns Returns true if attribute is applied */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const positiveStatStages: number = countPositiveStatStages(user); (args[0] as NumberHolder).value += positiveStatStages * 20; @@ -4598,33 +4873,33 @@ export class PositiveStatStagePowerAttr extends VariablePowerAttr { * up to a maximum of 200 base power in total. */ export class PunishmentPowerAttr extends VariablePowerAttr { - private PUNISHMENT_MIN_BASE_POWER = 60; - private PUNISHMENT_MAX_BASE_POWER = 200; + private readonly PUNISHMENT_MIN_BASE_POWER = 60; + private readonly PUNISHMENT_MAX_BASE_POWER = 200; /** - * @param user N/A - * @param target The pokemon that the move is being used against, as well as calculating the stats for the min/max base power - * @param move N/A - * @param args The value that is being changed due to VariablePowerAttr - * @returns Returns true if attribute is applied - */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + * @param user N/A + * @param target The pokemon that the move is being used against, as well as calculating the stats for the min/max base power + * @param move N/A + * @param args The value that is being changed due to VariablePowerAttr + * @returns Returns true if attribute is applied + */ + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const positiveStatStages: number = countPositiveStatStages(target); (args[0] as NumberHolder).value = Math.min( this.PUNISHMENT_MAX_BASE_POWER, - this.PUNISHMENT_MIN_BASE_POWER + positiveStatStages * 20 + this.PUNISHMENT_MIN_BASE_POWER + positiveStatStages * 20, ); return true; } } export class PresentPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { /** * If this move is multi-hit, and this attribute is applied to any hit * other than the first, this move cannot result in a heal. */ - const firstHit = (user.turnData.hitCount === user.turnData.hitsLeft); + const firstHit = user.turnData.hitCount === user.turnData.hitsLeft; const powerSeed = randSeedInt(firstHit ? 100 : 80); if (powerSeed <= 40) { @@ -4637,8 +4912,13 @@ export class PresentPowerAttr extends VariablePowerAttr { // If this move is multi-hit, disable all other hits user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; - globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), - toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true); + globalScene.phaseManager.unshiftNew( + "PokemonHealPhase", + target.getBattlerIndex(), + toDmgValue(target.getMaxHp() / 4), + i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), + true, + ); } return true; @@ -4646,8 +4926,12 @@ export class PresentPowerAttr extends VariablePowerAttr { } export class WaterShurikenPowerAttr extends VariablePowerAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (user.species.speciesId === SpeciesId.GRENINJA && user.hasAbility(AbilityId.BATTLE_BOND) && user.formIndex === 2) { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { + if ( + user.species.speciesId === SpeciesId.GRENINJA + && user.hasAbility(AbilityId.BATTLE_BOND) + && user.formIndex === 2 + ) { (args[0] as NumberHolder).value = 20; return true; } @@ -4659,14 +4943,14 @@ export class WaterShurikenPowerAttr extends VariablePowerAttr { * Attribute used to calculate the power of attacks that scale with Stockpile stacks (i.e. Spit Up). */ export class SpitUpPowerAttr extends VariablePowerAttr { - private multiplier: number = 0; + private readonly multiplier: number; constructor(multiplier: number) { super(); this.multiplier = multiplier; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const stockpilingTag = user.getTag(StockpilingTag); if (stockpilingTag && stockpilingTag.stockpiledCount > 0) { @@ -4685,10 +4969,10 @@ export class SpitUpPowerAttr extends VariablePowerAttr { */ export class SwallowHealAttr extends HealAttr { constructor() { - super(1) + super(1); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { const stockpilingTag = user.getTag(StockpilingTag); if (stockpilingTag && stockpilingTag.stockpiledCount > 0) { @@ -4698,9 +4982,10 @@ export class SwallowHealAttr extends HealAttr { if (stockpiled === 1) { healRatio = 0.25; } else if (stockpiled === 2) { - healRatio = 0.50; - } else { // stockpiled >= 3 - healRatio = 1.00; + healRatio = 0.5; + } else { + // stockpiled >= 3 + healRatio = 1.0; } if (healRatio) { @@ -4713,7 +4998,7 @@ export class SwallowHealAttr extends HealAttr { } } -const hasStockpileStacksCondition: MoveConditionFunc = (user) => { +const hasStockpileStacksCondition: MoveConditionFunc = user => { const hasStockpilingTag = user.getTag(StockpilingTag); return !!hasStockpilingTag && hasStockpilingTag.stockpiledCount > 0; }; @@ -4724,7 +5009,7 @@ const hasStockpileStacksCondition: MoveConditionFunc = (user) => { */ export class MultiHitPowerIncrementAttr extends VariablePowerAttr { /** The max number of base power increments allowed for this move */ - private maxHits: number; + private readonly maxHits: number; constructor(maxHits: number) { super(); @@ -4743,11 +5028,11 @@ export class MultiHitPowerIncrementAttr extends VariablePowerAttr { * @param args [0] {@linkcode NumberHolder} for final calculated power of move * @returns true if attribute application succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); const power = args[0] as NumberHolder; - power.value = move.power * (1 + hitsTotal % this.maxHits); + power.value = move.power * (1 + (hitsTotal % this.maxHits)); return true; } @@ -4760,7 +5045,7 @@ export class MultiHitPowerIncrementAttr extends VariablePowerAttr { */ export class LastMoveDoublePowerAttr extends VariablePowerAttr { /** The move that must precede the current move */ - private move: MoveId; + private readonly move: MoveId; constructor(move: MoveId) { super(); @@ -4778,17 +5063,16 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { * @param args [0] {@linkcode NumberHolder} that holds the resulting power of the move * @returns true if attribute application succeeds, false otherwise */ - apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; for (const p of globalScene.phaseManager.dynamicQueueManager.getLastTurnOrder().slice(0, -1).reverse()) { - const [ lastMove ] = p.getLastXMoves(1); + const [lastMove] = p.getLastXMoves(1); if (lastMove.result !== MoveResult.FAIL) { - if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) { + if (lastMove.result === MoveResult.SUCCESS && lastMove.move === this.move) { power.value *= 2; return true; - } else { - break; } + break; } } @@ -4801,7 +5085,7 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { * move from an ally. */ export class CombinedPledgePowerAttr extends VariablePowerAttr { - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const power = args[0]; if (!(power instanceof NumberHolder)) { return false; @@ -4820,7 +5104,7 @@ export class CombinedPledgePowerAttr extends VariablePowerAttr { * Applies STAB to the given Pledge move if the move is part of a combined attack. */ export class CombinedPledgeStabBoostAttr extends MoveAttr { - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const stabMultiplier = args[0]; if (!(stabMultiplier instanceof NumberHolder)) { return false; @@ -4840,7 +5124,7 @@ export class CombinedPledgeStabBoostAttr extends MoveAttr { * Doubles power if another Pokemon has previously selected Round this turn. */ export class RoundPowerAttr extends VariablePowerAttr { - override apply(user: Pokemon, target: Pokemon, move: Move, args: [NumberHolder]): boolean { + override apply(user: Pokemon, _target: Pokemon, _move: Move, args: [NumberHolder]): boolean { const power = args[0]; if (user.turnData.joinedRound) { @@ -4861,7 +5145,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { super(true, { lastHitOnly: true }); } - override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + override apply(_user: Pokemon, _target: Pokemon, _move: Move, _args?: any[]): boolean { const nextRoundPhase = globalScene.phaseManager.getMovePhase(phase => phase.move.moveId === MoveId.ROUND); if (!nextRoundPhase) { @@ -4890,7 +5174,7 @@ export class StatChangeBeforeDmgCalcAttr extends MoveAttr { * * @returns true if stat stages where correctly applied */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return false; } } @@ -4911,7 +5195,7 @@ export class SpectralThiefAttr extends StatChangeBeforeDmgCalcAttr { * * @returns true if stat stages where correctly stolen */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { /** * Copy all positive stat stages to user and reduce copied stat stages on target. */ @@ -4925,76 +5209,67 @@ export class SpectralThiefAttr extends StatChangeBeforeDmgCalcAttr { */ const availableToSteal = Math.min(statStageValueTarget, 6 - statStageValueUser); - globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), this.selfTarget, [ s ], availableToSteal); + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + user.getBattlerIndex(), + this.selfTarget, + [s], + availableToSteal, + ); target.setStatStage(s, statStageValueTarget - availableToSteal); } } target.updateInfo(); user.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:stealPositiveStats", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:stealPositiveStats", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + }), + ); return true; } - } export class VariableAtkAttr extends MoveAttr { - constructor() { - super(); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { //const atk = args[0] as Utils.NumberHolder; return false; } } export class TargetAtkUserAtkAttr extends VariableAtkAttr { - constructor() { - super(); - } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = target.getEffectiveStat(Stat.ATK, target); return true; } } export class DefAtkAttr extends VariableAtkAttr { - constructor() { - super(); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = user.getEffectiveStat(Stat.DEF, target); return true; } } export class VariableDefAttr extends MoveAttr { - constructor() { - super(); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { //const def = args[0] as Utils.NumberHolder; return false; } } export class DefDefAttr extends VariableDefAttr { - constructor() { - super(); - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { (args[0] as NumberHolder).value = target.getEffectiveStat(Stat.DEF, user); return true; } } export class VariableAccuracyAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { //const accuracy = args[0] as Utils.NumberHolder; return false; } @@ -5004,7 +5279,7 @@ export class VariableAccuracyAttr extends MoveAttr { * Attribute used for Thunder and Hurricane that sets accuracy to 50 in sun and never miss in rain */ export class ThunderAccuracyAttr extends VariableAccuracyAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { const accuracy = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; @@ -5030,7 +5305,7 @@ export class ThunderAccuracyAttr extends VariableAccuracyAttr { * Springtide Storm does NOT have this property */ export class StormAccuracyAttr extends VariableAccuracyAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { const accuracy = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; @@ -5059,7 +5334,7 @@ export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr { * @param args [0] Accuracy of the move to be modified * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { if (target.getTag(BattlerTagType.MINIMIZED)) { const accuracy = args[0] as NumberHolder; accuracy.value = -1; @@ -5072,7 +5347,7 @@ export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr { } export class ToxicAccuracyAttr extends VariableAccuracyAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { if (user.isOfType(PokemonType.POISON)) { const accuracy = args[0] as NumberHolder; accuracy.value = -1; @@ -5084,7 +5359,7 @@ export class ToxicAccuracyAttr extends VariableAccuracyAttr { } export class BlizzardAccuracyAttr extends VariableAccuracyAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { if (!globalScene.arena.weather?.isEffectSuppressed()) { const accuracy = args[0] as NumberHolder; const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; @@ -5099,14 +5374,14 @@ export class BlizzardAccuracyAttr extends VariableAccuracyAttr { } export class VariableMoveCategoryAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return false; } } export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as NumberHolder); + const category = args[0] as NumberHolder; if (user.getEffectiveStat(Stat.ATK, target, move) > user.getEffectiveStat(Stat.SPATK, target, move)) { category.value = MoveCategory.PHYSICAL; @@ -5125,10 +5400,13 @@ export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr { // TODO: Remove the `.partial()` tag from Tera Blast and Tera Starstorm when the above issue is resolved export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as NumberHolder); + const category = args[0] as NumberHolder; - if (user.isTerastallized && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) > - user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true)) { + if ( + user.isTerastallized + && user.getEffectiveStat(Stat.ATK, target, move, true, true, false, false, true) + > user.getEffectiveStat(Stat.SPATK, target, move, true, true, false, false, true) + ) { category.value = MoveCategory.PHYSICAL; return true; } @@ -5152,7 +5430,7 @@ export class TeraBlastPowerAttr extends VariablePowerAttr { * previously applied power modifiers. * @returns */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const power = args[0] as NumberHolder; if (user.isTerastallized && user.getTeraType() === PokemonType.STELLAR) { power.value = 100; @@ -5174,8 +5452,8 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { * @param args [0] {@linkcode 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 NumberHolder); + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { + const category = args[0] as NumberHolder; if (user.getAlly() === target) { category.value = MoveCategory.STATUS; @@ -5188,15 +5466,34 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as NumberHolder); + const category = args[0] as NumberHolder; - const predictedPhysDmg = target.getBaseDamage({source: user, move, moveCategory: MoveCategory.PHYSICAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true}); - const predictedSpecDmg = target.getBaseDamage({source: user, move, moveCategory: MoveCategory.SPECIAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true}); + const predictedPhysDmg = target.getBaseDamage({ + source: user, + move, + moveCategory: MoveCategory.PHYSICAL, + ignoreAbility: true, + ignoreSourceAbility: true, + ignoreAllyAbility: true, + ignoreSourceAllyAbility: true, + simulated: true, + }); + const predictedSpecDmg = target.getBaseDamage({ + source: user, + move, + moveCategory: MoveCategory.SPECIAL, + ignoreAbility: true, + ignoreSourceAbility: true, + ignoreAllyAbility: true, + ignoreSourceAllyAbility: true, + simulated: true, + }); if (predictedPhysDmg > predictedSpecDmg) { category.value = MoveCategory.PHYSICAL; return true; - } else if (predictedPhysDmg === predictedSpecDmg && user.randBattleSeedInt(2) === 0) { + } + if (predictedPhysDmg === predictedSpecDmg && user.randBattleSeedInt(2) === 0) { category.value = MoveCategory.PHYSICAL; return true; } @@ -5205,7 +5502,7 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr { } export class VariableMoveTypeAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return false; } @@ -5215,20 +5512,27 @@ export class VariableMoveTypeAttr extends MoveAttr { * @param move - The move being used * @returns An array of types to add to the pool of type-boosting items */ - getTypesForItemSpawn(user: Pokemon, move: Move): PokemonType[] { + getTypesForItemSpawn(_user: Pokemon, move: Move): PokemonType[] { return [move.type]; } } export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } - if ([ user.species.speciesId, user.fusionSpecies?.speciesId ].includes(SpeciesId.ARCEUS) || [ user.species.speciesId, user.fusionSpecies?.speciesId ].includes(SpeciesId.SILVALLY)) { - const form = user.species.speciesId === SpeciesId.ARCEUS || user.species.speciesId === SpeciesId.SILVALLY ? user.formIndex : user.fusionSpecies?.formIndex!; + // TODO: this needs to be cleaned up + if ( + [user.species.speciesId, user.fusionSpecies?.speciesId].includes(SpeciesId.ARCEUS) + || [user.species.speciesId, user.fusionSpecies?.speciesId].includes(SpeciesId.SILVALLY) + ) { + const form = + user.species.speciesId === SpeciesId.ARCEUS || user.species.speciesId === SpeciesId.SILVALLY + ? user.formIndex + : user.fusionSpecies!.formIndex; if (form >= 0 && form <= MAX_POKEMON_TYPE && form !== PokemonType.STELLAR) { moveType.value = form as PokemonType; return true; @@ -5240,7 +5544,7 @@ export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { if (moveType.value === move.type) { return false; } - moveType.value = move.type + moveType.value = move.type; return true; } @@ -5248,19 +5552,19 @@ export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { // Get the type const typeHolder = new NumberHolder(move.type); // Passing user in for target is fine; the parameter is unused anyway - this.apply(user, user, move, [ typeHolder ]); + this.apply(user, user, move, [typeHolder]); return [typeHolder.value]; } } export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } - if ([ user.species.speciesId, user.fusionSpecies?.speciesId ].includes(SpeciesId.GENESECT)) { + if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(SpeciesId.GENESECT)) { const form = user.species.speciesId === SpeciesId.GENESECT ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { @@ -5288,19 +5592,19 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { override getTypesForItemSpawn(user: Pokemon, move: Move): PokemonType[] { const typeHolder = new NumberHolder(move.type); - this.apply(user, user, move, [ typeHolder ]); + this.apply(user, user, move, [typeHolder]); return [typeHolder.value]; } } export class AuraWheelTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } - if ([ user.species.speciesId, user.fusionSpecies?.speciesId ].includes(SpeciesId.MORPEKO)) { + if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(SpeciesId.MORPEKO)) { const form = user.species.speciesId === SpeciesId.MORPEKO ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { @@ -5328,13 +5632,13 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr { } export class RagingBullTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } - if ([ user.species.speciesId, user.fusionSpecies?.speciesId ].includes(SpeciesId.PALDEA_TAUROS)) { + if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(SpeciesId.PALDEA_TAUROS)) { const form = user.species.speciesId === SpeciesId.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { @@ -5356,19 +5660,19 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { override getTypesForItemSpawn(user: Pokemon, move: Move): PokemonType[] { const typeHolder = new NumberHolder(move.type); - this.apply(user, user, move, [ typeHolder ]); - return [ typeHolder.value ]; + this.apply(user, user, move, [typeHolder]); + return [typeHolder.value]; } } export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } - if ([ user.species.speciesId, user.fusionSpecies?.speciesId ].includes(SpeciesId.OGERPON)) { + if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(SpeciesId.OGERPON)) { const form = user.species.speciesId === SpeciesId.OGERPON ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { @@ -5397,13 +5701,13 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { override getTypesForItemSpawn(user: Pokemon, move: Move): PokemonType[] { const typeHolder = new NumberHolder(move.type); - this.apply(user, user, move, [ typeHolder ]); - return [ typeHolder.value ]; + this.apply(user, user, move, [typeHolder]); + return [typeHolder.value]; } } export class WeatherBallTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; @@ -5452,7 +5756,7 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { * @param args [0] {@linkcode NumberHolder} The move's type to be modified * @returns true if the function succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; @@ -5492,31 +5796,48 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { * Changes type based on the user's IVs */ export class HiddenPowerTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } - const iv_val = Math.floor(((user.ivs[Stat.HP] & 1) - + (user.ivs[Stat.ATK] & 1) * 2 - + (user.ivs[Stat.DEF] & 1) * 4 - + (user.ivs[Stat.SPD] & 1) * 8 - + (user.ivs[Stat.SPATK] & 1) * 16 - + (user.ivs[Stat.SPDEF] & 1) * 32) * 15 / 63); + const iv_val = Math.floor( + (((user.ivs[Stat.HP] & 1) + + (user.ivs[Stat.ATK] & 1) * 2 + + (user.ivs[Stat.DEF] & 1) * 4 + + (user.ivs[Stat.SPD] & 1) * 8 + + (user.ivs[Stat.SPATK] & 1) * 16 + + (user.ivs[Stat.SPDEF] & 1) * 32) + * 15) + / 63, + ); moveType.value = [ - PokemonType.FIGHTING, PokemonType.FLYING, PokemonType.POISON, PokemonType.GROUND, - PokemonType.ROCK, PokemonType.BUG, PokemonType.GHOST, PokemonType.STEEL, - PokemonType.FIRE, PokemonType.WATER, PokemonType.GRASS, PokemonType.ELECTRIC, - PokemonType.PSYCHIC, PokemonType.ICE, PokemonType.DRAGON, PokemonType.DARK ][iv_val]; + PokemonType.FIGHTING, + PokemonType.FLYING, + PokemonType.POISON, + PokemonType.GROUND, + PokemonType.ROCK, + PokemonType.BUG, + PokemonType.GHOST, + PokemonType.STEEL, + PokemonType.FIRE, + PokemonType.WATER, + PokemonType.GRASS, + PokemonType.ELECTRIC, + PokemonType.PSYCHIC, + PokemonType.ICE, + PokemonType.DRAGON, + PokemonType.DARK, + ][iv_val]; return true; } override getTypesForItemSpawn(user: Pokemon, move: Move): PokemonType[] { const typeHolder = new NumberHolder(move.type); - this.apply(user, user, move, [ typeHolder ]); + this.apply(user, user, move, [typeHolder]); return [typeHolder.value]; } } @@ -5532,7 +5853,7 @@ export class TeraBlastTypeAttr extends VariableMoveTypeAttr { * @param args `[0]` the move's type to be modified * @returns `true` if the move's type was modified; `false` otherwise */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; @@ -5576,7 +5897,7 @@ export class TeraStarstormTypeAttr extends VariableMoveTypeAttr { * @param args[0] {@linkcode NumberHolder} the move type * @returns `true` if the move type is changed to {@linkcode PokemonType.STELLAR}, `false` otherwise */ - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { if (user.isTerastallized && user.hasSpecies(SpeciesId.TERAPAGOS)) { const moveType = args[0] as NumberHolder; @@ -5588,23 +5909,24 @@ export class TeraStarstormTypeAttr extends VariableMoveTypeAttr { } export class MatchUserTypeAttr extends VariableMoveTypeAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; } const userTypes = user.getTypes(true); - if (userTypes.includes(PokemonType.STELLAR)) { // will not change to stellar type + if (userTypes.includes(PokemonType.STELLAR)) { + // will not change to stellar type const nonTeraTypes = user.getTypes(); moveType.value = nonTeraTypes[0]; return true; - } else if (userTypes.length > 0) { + } + if (userTypes.length > 0) { moveType.value = userTypes[0]; return true; - } else { - return false; } + return false; } override getTypesForItemSpawn(user: Pokemon, move: Move): PokemonType[] { @@ -5619,7 +5941,7 @@ export class MatchUserTypeAttr extends VariableMoveTypeAttr { * Changes the type of a Pledge move based on the Pledge move combined with it. */ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, move: Move, args: any[]): boolean { const moveType = args[0]; if (!(moveType instanceof NumberHolder)) { return false; @@ -5656,13 +5978,13 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { } export class VariableMoveTypeMultiplierAttr extends MoveAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return false; } } export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { if (!target.getTag(BattlerTagType.IGNORE_FLYING)) { const multiplier = args[0] as NumberHolder; //When a flying type is hit, the first hit is always 1x multiplier. @@ -5685,7 +6007,7 @@ export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr { * @param args `[0]` a {@linkcode NumberHolder | NumberHolder} containing a type effectiveness multiplier * @returns `true` if this Ice-type immunity applies; `false` otherwise */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const multiplier = args[0] as NumberHolder; if (target.isOfType(PokemonType.ICE)) { multiplier.value = 0; @@ -5696,7 +6018,7 @@ export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr { } export class FlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const multiplier = args[0] as NumberHolder; multiplier.value *= target.getAttackTypeEffectiveness(PokemonType.FLYING, user); return true; @@ -5716,7 +6038,7 @@ export class VariableMoveTypeChartAttr extends MoveAttr { * * @returns true if application of the attribute succeeds */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { return false; } } @@ -5725,21 +6047,20 @@ export class VariableMoveTypeChartAttr extends MoveAttr { * This class forces Freeze-Dry to be super effective against Water Type. */ export class FreezeDryAttr extends VariableMoveTypeChartAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean { const multiplier = args[0] as NumberHolder; const defType = args[1] as PokemonType; if (defType === PokemonType.WATER) { multiplier.value = 2; return true; - } else { - return false; } + return false; } } export class OneHitKOAccuracyAttr extends VariableAccuracyAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const accuracy = args[0] as NumberHolder; if (user.level < target.level) { accuracy.value = 0; @@ -5761,7 +6082,7 @@ export class SheerColdAccuracyAttr extends OneHitKOAccuracyAttr { * the first if/else, or 30/20 depending on the type of the user Pokemon. * @returns Returns true if move is successful, false if misses. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const accuracy = args[0] as NumberHolder; if (user.level < target.level) { accuracy.value = 0; @@ -5774,7 +6095,7 @@ export class SheerColdAccuracyAttr extends OneHitKOAccuracyAttr { } export class MissEffectAttr extends MoveAttr { - private missEffectFunc: UserMoveConditionFunc; + private readonly missEffectFunc: UserMoveConditionFunc; constructor(missEffectFunc: UserMoveConditionFunc) { super(); @@ -5782,14 +6103,14 @@ export class MissEffectAttr extends MoveAttr { this.missEffectFunc = missEffectFunc; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean { this.missEffectFunc(user, move); return true; } } export class NoEffectAttr extends MoveAttr { - private noEffectFunc: UserMoveConditionFunc; + private readonly noEffectFunc: UserMoveConditionFunc; constructor(noEffectFunc: UserMoveConditionFunc) { super(); @@ -5797,7 +6118,7 @@ export class NoEffectAttr extends MoveAttr { this.noEffectFunc = noEffectFunc; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean { this.noEffectFunc(user, move); return true; } @@ -5806,30 +6127,32 @@ export class NoEffectAttr extends MoveAttr { /** * Function to deal Crash Damage (1/2 max hp) to the user on apply. */ -const crashDamageFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => { +const crashDamageFunc: UserMoveConditionFunc = (user: Pokemon, _move: Move) => { const cancelled = new BooleanHolder(false); - applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled}); + applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled }); if (cancelled.value) { return false; } user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT }); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) }), + ); user.turnData.damageTaken += toDmgValue(user.getMaxHp() / 2); return true; }; -export class TypelessAttr extends MoveAttr { } +export class TypelessAttr extends MoveAttr {} /** -* Attribute used for moves which ignore redirection effects, and always target their original target, i.e. Snipe Shot -* Bypasses Storm Drain, Follow Me, Ally Switch, and the like. -*/ + * Attribute used for moves which ignore redirection effects, and always target their original target, i.e. Snipe Shot + * Bypasses Storm Drain, Follow Me, Ally Switch, and the like. + */ export class BypassRedirectAttr extends MoveAttr { /** `true` if this move only bypasses redirection from Abilities */ public readonly abilitiesOnly: boolean; - constructor(abilitiesOnly: boolean = false) { + constructor(abilitiesOnly = false) { super(); this.abilitiesOnly = abilitiesOnly; } @@ -5840,7 +6163,7 @@ export class FrenzyAttr extends MoveEffectAttr { super(true, { lastHitOnly: true }); } - canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { + canApply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]) { return !(this.selfTarget ? user : target).isFainted(); } @@ -5857,7 +6180,7 @@ export class FrenzyAttr extends MoveEffectAttr { if (!user.getTag(BattlerTagType.FRENZY) && user.getMoveQueue().length === 0) { const turnCount = user.randBattleSeedIntRange(1, 2); // excludes initial use for (let i = 0; i < turnCount; i++) { - user.pushMoveQueue({ move: move.id, targets: [ target.getBattlerIndex() ], useMode: MoveUseMode.IGNORE_PP }); + user.pushMoveQueue({ move: move.id, targets: [target.getBattlerIndex()], useMode: MoveUseMode.IGNORE_PP }); } user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id); } else { @@ -5903,9 +6226,16 @@ export class AddBattlerTagAttr extends MoveEffectAttr { public tagType: BattlerTagType; public turnCountMin: number; public turnCountMax: number; - private failOnOverlap: boolean; + private readonly failOnOverlap: boolean; - constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap = false, turnCountMin = 0, turnCountMax = turnCountMin, lastHitOnly = false) { + constructor( + tagType: BattlerTagType, + selfTarget = false, + failOnOverlap = false, + turnCountMin = 0, + turnCountMax = turnCountMin, + lastHitOnly = false, + ) { super(selfTarget, { lastHitOnly }); this.tagType = tagType; @@ -5922,16 +6252,19 @@ export class AddBattlerTagAttr extends MoveEffectAttr { // TODO: Do any moves actually use chance-based battler tag adding? const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); if (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) { - return (this.selfTarget ? user : target).addTag(this.tagType, user.randBattleSeedIntRange(this.turnCountMin, this.turnCountMax), move.id, user.id); + return (this.selfTarget ? user : target).addTag( + this.tagType, + user.randBattleSeedIntRange(this.turnCountMin, this.turnCountMax), + move.id, + user.id, + ); } return false; } getCondition(): MoveConditionFunc | null { - return this.failOnOverlap - ? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType) - : null; + return this.failOnOverlap ? (user, target, _move) => !(this.selfTarget ? user : target).getTag(this.tagType) : null; } getTagTargetBenefitScore(): number { @@ -6022,7 +6355,9 @@ export class FallDownAttr extends AddBattlerTagAttr { */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!target.isGrounded()) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) }), + ); } return super.apply(user, target, move, args); } @@ -6050,7 +6385,7 @@ export class GulpMissileTagAttr extends MoveEffectAttr { } if (user.hasAbility(AbilityId.GULP_MISSILE) && user.species.speciesId === SpeciesId.CRAMORANT) { - if (user.getHpRatio() >= .5) { + if (user.getHpRatio() >= 0.5) { user.addTag(BattlerTagType.GULP_MISSILE_ARROKUDA, 0, move.id); } else { user.addTag(BattlerTagType.GULP_MISSILE_PIKACHU, 0, move.id); @@ -6061,7 +6396,7 @@ export class GulpMissileTagAttr extends MoveEffectAttr { return false; } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { const isCramorant = user.hasAbility(AbilityId.GULP_MISSILE) && user.species.speciesId === SpeciesId.CRAMORANT; return isCramorant && !user.getTag(GulpMissileTag) ? 10 : 0; } @@ -6091,8 +6426,10 @@ export class JawLockAttr extends AddBattlerTagAttr { * Add the tag to both the user and the target. * The target's tag source is considered to be the user and vice versa */ - return target.addTag(BattlerTagType.TRAPPED, 1, move.id, user.id) - && user.addTag(BattlerTagType.TRAPPED, 1, move.id, target.id); + return ( + target.addTag(BattlerTagType.TRAPPED, 1, move.id, user.id) + && user.addTag(BattlerTagType.TRAPPED, 1, move.id, target.id) + ); } return false; @@ -6100,8 +6437,7 @@ export class JawLockAttr extends AddBattlerTagAttr { } export class CurseAttr extends MoveEffectAttr { - - apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { if (user.getTypes(true).includes(PokemonType.GHOST)) { if (target.getTag(BattlerTagType.CURSED)) { globalScene.phaseManager.queueMessage(i18next.t("battle:attackFailed")); @@ -6112,17 +6448,16 @@ export class CurseAttr extends MoveEffectAttr { globalScene.phaseManager.queueMessage( i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), - pokemonName: getPokemonNameWithAffix(target) - }) + pokemonName: getPokemonNameWithAffix(target), + }), ); target.addTag(BattlerTagType.CURSED, 0, move.id, user.id); return true; - } else { - globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1); - globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [ Stat.SPD ], -1); - return true; } + globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [Stat.ATK, Stat.DEF], 1); + globalScene.phaseManager.unshiftNew("StatStageChangePhase", user.getBattlerIndex(), true, [Stat.SPD], -1); + return true; } } @@ -6164,7 +6499,9 @@ export class ConfuseAttr extends AddBattlerTagAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!this.selfTarget && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }), + ); } return false; } @@ -6191,24 +6528,26 @@ export class ProtectAttr extends AddBattlerTagAttr { } getCondition(): MoveConditionFunc { - return ((user, target, move): boolean => { + return (user, _target, _move): boolean => { let timesUsed = 0; for (const turnMove of user.getLastXMoves(-1).slice()) { if ( // Quick & Wide guard increment the Protect counter without using it for fail chance - !(allMoves[turnMove.move].hasAttr("ProtectAttr") || - [MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) || - turnMove.result !== MoveResult.SUCCESS + !( + allMoves[turnMove.move].hasAttr("ProtectAttr") + || [MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move) + ) + || turnMove.result !== MoveResult.SUCCESS ) { break; } - timesUsed++ + timesUsed++; } return timesUsed === 0 || user.randBattleSeedInt(Math.pow(3, timesUsed)) === 0; - }); + }; } } @@ -6247,7 +6586,7 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr { * Attribute used when a move can deal damage to {@linkcode BattlerTagType} * Moves that always hit but do not deal double damage: Thunder, Fissure, Sky Uppercut, * Smack Down, Hurricane, Thousand Arrows -*/ + */ export class HitsTagAttr extends MoveAttr { /** The {@linkcode BattlerTagType} this move hits */ public tagType: BattlerTagType; @@ -6260,8 +6599,8 @@ export class HitsTagAttr extends MoveAttr { this.tagType = tagType; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { - return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0; + getTargetBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { + return target.getTag(this.tagType) ? (this.doubleDamage ? 10 : 5) : 0; } } @@ -6280,10 +6619,10 @@ export class HitsTagForDoubleDamageAttr extends HitsTagAttr { export class AddArenaTagAttr extends MoveEffectAttr { public tagType: ArenaTagType; public turnCount: number; - private failOnOverlap: boolean; + private readonly failOnOverlap: boolean; public selfSideTarget: boolean; - constructor(tagType: ArenaTagType, turnCount = 0, failOnOverlap = false, selfSideTarget: boolean = false) { + constructor(tagType: ArenaTagType, turnCount = 0, failOnOverlap = false, selfSideTarget = false) { super(true); this.tagType = tagType; @@ -6298,8 +6637,14 @@ export class AddArenaTagAttr extends MoveEffectAttr { } // TODO: Why does this check effect chance if nothing uses it? - if ((move.chance < 0 || move.chance === 100 || user.randBattleSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { - const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr("AddArenaTrapTagAttr") && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if ( + (move.chance < 0 || move.chance === 100 || user.randBattleSeedInt(100) < move.chance) + && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS + ) { + const side = + (this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr("AddArenaTrapTagAttr") && target === user) + ? ArenaTagSide.PLAYER + : ArenaTagSide.ENEMY; globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); return true; } @@ -6309,7 +6654,8 @@ export class AddArenaTagAttr extends MoveEffectAttr { getCondition(): MoveConditionFunc | null { return this.failOnOverlap - ? (_user, target, _move) => !globalScene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY) + ? (_user, target, _move) => + !globalScene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY) : null; } } @@ -6325,9 +6671,13 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr { * the target's side of the field (`false`) * @defaultValue `false` */ - private removeAllTags: boolean + private readonly removeAllTags: boolean; - constructor(tagTypes: readonly [ArenaTagType, ...ArenaTagType[]], removeAllTags = false, options?: MoveEffectAttrOptions) { + constructor( + tagTypes: readonly [ArenaTagType, ...ArenaTagType[]], + removeAllTags = false, + options?: MoveEffectAttrOptions, + ) { super(true, options); this.tagTypes = tagTypes; @@ -6349,8 +6699,8 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr { export class AddArenaTrapTagAttr extends AddArenaTagAttr { getCondition(): MoveConditionFunc { - return (user, target, move) => { - const side = (this.selfSideTarget !== user.isPlayer()) ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER; + return (user, _target, _move) => { + const side = this.selfSideTarget !== user.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER; const tag = globalScene.arena.getTagOnSide(this.tagType, side) as EntryHazardTag; if (!tag) { return true; @@ -6371,11 +6721,14 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { * @param target {@linkcode Pokemon} target of this move * @param move {@linkcode Move} being used */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const tag = globalScene.arena.getTagOnSide(this.tagType, side) as EntryHazardTag; - if ((moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { + if ( + (moveChance < 0 || moveChance === 100 || user.randBattleSeedInt(100) < moveChance) + && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS + ) { globalScene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { return true; @@ -6401,11 +6754,7 @@ export class RemoveArenaTrapAttr extends RemoveArenaTagsAttr { } } -const screenTags = [ - ArenaTagType.REFLECT, - ArenaTagType.LIGHT_SCREEN, - ArenaTagType.AURORA_VEIL -] as const; +const screenTags = [ArenaTagType.REFLECT, ArenaTagType.LIGHT_SCREEN, ArenaTagType.AURORA_VEIL] as const; export class RemoveScreensAttr extends RemoveArenaTagsAttr { constructor(targetBothSides = false) { @@ -6426,24 +6775,43 @@ export class SwapArenaTagsAttr extends MoveEffectAttr { this.validTags = validTags; } - apply(user:Pokemon, target:Pokemon, move:Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) { return false; } - const tagPlayerTemp = globalScene.arena.findTagsOnSide((t => this.validTags.includes(t.tagType)), ArenaTagSide.PLAYER); - const tagEnemyTemp = globalScene.arena.findTagsOnSide((t => this.validTags.includes(t.tagType)), ArenaTagSide.ENEMY); + const tagPlayerTemp = globalScene.arena.findTagsOnSide( + t => this.validTags.includes(t.tagType), + ArenaTagSide.PLAYER, + ); + const tagEnemyTemp = globalScene.arena.findTagsOnSide(t => this.validTags.includes(t.tagType), ArenaTagSide.ENEMY); for (const playerTag of tagPlayerTemp) { globalScene.arena.removeTagOnSide(playerTag.tagType, ArenaTagSide.PLAYER, true); - globalScene.arena.addTag(playerTag.tagType, playerTag.turnCount, playerTag.sourceMove, playerTag.sourceId!, ArenaTagSide.ENEMY, true); // TODO: is the bang correct? + globalScene.arena.addTag( + playerTag.tagType, + playerTag.turnCount, + playerTag.sourceMove, + playerTag.sourceId!, + ArenaTagSide.ENEMY, + true, + ); // TODO: is the bang correct? } for (const enemyTag of tagEnemyTemp) { globalScene.arena.removeTagOnSide(enemyTag.tagType, ArenaTagSide.ENEMY, true); - globalScene.arena.addTag(enemyTag.tagType, enemyTag.turnCount, enemyTag.sourceMove, enemyTag.sourceId!, ArenaTagSide.PLAYER, true); // TODO: is the bang correct? + globalScene.arena.addTag( + enemyTag.tagType, + enemyTag.turnCount, + enemyTag.sourceMove, + enemyTag.sourceId!, + ArenaTagSide.PLAYER, + true, + ); // TODO: is the bang correct? } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:swapArenaTags", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:swapArenaTags", { pokemonName: getPokemonNameWithAffix(user) }), + ); return true; } } @@ -6455,7 +6823,7 @@ export class SwapArenaTagsAttr extends MoveEffectAttr { export class AddPledgeEffectAttr extends AddArenaTagAttr { private readonly requiredPledge: MoveId; - constructor(tagType: ArenaTagType, requiredPledge: MoveId, selfSideTarget: boolean = false) { + constructor(tagType: ArenaTagType, requiredPledge: MoveId, selfSideTarget = false) { super(tagType, 4, false, selfSideTarget); this.requiredPledge = requiredPledge; @@ -6490,35 +6858,53 @@ export class RevivalBlessingAttr extends MoveEffectAttr { * @param args N/A * @returns `true` if function succeeds. */ - override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + override apply(user: Pokemon, _target: Pokemon, _move: Move, _args: any[]): boolean { // If user is player, checks if the user has fainted pokemon if (user.isPlayer()) { globalScene.phaseManager.unshiftNew("RevivalBlessingPhase", user); return true; - } else if (user.isEnemy() && user.hasTrainer() && globalScene.getEnemyParty().findIndex((p) => p.isFainted() && !p.isBoss()) > -1) { + } + if ( + user.isEnemy() + && user.hasTrainer() + && globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1 + ) { // If used by an enemy trainer with at least one fainted non-boss Pokemon, this // revives one of said Pokemon selected at random. - const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss()); + const faintedPokemon = globalScene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss()); const pokemon = faintedPokemon[user.randBattleSeedInt(faintedPokemon.length)]; - const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id); + const slotIndex = globalScene.getEnemyParty().findIndex(p => pokemon.id === p.id); pokemon.resetStatus(true, false, false, true); pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), + 0, + true, + ); const allyPokemon = user.getAlly(); - if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1 && allyPokemon != null) { - // Handle cases where revived pokemon needs to get switched in on same turn - if (allyPokemon.isFainted() || allyPokemon === pokemon) { - // Enemy switch phase should be removed and replaced with the revived pkmn switching in - globalScene.phaseManager.tryRemovePhase("SwitchSummonPhase", phase => phase.getFieldIndex() === slotIndex); - // If the pokemon being revived was alive earlier in the turn, cancel its move - // (revived pokemon can't move in the turn they're brought back) - // TODO: might make sense to move this to `FaintPhase` after checking for Rev Seed (rather than handling it in the move) - globalScene.phaseManager.getMovePhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); - if (user.fieldPosition === FieldPosition.CENTER) { - user.setFieldPosition(FieldPosition.LEFT); - } - globalScene.phaseManager.unshiftNew("SwitchSummonPhase", SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false); + if ( + globalScene.currentBattle.double + && globalScene.getEnemyParty().length > 1 + && allyPokemon != null // Handle cases where revived pokemon needs to get switched in on same turn + && (allyPokemon.isFainted() || allyPokemon === pokemon) + ) { + // Enemy switch phase should be removed and replaced with the revived pkmn switching in + globalScene.phaseManager.tryRemovePhase("SwitchSummonPhase", phase => phase.getFieldIndex() === slotIndex); + // If the pokemon being revived was alive earlier in the turn, cancel its move + // TODO: check if revived pokemon shouldn't be able to move in the same turn they're brought back + // TODO: might make sense to move this to `FaintPhase` after checking for Rev Seed (rather than handling it in the move) + globalScene.phaseManager.getMovePhase((phase: MovePhase) => phase.pokemon === pokemon)?.cancel(); + if (user.fieldPosition === FieldPosition.CENTER) { + user.setFieldPosition(FieldPosition.LEFT); } + globalScene.phaseManager.unshiftNew( + "SwitchSummonPhase", + SwitchType.SWITCH, + allyPokemon.getFieldIndex(), + slotIndex, + false, + false, + ); } return true; } @@ -6526,13 +6912,15 @@ export class RevivalBlessingAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => - user.hasTrainer() && - (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).some((p: Pokemon) => p.isFainted() && !p.isBoss()); + return (user, _target, _move) => + user.hasTrainer() + && (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).some( + (p: Pokemon) => p.isFainted() && !p.isBoss(), + ); } override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { - if (user.hasTrainer() && globalScene.getEnemyParty().some((p) => p.isFainted() && !p.isBoss())) { + if (user.hasTrainer() && globalScene.getEnemyParty().some(p => p.isFainted() && !p.isBoss())) { return 20; } @@ -6541,18 +6929,21 @@ export class RevivalBlessingAttr extends MoveEffectAttr { } export class ForceSwitchOutAttr extends MoveEffectAttr { - constructor( - private selfSwitch: boolean = false, - private switchType: SwitchType = SwitchType.SWITCH - ) { + private readonly selfSwitch: boolean; + private readonly switchType: SwitchType; + + constructor(selfSwitch = false, switchType: SwitchType = SwitchType.SWITCH) { super(false, { lastHitOnly: true }); + + this.selfSwitch = selfSwitch; + this.switchType = switchType; } isBatonPass() { return this.switchType === SwitchType.BATON_PASS; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { // Check if the move category is not STATUS or if the switch out condition is not met if (!this.getSwitchOutCondition()(user, target, move)) { return false; @@ -6567,15 +6958,15 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (switchOutTarget.isPlayer()) { /** - * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch - * If it did, the user of U-turn or Volt Switch will not be switched out. - */ - if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr") - && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) + * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch + * If it did, the user of U-turn or Volt Switch will not be switched out. + */ + if ( + target.getAbility().hasAttr("PostDamageForceSwitchAbAttr") + && [MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN].includes(move.id) + && this.hpDroppedBelowHalf(target) ) { - if (this.hpDroppedBelowHalf(target)) { - return false; - } + return false; } // Find indices of off-field Pokemon that are eligible to be switched into @@ -6586,7 +6977,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } }); - if (eligibleNewIndices.length < 1) { + if (eligibleNewIndices.length === 0) { return false; } @@ -6600,32 +6991,38 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { switchOutTarget.getFieldIndex(), slotIndex, false, - true + true, ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); globalScene.phaseManager.queueDeferred( "SwitchPhase", - this.switchType, - switchOutTarget.getFieldIndex(), - true, - true + this.switchType, + switchOutTarget.getFieldIndex(), + true, + true, ); return true; } } return false; - } else if (globalScene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers + } + if (globalScene.currentBattle.battleType !== BattleType.WILD) { + // Switch out logic for enemy trainers // Find indices of off-field Pokemon that are eligible to be switched into const isPartnerTrainer = globalScene.currentBattle.trainer?.isPartner(); const eligibleNewIndices: number[] = []; globalScene.getEnemyParty().forEach((pokemon, index) => { - if (pokemon.isAllowedInBattle() && !pokemon.isOnField() && (!isPartnerTrainer || pokemon.trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)) { + if ( + pokemon.isAllowedInBattle() + && !pokemon.isOnField() + && (!isPartnerTrainer || pokemon.trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot) + ) { eligibleNewIndices.push(index); } }); - if (eligibleNewIndices.length < 1) { + if (eligibleNewIndices.length === 0) { return false; } @@ -6635,11 +7032,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; globalScene.phaseManager.queueDeferred( "SwitchSummonPhase", - this.switchType, - switchOutTarget.getFieldIndex(), - slotIndex, - false, - false + this.switchType, + switchOutTarget.getFieldIndex(), + slotIndex, + false, + false, ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); @@ -6647,30 +7044,38 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { "SwitchSummonPhase", this.switchType, switchOutTarget.getFieldIndex(), - (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), + globalScene.currentBattle.trainer + ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) + : 0, + false, false, - false ); } } - } else { // Switch out logic for wild pokemon + } else { + // Switch out logic for wild pokemon /** - * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch - * If it did, the user of U-turn or Volt Switch will not be switched out. - */ - if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr") - && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) + * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch + * If it did, the user of U-turn or Volt Switch will not be switched out. + */ + if ( + target.getAbility().hasAttr("PostDamageForceSwitchAbAttr") + && [MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN].includes(move.id) + && this.hpDroppedBelowHalf(target) ) { - if (this.hpDroppedBelowHalf(target)) { - return false; - } + return false; } const allyPokemon = switchOutTarget.getAlly(); if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), + null, + true, + 500, + ); // in double battles redirect potential moves off fled pokemon if (globalScene.currentBattle.double && allyPokemon != null) { @@ -6682,37 +7087,39 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { globalScene.clearEnemyHeldItemModifiers(switchOutTarget); if (!allyPokemon?.isActive(true) && switchOutTarget.hp) { - globalScene.phaseManager.pushNew("BattleEndPhase", false); + globalScene.phaseManager.pushNew("BattleEndPhase", false); - if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { - globalScene.phaseManager.pushNew("SelectBiomePhase"); - } + if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { + globalScene.phaseManager.pushNew("SelectBiomePhase"); + } - globalScene.phaseManager.pushNew("NewBattlePhase"); + globalScene.phaseManager.pushNew("NewBattlePhase"); } } - return true; + return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => (move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move)); + return (user, target, move) => + move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move); } getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined { const cancelled = new BooleanHolder(false); - applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled}); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", { pokemon: target, cancelled }); if (cancelled.value) { return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }); } } - getSwitchOutCondition(): MoveConditionFunc { return (user, target, move) => { - const switchOutTarget = (this.selfSwitch ? user : target); + const switchOutTarget = this.selfSwitch ? user : target; const player = switchOutTarget.isPlayer(); - const forceSwitchAttr = move.getAttrs("ForceSwitchOutAttr").find(attr => attr.switchType === SwitchType.FORCE_SWITCH); + const forceSwitchAttr = move + .getAttrs("ForceSwitchOutAttr") + .find(attr => attr.switchType === SwitchType.FORCE_SWITCH); if (!this.selfSwitch) { if (move.hitsSubstitute(user, target)) { @@ -6721,9 +7128,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { // Check if the move is Roar or Whirlwind and if there is a trainer with only Pokémon left. if (forceSwitchAttr && globalScene.currentBattle.trainer) { - const enemyParty = globalScene.getEnemyParty(); - // Filter out any Pokémon that are not allowed in battle (e.g. fainted ones) - const remainingPokemon = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle()); + const enemyParty = globalScene.getEnemyParty(); + // Filter out any Pokémon that are not allowed in battle (e.g. fainted ones) + const remainingPokemon = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle()); if (remainingPokemon.length <= 1) { return false; } @@ -6735,30 +7142,40 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return false; } - if (!player && globalScene.currentBattle.isBattleMysteryEncounter() && !globalScene.currentBattle.mysteryEncounter?.fleeAllowed) { + if ( + !player + && globalScene.currentBattle.isBattleMysteryEncounter() + && !globalScene.currentBattle.mysteryEncounter?.fleeAllowed + ) { // Don't allow wild opponents to be force switched during MEs with flee disabled return false; } const blockedByAbility = new BooleanHolder(false); - applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled: blockedByAbility}); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", { pokemon: target, cancelled: blockedByAbility }); if (blockedByAbility.value) { return false; } } - if (!player && globalScene.currentBattle.battleType === BattleType.WILD) { // wild pokemon cannot switch out with baton pass. - return !this.isBatonPass() - && globalScene.currentBattle.waveIndex % 10 !== 0 - // Don't allow wild mons to flee with U-turn et al. - && !(this.selfSwitch && MoveCategory.STATUS !== move.category); + return ( + !this.isBatonPass() + && globalScene.currentBattle.waveIndex % 10 !== 0 // Don't allow wild mons to flee with U-turn et al. + && !(this.selfSwitch && MoveCategory.STATUS !== move.category) + ); } const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); - return party.filter(p => p.isAllowedInBattle() && !p.isOnField() - && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > 0; + return ( + party.filter( + p => + p.isAllowedInBattle() + && !p.isOnField() + && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot), + ).length > 0 + ); }; } @@ -6766,19 +7183,24 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (!globalScene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) { return -20; } - let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move); + let ret = this.selfSwitch + ? Math.floor((1 - user.getHpRatio()) * 20) + : super.getUserBenefitScore(user, target, move); if (this.selfSwitch && this.isBatonPass()) { - const statStageTotal = user.getStatStages().reduce((s: number, total: number) => total += s, 0); - ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10)); + const statStageTotal = user.getStatStages().reduce((s: number, total: number) => (total += s), 0); + ret = + ret / 2 + + Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) + * (statStageTotal >= 0 ? 10 : -10); } return ret; } /** - * Helper function to check if the Pokémon's health is below half after taking damage. - * Used for an edge case interaction with Wimp Out/Emergency Exit. - * If the Ability activates due to being hit by U-turn or Volt Switch, the user of that move will not be switched out. - */ + * Helper function to check if the Pokémon's health is below half after taking damage. + * Used for an edge case interaction with Wimp Out/Emergency Exit. + * If the Ability activates due to being hit by U-turn or Volt Switch, the user of that move will not be switched out. + */ hpDroppedBelowHalf(target: Pokemon): boolean { const pokemonHealth = target.hp; const maxPokemonHealth = target.getMaxHp(); @@ -6798,21 +7220,20 @@ export class ChillyReceptionAttr extends ForceSwitchOutAttr { getCondition(): MoveConditionFunc { // chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail - return (user, target, move) => globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move); + return (user, target, move) => + globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move); } } export class RemoveTypeAttr extends MoveEffectAttr { - // TODO: Remove the message callback - private removedType: PokemonType; - private messageCallback: ((user: Pokemon) => void) | undefined; + private readonly removedType: PokemonType; + private readonly messageCallback: ((user: Pokemon) => void) | undefined; constructor(removedType: PokemonType, messageCallback?: (user: Pokemon) => void) { super(true, { trigger: MoveEffectTrigger.POST_TARGET }); this.removedType = removedType; this.messageCallback = messageCallback; - } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -6820,7 +7241,8 @@ export class RemoveTypeAttr extends MoveEffectAttr { return false; } - if (user.isTerastallized && user.getTeraType() === this.removedType) { // active tera types cannot be removed + if (user.isTerastallized && user.getTeraType() === this.removedType) { + // active tera types cannot be removed return false; } @@ -6832,7 +7254,6 @@ export class RemoveTypeAttr extends MoveEffectAttr { user.summonData.types = modifiedTypes; user.updateInfo(); - if (this.messageCallback) { this.messageCallback(user); } @@ -6858,13 +7279,19 @@ export class CopyTypeAttr extends MoveEffectAttr { user.summonData.types = targetTypes; user.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:copyType", { + pokemonName: getPokemonNameWithAffix(user), + targetPokemonName: getPokemonNameWithAffix(target), + }), + ); return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => target.getTypes()[0] !== PokemonType.UNKNOWN || target.summonData.addedType !== null; + return (_user, target, _move) => + target.getTypes()[0] !== PokemonType.UNKNOWN || target.summonData.addedType !== null; } } @@ -6886,10 +7313,15 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr { typeChange = this.getTypeForBiome(globalScene.arena.biomeType); } - user.summonData.types = [ typeChange ]; + user.summonData.types = [typeChange]; user.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), typeName: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[typeChange])}`) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:transformedIntoType", { + pokemonName: getPokemonNameWithAffix(user), + typeName: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[typeChange])}`), + }), + ); return true; } @@ -6986,29 +7418,38 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr { * Used by {@linkcode MoveId.SOAK} and {@linkcode MoveId.MAGIC_POWDER}. */ export class ChangeTypeAttr extends MoveEffectAttr { - private type: PokemonType; + private readonly type: PokemonType; constructor(type: PokemonType) { super(false); this.type = type; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - target.summonData.types = [ this.type ]; + apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { + target.summonData.types = [this.type]; target.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(target), typeName: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.type])}`) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:transformedIntoType", { + pokemonName: getPokemonNameWithAffix(target), + typeName: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.type])}`), + }), + ); return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => !target.isTerastallized && !target.hasAbility(AbilityId.MULTITYPE) && !target.hasAbility(AbilityId.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type); + return (_user, target, _move) => + !target.isTerastallized + && !target.hasAbility(AbilityId.MULTITYPE) + && !target.hasAbility(AbilityId.RKS_SYSTEM) + && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type); } } export class AddTypeAttr extends MoveEffectAttr { - private type: PokemonType; + private readonly type: PokemonType; constructor(type: PokemonType) { super(false); @@ -7016,17 +7457,22 @@ export class AddTypeAttr extends MoveEffectAttr { this.type = type; } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { target.summonData.addedType = this.type; target.updateInfo(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.type])}`), pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:addType", { + typeName: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[this.type])}`), + pokemonName: getPokemonNameWithAffix(target), + }), + ); return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => !target.isTerastallized && !target.getTypes().includes(this.type); + return (_user, target, _move) => !target.isTerastallized && !target.getTypes().includes(this.type); } } @@ -7041,8 +7487,13 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { } const firstMoveType = target.getMoveset()[0].getMove().type; - user.summonData.types = [ firstMoveType ]; - globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[firstMoveType])}`) })); + user.summonData.types = [firstMoveType]; + globalScene.phaseManager.queueMessage( + i18next.t("battle:transformedIntoType", { + pokemonName: getPokemonNameWithAffix(user), + type: i18next.t(`pokemonInfo:type.${toCamelCase(PokemonType[firstMoveType])}`), + }), + ); return true; } @@ -7056,7 +7507,7 @@ class CallMoveAttr extends OverrideMoveEffectAttr { protected invalidMoves: ReadonlySet; protected hasTarget: boolean; - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { // Get eligible targets for move, failing if we can't target anything const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); @@ -7067,14 +7518,24 @@ class CallMoveAttr extends OverrideMoveEffectAttr { // Spread moves and ones with only 1 valid target will use their normal targeting. // If not, target the Mirror Move recipient or else a random enemy in our target list - const targets = moveTargets.multiple || moveTargets.targets.length === 1 - ? moveTargets.targets - : [this.hasTarget - ? target.getBattlerIndex() - : moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)]]; + const targets = + moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : [ + this.hasTarget + ? target.getBattlerIndex() + : moveTargets.targets[user.randBattleSeedInt(moveTargets.targets.length)], + ]; globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", move.id); - globalScene.phaseManager.unshiftNew("MovePhase", user, targets, new PokemonMove(move.id), MoveUseMode.FOLLOW_UP, MovePhaseTimingModifier.FIRST); + globalScene.phaseManager.unshiftNew( + "MovePhase", + user, + targets, + new PokemonMove(move.id), + MoveUseMode.FOLLOW_UP, + MovePhaseTimingModifier.FIRST, + ); return true; } } @@ -7107,17 +7568,18 @@ export class RandomMoveAttr extends CallMoveAttr { */ override apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { // TODO: Move this into the constructor to avoid constructing this every call - const moveIds = getEnumValues(MoveId).map(m => !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)") ? m : MoveId.NONE); + const moveIds = getEnumValues(MoveId).map(m => + !this.invalidMoves.has(m) && !allMoves[m].name.endsWith(" (N)") ? m : MoveId.NONE, + ); let moveId: MoveId = MoveId.NONE; const moveStatus = new BooleanHolder(true); do { moveId = this.getMoveOverride() ?? moveIds[user.randBattleSeedInt(moveIds.length)]; moveStatus.value = moveId !== MoveId.NONE; if (user.isPlayer()) { - applyChallenges(ChallengeType.POKEMON_MOVE, moveId, moveStatus); + applyChallenges(ChallengeType.POKEMON_MOVE, moveId, moveStatus); } - } - while (!moveStatus.value); + } while (!moveStatus.value); return super.apply(user, target, allMoves[moveId], args); } } @@ -7131,9 +7593,9 @@ export class RandomMoveAttr extends CallMoveAttr { * Invalid moves are indicated by what is passed in to invalidMoves: {@linkcode invalidAssistMoves} or {@linkcode invalidSleepTalkMoves} */ export class RandomMovesetMoveAttr extends CallMoveAttr { - private includeParty: boolean; + private readonly includeParty: boolean; private moveId: number; - constructor(invalidMoves: ReadonlySet, includeParty: boolean = false) { + constructor(invalidMoves: ReadonlySet, includeParty = false) { super(); this.includeParty = includeParty; this.invalidMoves = invalidMoves; @@ -7146,18 +7608,18 @@ export class RandomMovesetMoveAttr extends CallMoveAttr { * @param move Move being used * @param args Unused */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { return super.apply(user, target, allMoves[this.moveId], args); } getCondition(): MoveConditionFunc { - return (user, target, move) => { + return (user, _target, _move) => { // includeParty will be true for Assist, false for Sleep Talk let allies: Pokemon[]; if (this.includeParty) { allies = (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user); } else { - allies = [ user ]; + allies = [user]; } const partyMoveset = allies.flatMap(p => p.moveset); const moves = partyMoveset.filter(m => !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)")); @@ -7173,10 +7635,10 @@ export class RandomMovesetMoveAttr extends CallMoveAttr { // TODO: extend CallMoveAttr export class NaturePowerAttr extends OverrideMoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { let moveId = MoveId.NONE; switch (globalScene.arena.getTerrainType()) { - // this allows terrains to 'override' the biome move + // this allows terrains to 'override' the biome move case TerrainType.NONE: switch (globalScene.arena.biomeType) { case BiomeId.TOWN: @@ -7306,7 +7768,14 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { // Load the move's animation if we didn't already and unshift a new usage phase globalScene.phaseManager.unshiftNew("LoadMoveAnimPhase", moveId); - globalScene.phaseManager.unshiftNew("MovePhase", user, [ target.getBattlerIndex() ], new PokemonMove(moveId), MoveUseMode.FOLLOW_UP, MovePhaseTimingModifier.FIRST); + globalScene.phaseManager.unshiftNew( + "MovePhase", + user, + [target.getBattlerIndex()], + new PokemonMove(moveId), + MoveUseMode.FOLLOW_UP, + MovePhaseTimingModifier.FIRST, + ); return true; } } @@ -7316,7 +7785,7 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { * Used for {@linkcode MoveId.COPYCAT} and {@linkcode MoveId.MIRROR_MOVE} */ export class CopyMoveAttr extends CallMoveAttr { - private mirrorMove: boolean; + private readonly mirrorMove: boolean; constructor(mirrorMove: boolean, invalidMoves: ReadonlySet = new Set()) { super(); this.mirrorMove = mirrorMove; @@ -7326,13 +7795,17 @@ export class CopyMoveAttr extends CallMoveAttr { apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { this.hasTarget = this.mirrorMove; // bang is correct as condition func returns `false` and fails move if no last move exists - const lastMove = this.mirrorMove ? target.getLastNonVirtualMove(false, false)!.move : globalScene.currentBattle.lastMove; + const lastMove = this.mirrorMove + ? target.getLastNonVirtualMove(false, false)!.move + : globalScene.currentBattle.lastMove; return super.apply(user, target, allMoves[lastMove], args); } getCondition(): MoveConditionFunc { return (_user, target, _move) => { - const lastMove = this.mirrorMove ? target.getLastNonVirtualMove(false, false)?.move : globalScene.currentBattle.lastMove; + const lastMove = this.mirrorMove + ? target.getLastNonVirtualMove(false, false)?.move + : globalScene.currentBattle.lastMove; return lastMove != null && !this.invalidMoves.has(lastMove); }; } @@ -7343,7 +7816,7 @@ export class CopyMoveAttr extends CallMoveAttr { * * Used by {@linkcode MoveId.INSTRUCT | Instruct}. * @see [Instruct on Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)) -*/ + */ export class RepeatMoveAttr extends MoveEffectAttr { private movesetMove: PokemonMove; constructor() { @@ -7361,13 +7834,15 @@ export class RepeatMoveAttr extends MoveEffectAttr { // bangs are justified as Instruct fails if no prior move or moveset move exists // TODO: How does instruct work when copying a move called via Copycat that the user itself knows? const lastMove = target.getLastNonVirtualMove()!; - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move)! + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move)!; // If the last move used can hit more than one target or has variable targets, // re-compute the targets for the attack (mainly for alternating double/single battles) // Rampaging moves (e.g. Outrage) are not included due to being incompatible with Instruct, // nor is Dragon Darts (due to its smart targeting bypassing normal target selection) - let moveTargets = this.movesetMove.getMove().isMultiTarget() ? getMoveTargets(target, this.movesetMove.moveId).targets : lastMove.targets; + let moveTargets = this.movesetMove.getMove().isMultiTarget() + ? getMoveTargets(target, this.movesetMove.moveId).targets + : lastMove.targets; // In the event the instructed move's only target is a fainted opponent, redirect it to an alive ally if possible. // Normally, all yet-unexecuted move phases would swap targets after any foe faints or flees (see `redirectPokemonMoves` in `battle-scene.ts`), @@ -7380,17 +7855,26 @@ export class RepeatMoveAttr extends MoveEffectAttr { && firstTarget !== target.getAlly() ) { const ally = firstTarget.getAlly(); - if (ally != null && ally.isActive()) { - moveTargets = [ ally.getBattlerIndex() ]; + if (ally?.isActive()) { + moveTargets = [ally.getBattlerIndex()]; } } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:instructingMove", { - userPokemonName: getPokemonNameWithAffix(user), - targetPokemonName: getPokemonNameWithAffix(target) - })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:instructingMove", { + userPokemonName: getPokemonNameWithAffix(user), + targetPokemonName: getPokemonNameWithAffix(target), + }), + ); target.turnData.extraTurns++; - globalScene.phaseManager.unshiftNew("MovePhase", target, moveTargets, movesetMove, MoveUseMode.NORMAL, MovePhaseTimingModifier.FIRST); + globalScene.phaseManager.unshiftNew( + "MovePhase", + target, + moveTargets, + movesetMove, + MoveUseMode.NORMAL, + MovePhaseTimingModifier.FIRST, + ); return true; } @@ -7464,12 +7948,14 @@ export class RepeatMoveAttr extends MoveEffectAttr { // TODO: Add Max/G-Max/Z-Move blockage if or when they are implemented ]; - if (!lastMove?.move // no move to instruct + if ( + !lastMove?.move // no move to instruct || !movesetMove // called move not in target's moveset (forgetting the move, etc.) - || movesetMove.ppUsed === movesetMove.getMovePp() // move out of pp - // TODO: This next line is likely redundant as all charging moves are in the above list + || movesetMove.ppUsed === movesetMove.getMovePp() // move out of pp // TODO: This next line is likely redundant as all charging moves are in the above list || allMoves[lastMove.move].isChargingMove() // called move is a charging/recharging move - || uninstructableMoves.includes(lastMove.move)) { // called move is in the banlist + || uninstructableMoves.includes(lastMove.move) + ) { + // called move is in the banlist return false; } this.movesetMove = movesetMove; @@ -7477,14 +7963,14 @@ export class RepeatMoveAttr extends MoveEffectAttr { }; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, _target: Pokemon, _move: Move): number { // TODO: Make the AI actually use instruct /* Ideally, the AI would score instruct based on the scorings of the on-field pokemons' - * last used moves at the time of using Instruct (by the time the instructor gets to act) - * with respect to the user's side. - * In 99.9% of cases, this would be the pokemon's ally (unless the target had last - * used a move like Decorate on the user or its ally) - */ + * last used moves at the time of using Instruct (by the time the instructor gets to act) + * with respect to the user's side. + * In 99.9% of cases, this would be the pokemon's ally (unless the target had last + * used a move like Decorate on the user or its ally) + */ return 2; } } @@ -7509,7 +7995,7 @@ export class ReducePpMoveAttr extends MoveEffectAttr { * @param args - N/A * @returns always `true` */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { /** The last move the target themselves used */ const lastMove = target.getLastNonVirtualMove(); const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move)!; // bang is correct as condition prevents this from being nullish @@ -7517,22 +8003,28 @@ export class ReducePpMoveAttr extends MoveEffectAttr { movesetMove.ppUsed = Math.min(lastPpUsed + this.reduction, movesetMove.getMovePp()); globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(target.id, movesetMove.getMove(), movesetMove.ppUsed)); - globalScene.phaseManager.queueMessage(i18next.t("battle:ppReduced", { targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: (movesetMove.ppUsed) - lastPpUsed })); + globalScene.phaseManager.queueMessage( + i18next.t("battle:ppReduced", { + targetName: getPokemonNameWithAffix(target), + moveName: movesetMove.getName(), + reduction: movesetMove.ppUsed - lastPpUsed, + }), + ); return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => { + return (_user, target, _move) => { const lastMove = target.getLastNonVirtualMove(); - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move) + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move); return !!movesetMove?.getPpRatio(); }; } - getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + getTargetBenefitScore(_user: Pokemon, target: Pokemon, _move: Move): number { const lastMove = target.getLastNonVirtualMove(); - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move) + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move); if (!movesetMove) { return 0; } @@ -7544,7 +8036,6 @@ export class ReducePpMoveAttr extends MoveEffectAttr { return (value / 4) * ppLeft; } return value; - } } @@ -7553,10 +8044,6 @@ export class ReducePpMoveAttr extends MoveEffectAttr { * Used for Eerie Spell. */ export class AttackReducePpMoveAttr extends ReducePpMoveAttr { - constructor(reduction: number) { - super(reduction); - } - /** * Checks if the target has used a move prior to the attack. PP-reduction is applied through the super class if so. * @@ -7587,7 +8074,7 @@ export class AttackReducePpMoveAttr extends ReducePpMoveAttr { } } -const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { +const targetMoveCopiableCondition: MoveConditionFunc = (_user, target, _move) => { const copiableMove = target.getLastNonVirtualMove(); if (!copiableMove?.move) { return false; @@ -7607,8 +8094,8 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { * Used by {@linkcode MoveId.MIMIC}. */ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const lastMove = target.getLastNonVirtualMove() + apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean { + const lastMove = target.getLastNonVirtualMove(); if (!lastMove?.move) { return false; } @@ -7625,7 +8112,9 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { user.summonData.moveset = user.getMoveset().slice(0); user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name }), + ); return true; } @@ -7661,7 +8150,7 @@ export class SketchAttr extends MoveEffectAttr { return false; } - const targetMove = target.getLastNonVirtualMove() + const targetMove = target.getLastNonVirtualMove(); if (!targetMove) { // failsafe for TS compiler return false; @@ -7675,7 +8164,12 @@ export class SketchAttr extends MoveEffectAttr { user.setMove(sketchIndex, sketchedMove.id); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:sketchedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: sketchedMove.name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:sketchedMove", { + pokemonName: getPokemonNameWithAffix(user), + moveName: sketchedMove.name, + }), + ); return true; } @@ -7687,9 +8181,11 @@ export class SketchAttr extends MoveEffectAttr { } const targetMove = target.getLastNonVirtualMove(); - return targetMove != null + return ( + targetMove != null && !invalidSketchMoves.has(targetMove.move) && user.getMoveset().every(m => m.moveId !== targetMove.move) + ); }; } } @@ -7712,23 +8208,32 @@ export class AbilityChangeAttr extends MoveEffectAttr { globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); if (moveTarget.breakIllusion()) { - globalScene.phaseManager.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(moveTarget) })); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(moveTarget) }), + ); } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(moveTarget), abilityName: allAbilities[this.ability].name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:acquiredAbility", { + pokemonName: getPokemonNameWithAffix(moveTarget), + abilityName: allAbilities[this.ability].name, + }), + ); moveTarget.setTempAbility(allAbilities[this.ability]); globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); return true; } getCondition(): MoveConditionFunc { - return (user, target, move) => (this.selfTarget ? user : target).getAbility().replaceable && (this.selfTarget ? user : target).getAbility().id !== this.ability; + return (user, target, _move) => + (this.selfTarget ? user : target).getAbility().replaceable + && (this.selfTarget ? user : target).getAbility().id !== this.ability; } } export class AbilityCopyAttr extends MoveEffectAttr { public copyToPartner: boolean; - constructor(copyToPartner: boolean = false) { + constructor(copyToPartner = false) { super(false); this.copyToPartner = copyToPartner; @@ -7739,13 +8244,26 @@ export class AbilityCopyAttr extends MoveEffectAttr { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:copiedTargetAbility", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + abilityName: allAbilities[target.getAbility().id].name, + }), + ); user.setTempAbility(target.getAbility()); const ally = user.getAlly(); - if (this.copyToPartner && globalScene.currentBattle?.double && ally != null && ally.hp) { // TODO is this the best way to check that the ally is active? - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(ally), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); + if (this.copyToPartner && globalScene.currentBattle?.double && ally != null && ally.hp) { + // TODO is this the best way to check that the ally is active? + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:copiedTargetAbility", { + pokemonName: getPokemonNameWithAffix(ally), + targetName: getPokemonNameWithAffix(target), + abilityName: allAbilities[target.getAbility().id].name, + }), + ); ally.setTempAbility(target.getAbility()); } @@ -7753,7 +8271,7 @@ export class AbilityCopyAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => { + return (user, target, _move) => { const ally = user.getAlly(); let ret = target.getAbility().copiable && user.getAbility().replaceable; if (this.copyToPartner && globalScene.currentBattle?.double) { @@ -7778,7 +8296,12 @@ export class AbilityGiveAttr extends MoveEffectAttr { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:acquiredAbility", { + pokemonName: getPokemonNameWithAffix(target), + abilityName: allAbilities[user.getAbility().id].name, + }), + ); target.setTempAbility(user.getAbility()); @@ -7786,7 +8309,8 @@ export class AbilityGiveAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => user.getAbility().copiable && target.getAbility().replaceable && user.getAbility().id !== target.getAbility().id; + return (user, target, _move) => + user.getAbility().copiable && target.getAbility().replaceable && user.getAbility().id !== target.getAbility().id; } } @@ -7798,7 +8322,9 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr { const tempAbility = user.getAbility(); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) }), + ); user.setTempAbility(target.getAbility()); target.setTempAbility(tempAbility); @@ -7809,7 +8335,7 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => [user, target].every(pkmn => pkmn.getAbility().swappable); + return (user, target, _move) => [user, target].every(pkmn => pkmn.getAbility().swappable); } } @@ -7824,7 +8350,9 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) }), + ); target.suppressAbility(); @@ -7835,7 +8363,9 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { /** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */ getCondition(): MoveConditionFunc { - return (_user, target, _move) => !target.summonData.abilitySuppressed && (target.getAbility().suppressable || (target.hasPassive() && target.getPassiveAbility().suppressable)); + return (_user, target, _move) => + !target.summonData.abilitySuppressed + && (target.getAbility().suppressable || (target.hasPassive() && target.getPassiveAbility().suppressable)); } } @@ -7883,7 +8413,7 @@ export class TransformAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target) => user.canTransformInto(target) + return (user, target) => user.canTransformInto(target); } } @@ -7893,7 +8423,7 @@ export class TransformAttr extends MoveEffectAttr { */ export class SwapStatAttr extends MoveEffectAttr { /** The stat to be swapped between the user and the target */ - private stat: EffectiveStat; + private readonly stat: EffectiveStat; constructor(stat: EffectiveStat) { super(); @@ -7916,10 +8446,12 @@ export class SwapStatAttr extends MoveEffectAttr { user.setStat(this.stat, target.getStat(this.stat, false), false); target.setStat(this.stat, temp, false); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:switchedStat", { - pokemonName: getPokemonNameWithAffix(user), - stat: i18next.t(getStatKey(this.stat)), - })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:switchedStat", { + pokemonName: getPokemonNameWithAffix(user), + stat: i18next.t(getStatKey(this.stat)), + }), + ); return true; } @@ -7932,8 +8464,8 @@ export class SwapStatAttr extends MoveEffectAttr { * Used by Power Shift. */ export class ShiftStatAttr extends MoveEffectAttr { - private statToSwitch: EffectiveStat; - private statToSwitchWith: EffectiveStat; + private readonly statToSwitch: EffectiveStat; + private readonly statToSwitchWith: EffectiveStat; constructor(statToSwitch: EffectiveStat, statToSwitchWith: EffectiveStat) { super(); @@ -7961,11 +8493,13 @@ export class ShiftStatAttr extends MoveEffectAttr { user.setStat(this.statToSwitch, secondStat, false); user.setStat(this.statToSwitchWith, firstStat, false); - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:shiftedStats", { - pokemonName: getPokemonNameWithAffix(user), - statToSwitch: i18next.t(getStatKey(this.statToSwitch)), - statToSwitchWith: i18next.t(getStatKey(this.statToSwitchWith)) - })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:shiftedStats", { + pokemonName: getPokemonNameWithAffix(user), + statToSwitch: i18next.t(getStatKey(this.statToSwitch)), + statToSwitchWith: i18next.t(getStatKey(this.statToSwitchWith)), + }), + ); return true; } @@ -7977,7 +8511,7 @@ export class ShiftStatAttr extends MoveEffectAttr { * @param move n/a * @returns number of points to add to the user's benefit score */ - override getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { return user.getStat(this.statToSwitchWith, false) > user.getStat(this.statToSwitch, false) ? 10 : 0; } } @@ -7989,8 +8523,8 @@ export class ShiftStatAttr extends MoveEffectAttr { */ export class AverageStatsAttr extends MoveEffectAttr { /** The stats to be averaged individually between the user and the target */ - private stats: readonly EffectiveStat[]; - private msgKey: string; + private readonly stats: readonly EffectiveStat[]; + private readonly msgKey: string; constructor(stats: readonly EffectiveStat[], msgKey: string) { super(); @@ -8028,10 +8562,10 @@ export class AverageStatsAttr extends MoveEffectAttr { export class MoneyAttr extends MoveEffectAttr { constructor() { - super(true, {firstHitOnly: true }); + super(true, { firstHitOnly: true }); } - apply(user: Pokemon, target: Pokemon, move: Move): boolean { + apply(_user: Pokemon, _target: Pokemon, _move: Move): boolean { globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere")); return true; @@ -8052,8 +8586,10 @@ export class DestinyBondAttr extends MoveEffectAttr { * @param args N/A * @returns true */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - globalScene.phaseManager.queueMessage(`${i18next.t("moveTriggers:tryingToTakeFoeDown", { pokemonName: getPokemonNameWithAffix(user) })}`); + apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean { + globalScene.phaseManager.queueMessage( + `${i18next.t("moveTriggers:tryingToTakeFoeDown", { pokemonName: getPokemonNameWithAffix(user) })}`, + ); user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id); return true; } @@ -8098,7 +8634,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr { * @param args N/A * @returns true */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { if (target.turnData.statStagesIncreased) { target.trySetStatus(this.effect, user); } @@ -8107,7 +8643,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr { } export class VariableTargetAttr extends MoveAttr { - private targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number; + private readonly targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number; constructor(targetChange: (user: Pokemon, target: Pokemon, move: Move) => number) { super(); @@ -8136,8 +8672,10 @@ export class AfterYouAttr extends MoveEffectAttr { * @param _args - Unused * @returns `true` */ - override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) })); + override apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) }), + ); globalScene.phaseManager.forceMoveNext((phase: MovePhase) => phase.pokemon === target); return true; @@ -8158,50 +8696,59 @@ export class ForceLastAttr extends MoveEffectAttr { * @param _args N/A * @returns true */ - override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) })); + override apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:forceLast", { targetPokemonName: getPokemonNameWithAffix(target) }), + ); globalScene.phaseManager.forceMoveLast((phase: MovePhase) => phase.pokemon === target); return true; } } -const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune(); +const failOnBossCondition: MoveConditionFunc = (_user, target, _move) => !target.isBossImmune(); -const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScene.currentBattle.double; +const failIfSingleBattle: MoveConditionFunc = (_user, _target, _move) => globalScene.currentBattle.double; -const failIfDampCondition: MoveConditionFunc = (user, target, move) => { +const failIfDampCondition: MoveConditionFunc = (user, _target, move) => { const cancelled = new BooleanHolder(false); // temporary workaround to prevent displaying the message during enemy command phase // TODO: either move this, or make the move condition func have a `simulated` param - const simulated = globalScene.phaseManager.getCurrentPhase()?.is('EnemyCommandPhase'); + const simulated = globalScene.phaseManager.getCurrentPhase()?.is("EnemyCommandPhase"); for (const p of inSpeedOrder(ArenaTagSide.BOTH)) { - applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled, simulated}); + applyAbAttrs("FieldPreventExplosiveMovesAbAttr", { pokemon: p, cancelled, simulated }); } // Queue a message if an ability prevented usage of the move if (!simulated && cancelled.value) { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }), + ); } return !cancelled.value; }; -const userSleptOrComatoseCondition: MoveConditionFunc = (user) => user.status?.effect === StatusEffect.SLEEP || user.hasAbility(AbilityId.COMATOSE); +const userSleptOrComatoseCondition: MoveConditionFunc = user => + user.status?.effect === StatusEffect.SLEEP || user.hasAbility(AbilityId.COMATOSE); -const targetSleptOrComatoseCondition: MoveConditionFunc = (_user: Pokemon, target: Pokemon, _move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(AbilityId.COMATOSE); +const targetSleptOrComatoseCondition: MoveConditionFunc = (_user: Pokemon, target: Pokemon, _move: Move) => + target.status?.effect === StatusEffect.SLEEP || target.hasAbility(AbilityId.COMATOSE); const failIfLastCondition: MoveConditionFunc = () => globalScene.phaseManager.hasPhaseOfType("MovePhase"); -const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => { +const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, _target: Pokemon, _move: Move) => { const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField()); }; -const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(PokemonType.GHOST); +const failIfGhostTypeCondition: MoveConditionFunc = (_user: Pokemon, target: Pokemon, _move: Move) => + !target.isOfType(PokemonType.GHOST); -const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0; +const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (_user: Pokemon, target: Pokemon, _move: Move) => + target.getHeldItems().filter(i => i.isTransferable)?.length > 0; -const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { - if (target == null) { // Fix bug when used against targets that have both fainted +const attackedByItemMessageFunc = (_user: Pokemon, target: Pokemon, _move: Move) => { + if (target == null) { + // Fix bug when used against targets that have both fainted return ""; } const heldItems = target.getHeldItems().filter(i => i.isTransferable); @@ -8209,12 +8756,15 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) = return ""; } const itemName = heldItems[0]?.type?.name ?? "item"; - const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName }); + const message: string = i18next.t("moveTriggers:attackedByItem", { + pokemonName: getPokemonNameWithAffix(target), + itemName, + }); return message; }; export class HitsSameTypeAttr extends VariableMoveTypeMultiplierAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { const multiplier = args[0] as NumberHolder; if (!user.getTypes(true).some(type => target.getTypes(true).includes(type))) { multiplier.value = 0; @@ -8263,12 +8813,17 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { } const userTypes = user.getTypes(); const validTypes = this.getTypeResistances(globalScene.gameMode, moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types - if (!validTypes.length) { + if (validTypes.length === 0) { return false; } const type = validTypes[user.randBattleSeedInt(validTypes.length)]; - user.summonData.types = [ type ]; - globalScene.phaseManager.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: toTitleCase(PokemonType[type]) })); + user.summonData.types = [type]; + globalScene.phaseManager.queueMessage( + i18next.t("battle:transformedIntoType", { + pokemonName: getPokemonNameWithAffix(user), + type: toTitleCase(PokemonType[type]), + }), + ); user.updateInfo(); return true; @@ -8278,7 +8833,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { * Retrieve the types resisting a given type. Used by Conversion 2 * @returns An array populated with Types, or an empty array if no resistances exist (Unknown or Stellar type) */ - getTypeResistances(gameMode: GameMode, type: number): PokemonType[] { + getTypeResistances(_gameMode: GameMode, type: number): PokemonType[] { const typeResistances: PokemonType[] = []; for (let i = 0; i < Object.keys(PokemonType).length; i++) { @@ -8295,7 +8850,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { getCondition(): MoveConditionFunc { // TODO: Does this count dancer? - return (user, target, move) => { + return (_user, target, _move) => { return target.getLastXMoves(-1).some(tm => tm.move !== MoveId.NONE); }; } @@ -8324,14 +8879,19 @@ export class ExposedMoveAttr extends AddBattlerTagAttr { return false; } - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:exposedMove", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:exposedMove", { + pokemonName: getPokemonNameWithAffix(user), + targetPokemonName: getPokemonNameWithAffix(target), + }), + ); return true; } } - -const unknownTypeCondition: MoveConditionFunc = (user, target, move) => !user.getTypes().includes(PokemonType.UNKNOWN); +const unknownTypeCondition: MoveConditionFunc = (user, _target, _move) => + !user.getTypes().includes(PokemonType.UNKNOWN); export type MoveTargetSet = { targets: BattlerIndex[]; @@ -8564,14 +9124,14 @@ export function initMoves() { (allMoves as Move[]).push( new SelfStatusMove(MoveId.NONE, PokemonType.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1), new AttackMove(MoveId.POUND, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1), - new AttackMove(MoveId.KARATE_CHOP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 1) + new AttackMove(MoveId.KARATE_CHOP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 1) // .attr(HighCritAttr), - new AttackMove(MoveId.DOUBLE_SLAP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 10, -1, 0, 1) + new AttackMove(MoveId.DOUBLE_SLAP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 10, -1, 0, 1) // .attr(MultiHitAttr), new AttackMove(MoveId.COMET_PUNCH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 18, 85, 15, -1, 0, 1) .attr(MultiHitAttr) .punchingMove(), - new AttackMove(MoveId.MEGA_PUNCH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 80, 85, 20, -1, 0, 1) + new AttackMove(MoveId.MEGA_PUNCH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 80, 85, 20, -1, 0, 1) // .punchingMove(), new AttackMove(MoveId.PAY_DAY, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 1) .attr(MoneyAttr) @@ -8596,9 +9156,9 @@ export function initMoves() { .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), new SelfStatusMove(MoveId.SWORDS_DANCE, PokemonType.NORMAL, -1, 20, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ATK ], 2, true) + .attr(StatStageChangeAttr, [Stat.ATK], 2, true) .danceMove(), - new AttackMove(MoveId.CUT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1) + new AttackMove(MoveId.CUT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1) // .slicingMove(), new AttackMove(MoveId.GUST, PokemonType.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING) @@ -8614,7 +9174,7 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .affectedByGravity(), - new AttackMove(MoveId.BIND, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) + new AttackMove(MoveId.BIND, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) // .attr(TrapAttr, BattlerTagType.BIND), new AttackMove(MoveId.SLAM, PokemonType.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1), new AttackMove(MoveId.VINE_WHIP, PokemonType.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1), @@ -8622,7 +9182,7 @@ export function initMoves() { .attr(AlwaysHitMinimizeAttr) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(FlinchAttr), - new AttackMove(MoveId.DOUBLE_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1) + new AttackMove(MoveId.DOUBLE_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1) // .attr(MultiHitAttr, MultiHitType._2), new AttackMove(MoveId.MEGA_KICK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 120, 75, 5, -1, 0, 1), new AttackMove(MoveId.JUMP_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 1) @@ -8630,15 +9190,15 @@ export function initMoves() { .attr(NoEffectAttr, crashDamageFunc) .affectedByGravity() .recklessMove(), - new AttackMove(MoveId.ROLLING_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, 30, 0, 1) + new AttackMove(MoveId.ROLLING_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, 30, 0, 1) // .attr(FlinchAttr), new StatusMove(MoveId.SAND_ATTACK, PokemonType.GROUND, 100, 15, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + .attr(StatStageChangeAttr, [Stat.ACC], -1) .reflectable(), - new AttackMove(MoveId.HEADBUTT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 15, 30, 0, 1) + new AttackMove(MoveId.HEADBUTT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 15, 30, 0, 1) // .attr(FlinchAttr), new AttackMove(MoveId.HORN_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 65, 100, 25, -1, 0, 1), - new AttackMove(MoveId.FURY_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) + new AttackMove(MoveId.FURY_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) // .attr(MultiHitAttr), new AttackMove(MoveId.HORN_DRILL, PokemonType.NORMAL, MoveCategory.PHYSICAL, 250, 30, 5, -1, 0, 1) .attr(OneHitKOAttr) @@ -8648,7 +9208,7 @@ export function initMoves() { .attr(AlwaysHitMinimizeAttr) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(StatusEffectAttr, StatusEffect.PARALYSIS), - new AttackMove(MoveId.WRAP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1) + new AttackMove(MoveId.WRAP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1) // .attr(TrapAttr, BattlerTagType.WRAP), new AttackMove(MoveId.TAKE_DOWN, PokemonType.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, 0, 1) .attr(RecoilAttr) @@ -8662,7 +9222,7 @@ export function initMoves() { .attr(RecoilAttr, false, 0.33) .recklessMove(), new StatusMove(MoveId.TAIL_WHIP, PokemonType.NORMAL, 100, 30, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) + .attr(StatStageChangeAttr, [Stat.DEF], -1) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), new AttackMove(MoveId.POISON_STING, PokemonType.POISON, MoveCategory.PHYSICAL, 15, 100, 35, 30, 0, 1) @@ -8676,14 +9236,14 @@ export function initMoves() { .attr(MultiHitAttr) .makesContact(false), new StatusMove(MoveId.LEER, PokemonType.NORMAL, 100, 30, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) + .attr(StatStageChangeAttr, [Stat.DEF], -1) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), new AttackMove(MoveId.BITE, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 1) .attr(FlinchAttr) .bitingMove(), new StatusMove(MoveId.GROWL, PokemonType.NORMAL, 100, 40, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1) + .attr(StatStageChangeAttr, [Stat.ATK], -1) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), @@ -8700,7 +9260,7 @@ export function initMoves() { .attr(ConfuseAttr) .soundBased() .reflectable(), - new AttackMove(MoveId.SONIC_BOOM, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, 0, 1) + new AttackMove(MoveId.SONIC_BOOM, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, 0, 1) // .attr(FixedDamageAttr, 20), new StatusMove(MoveId.DISABLE, PokemonType.NORMAL, 100, 20, -1, 0, 1) .attr(AddBattlerTagAttr, BattlerTagType.DISABLED, false, true) @@ -8711,11 +9271,11 @@ export function initMoves() { .ignoresSubstitute() .reflectable(), new AttackMove(MoveId.ACID, PokemonType.POISON, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) + .attr(StatStageChangeAttr, [Stat.SPDEF], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.EMBER, PokemonType.FIRE, MoveCategory.SPECIAL, 40, 100, 25, 10, 0, 1) + new AttackMove(MoveId.EMBER, PokemonType.FIRE, MoveCategory.SPECIAL, 40, 100, 25, 10, 0, 1) // .attr(StatusEffectAttr, StatusEffect.BURN), - new AttackMove(MoveId.FLAMETHROWER, PokemonType.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 10, 0, 1) + new AttackMove(MoveId.FLAMETHROWER, PokemonType.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 10, 0, 1) // .attr(StatusEffectAttr, StatusEffect.BURN), new StatusMove(MoveId.MIST, PokemonType.ICE, -1, 30, -1, 0, 1) .attr(AddArenaTagAttr, ArenaTagType.MIST, 5, true) @@ -8726,34 +9286,34 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_OTHERS) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERWATER) .attr(GulpMissileTagAttr), - new AttackMove(MoveId.ICE_BEAM, PokemonType.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1) + new AttackMove(MoveId.ICE_BEAM, PokemonType.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1) // .attr(StatusEffectAttr, StatusEffect.FREEZE), new AttackMove(MoveId.BLIZZARD, PokemonType.ICE, MoveCategory.SPECIAL, 110, 70, 5, 10, 0, 1) .attr(BlizzardAccuracyAttr) .attr(StatusEffectAttr, StatusEffect.FREEZE) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.PSYBEAM, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) + new AttackMove(MoveId.PSYBEAM, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) // .attr(ConfuseAttr), - new AttackMove(MoveId.BUBBLE_BEAM, PokemonType.WATER, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1), - new AttackMove(MoveId.AURORA_BEAM, PokemonType.ICE, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), - new AttackMove(MoveId.HYPER_BEAM, PokemonType.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 1) + new AttackMove(MoveId.BUBBLE_BEAM, PokemonType.WATER, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) // + .attr(StatStageChangeAttr, [Stat.SPD], -1), + new AttackMove(MoveId.AURORA_BEAM, PokemonType.ICE, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1) // + .attr(StatStageChangeAttr, [Stat.ATK], -1), + new AttackMove(MoveId.HYPER_BEAM, PokemonType.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 1) // .attr(RechargeAttr), new AttackMove(MoveId.PECK, PokemonType.FLYING, MoveCategory.PHYSICAL, 35, 100, 35, -1, 0, 1), new AttackMove(MoveId.DRILL_PECK, PokemonType.FLYING, MoveCategory.PHYSICAL, 80, 100, 20, -1, 0, 1), new AttackMove(MoveId.SUBMISSION, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 80, 80, 20, -1, 0, 1) .attr(RecoilAttr) .recklessMove(), - new AttackMove(MoveId.LOW_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1) + new AttackMove(MoveId.LOW_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1) // .attr(WeightPowerAttr), new AttackMove(MoveId.COUNTER, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, -5, 1) .attr(CounterDamageAttr, 2, MoveCategory.PHYSICAL) .attr(CounterRedirectAttr, MoveCategory.PHYSICAL) .condition(counterAttackConditionPhysical, 3) .target(MoveTarget.ATTACKER), - new AttackMove(MoveId.SEISMIC_TOSS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1) + new AttackMove(MoveId.SEISMIC_TOSS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1) // .attr(LevelDamageAttr), new AttackMove(MoveId.STRENGTH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 1), new AttackMove(MoveId.ABSORB, PokemonType.GRASS, MoveCategory.SPECIAL, 20, 100, 25, -1, 0, 1) @@ -8764,9 +9324,9 @@ export function initMoves() { .triageMove(), new StatusMove(MoveId.LEECH_SEED, PokemonType.GRASS, 90, 10, -1, 0, 1) .attr(LeechSeedAttr) - .condition((user, target, move) => !target.getTag(BattlerTagType.SEEDED) && !target.isOfType(PokemonType.GRASS)) + .condition((_user, target, _move) => !target.getTag(BattlerTagType.SEEDED) && !target.isOfType(PokemonType.GRASS)) .reflectable(), - new SelfStatusMove(MoveId.GROWTH, PokemonType.NORMAL, -1, 20, -1, 0, 1) + new SelfStatusMove(MoveId.GROWTH, PokemonType.NORMAL, -1, 20, -1, 0, 1) // .attr(GrowthStatStageChangeAttr), new AttackMove(MoveId.RAZOR_LEAF, PokemonType.GRASS, MoveCategory.PHYSICAL, 55, 95, 25, -1, 0, 1) .attr(HighCritAttr) @@ -8775,7 +9335,7 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new ChargingAttackMove(MoveId.SOLAR_BEAM, PokemonType.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:tookInSunlight", { pokemonName: "{USER}" })) - .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]) + .chargeAttr(WeatherInstantChargeAttr, [WeatherType.SUNNY, WeatherType.HARSH_SUN]) .attr(AntiSunlightPowerDecreaseAttr), new StatusMove(MoveId.POISON_POWDER, PokemonType.POISON, 75, 35, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -8797,16 +9357,16 @@ export function initMoves() { .danceMove() .target(MoveTarget.RANDOM_NEAR_ENEMY), new StatusMove(MoveId.STRING_SHOT, PokemonType.BUG, 95, 40, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPD ], -2) + .attr(StatStageChangeAttr, [Stat.SPD], -2) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), - new AttackMove(MoveId.DRAGON_RAGE, PokemonType.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1) + new AttackMove(MoveId.DRAGON_RAGE, PokemonType.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1) // .attr(FixedDamageAttr, 40), - new AttackMove(MoveId.FIRE_SPIN, PokemonType.FIRE, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 1) + new AttackMove(MoveId.FIRE_SPIN, PokemonType.FIRE, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 1) // .attr(TrapAttr, BattlerTagType.FIRE_SPIN), - new AttackMove(MoveId.THUNDER_SHOCK, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) + new AttackMove(MoveId.THUNDER_SHOCK, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), - new AttackMove(MoveId.THUNDERBOLT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 90, 100, 15, 10, 0, 1) + new AttackMove(MoveId.THUNDERBOLT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 90, 100, 15, 10, 0, 1) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new StatusMove(MoveId.THUNDER_WAVE, PokemonType.ELECTRIC, 90, 20, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) @@ -8816,11 +9376,13 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .attr(ThunderAccuracyAttr) .attr(HitsTagAttr, BattlerTagType.FLYING), - new AttackMove(MoveId.ROCK_THROW, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1) + new AttackMove(MoveId.ROCK_THROW, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1) // .makesContact(false), new AttackMove(MoveId.EARTHQUAKE, PokemonType.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1, + ) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(MoveId.FISSURE, PokemonType.GROUND, MoveCategory.PHYSICAL, 250, 30, 5, -1, 0, 1) @@ -8835,59 +9397,59 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.TOXIC) .attr(ToxicAccuracyAttr) .reflectable(), - new AttackMove(MoveId.CONFUSION, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, 10, 0, 1) + new AttackMove(MoveId.CONFUSION, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, 10, 0, 1) // .attr(ConfuseAttr), - new AttackMove(MoveId.PSYCHIC, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1), + new AttackMove(MoveId.PSYCHIC, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -1), new StatusMove(MoveId.HYPNOSIS, PokemonType.PSYCHIC, 60, 20, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.SLEEP) .reflectable(), - new SelfStatusMove(MoveId.MEDITATE, PokemonType.PSYCHIC, -1, 40, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true), - new SelfStatusMove(MoveId.AGILITY, PokemonType.PSYCHIC, -1, 30, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), + new SelfStatusMove(MoveId.MEDITATE, PokemonType.PSYCHIC, -1, 40, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.ATK], 1, true), + new SelfStatusMove(MoveId.AGILITY, PokemonType.PSYCHIC, -1, 30, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.SPD], 2, true), new AttackMove(MoveId.QUICK_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1), - new AttackMove(MoveId.RAGE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1) + new AttackMove(MoveId.RAGE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1) // .partial(), // No effect implemented new SelfStatusMove(MoveId.TELEPORT, PokemonType.PSYCHIC, -1, 20, -1, -6, 1) .attr(ForceSwitchOutAttr, true) .hidesUser() .condition(failTeleportCondition, 3), - new AttackMove(MoveId.NIGHT_SHADE, PokemonType.GHOST, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1) + new AttackMove(MoveId.NIGHT_SHADE, PokemonType.GHOST, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1) // .attr(LevelDamageAttr), - new StatusMove(MoveId.MIMIC, PokemonType.NORMAL, -1, 10, -1, 0, 1) + new StatusMove(MoveId.MIMIC, PokemonType.NORMAL, -1, 10, -1, 0, 1) // .attr(MovesetCopyMoveAttr) .ignoresSubstitute(), - new StatusMove(MoveId.SCREECH, PokemonType.NORMAL, 85, 40, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], -2) + new StatusMove(MoveId.SCREECH, PokemonType.NORMAL, 85, 40, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.DEF], -2) .soundBased() .reflectable(), - new SelfStatusMove(MoveId.DOUBLE_TEAM, PokemonType.NORMAL, -1, 15, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.EVA ], 1, true), - new SelfStatusMove(MoveId.RECOVER, PokemonType.NORMAL, -1, 5, -1, 0, 1) + new SelfStatusMove(MoveId.DOUBLE_TEAM, PokemonType.NORMAL, -1, 15, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.EVA], 1, true), + new SelfStatusMove(MoveId.RECOVER, PokemonType.NORMAL, -1, 5, -1, 0, 1) // .attr(HealAttr, 0.5) .triageMove(), - new SelfStatusMove(MoveId.HARDEN, PokemonType.NORMAL, -1, 30, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), + new SelfStatusMove(MoveId.HARDEN, PokemonType.NORMAL, -1, 30, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.DEF], 1, true), new SelfStatusMove(MoveId.MINIMIZE, PokemonType.NORMAL, -1, 10, -1, 0, 1) .attr(AddBattlerTagAttr, BattlerTagType.MINIMIZED, true, false) - .attr(StatStageChangeAttr, [ Stat.EVA ], 2, true), + .attr(StatStageChangeAttr, [Stat.EVA], 2, true), new StatusMove(MoveId.SMOKESCREEN, PokemonType.NORMAL, 100, 20, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + .attr(StatStageChangeAttr, [Stat.ACC], -1) .reflectable(), - new StatusMove(MoveId.CONFUSE_RAY, PokemonType.GHOST, 100, 10, -1, 0, 1) + new StatusMove(MoveId.CONFUSE_RAY, PokemonType.GHOST, 100, 10, -1, 0, 1) // .attr(ConfuseAttr) .reflectable(), - new SelfStatusMove(MoveId.WITHDRAW, PokemonType.WATER, -1, 40, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), - new SelfStatusMove(MoveId.DEFENSE_CURL, PokemonType.NORMAL, -1, 40, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), - new SelfStatusMove(MoveId.BARRIER, PokemonType.PSYCHIC, -1, 20, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true), + new SelfStatusMove(MoveId.WITHDRAW, PokemonType.WATER, -1, 40, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.DEF], 1, true), + new SelfStatusMove(MoveId.DEFENSE_CURL, PokemonType.NORMAL, -1, 40, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.DEF], 1, true), + new SelfStatusMove(MoveId.BARRIER, PokemonType.PSYCHIC, -1, 20, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.DEF], 2, true), new StatusMove(MoveId.LIGHT_SCREEN, PokemonType.PSYCHIC, -1, 30, -1, 0, 1) .attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, true) .target(MoveTarget.USER_SIDE), - new SelfStatusMove(MoveId.HAZE, PokemonType.ICE, -1, 30, -1, 0, 1) + new SelfStatusMove(MoveId.HAZE, PokemonType.ICE, -1, 30, -1, 0, 1) // .ignoresSubstitute() .attr(ResetStatsAttr, true), new StatusMove(MoveId.REFLECT, PokemonType.PSYCHIC, -1, 20, -1, 0, 1) @@ -8900,9 +9462,9 @@ export function initMoves() { new AttackMove(MoveId.BIDE, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, 1, 1) .target(MoveTarget.USER) .unimplemented(), - new SelfStatusMove(MoveId.METRONOME, PokemonType.NORMAL, -1, 10, -1, 0, 1) + new SelfStatusMove(MoveId.METRONOME, PokemonType.NORMAL, -1, 10, -1, 0, 1) // .attr(RandomMoveAttr, invalidMetronomeMoves), - new StatusMove(MoveId.MIRROR_MOVE, PokemonType.FLYING, -1, 20, -1, 0, 1) + new StatusMove(MoveId.MIRROR_MOVE, PokemonType.FLYING, -1, 20, -1, 0, 1) // .attr(CopyMoveAttr, true, invalidMirrorMoveMoves), new AttackMove(MoveId.SELF_DESTRUCT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, 0, 1) .attr(SacrificialAttr) @@ -8912,37 +9474,37 @@ export function initMoves() { new AttackMove(MoveId.EGG_BOMB, PokemonType.NORMAL, MoveCategory.PHYSICAL, 100, 75, 10, -1, 0, 1) .makesContact(false) .ballBombMove(), - new AttackMove(MoveId.LICK, PokemonType.GHOST, MoveCategory.PHYSICAL, 30, 100, 30, 30, 0, 1) + new AttackMove(MoveId.LICK, PokemonType.GHOST, MoveCategory.PHYSICAL, 30, 100, 30, 30, 0, 1) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), - new AttackMove(MoveId.SMOG, PokemonType.POISON, MoveCategory.SPECIAL, 30, 70, 20, 40, 0, 1) + new AttackMove(MoveId.SMOG, PokemonType.POISON, MoveCategory.SPECIAL, 30, 70, 20, 40, 0, 1) // .attr(StatusEffectAttr, StatusEffect.POISON), - new AttackMove(MoveId.SLUDGE, PokemonType.POISON, MoveCategory.SPECIAL, 65, 100, 20, 30, 0, 1) + new AttackMove(MoveId.SLUDGE, PokemonType.POISON, MoveCategory.SPECIAL, 65, 100, 20, 30, 0, 1) // .attr(StatusEffectAttr, StatusEffect.POISON), new AttackMove(MoveId.BONE_CLUB, PokemonType.GROUND, MoveCategory.PHYSICAL, 65, 85, 20, 10, 0, 1) .attr(FlinchAttr) .makesContact(false), - new AttackMove(MoveId.FIRE_BLAST, PokemonType.FIRE, MoveCategory.SPECIAL, 110, 85, 5, 10, 0, 1) + new AttackMove(MoveId.FIRE_BLAST, PokemonType.FIRE, MoveCategory.SPECIAL, 110, 85, 5, 10, 0, 1) // .attr(StatusEffectAttr, StatusEffect.BURN), - new AttackMove(MoveId.WATERFALL, PokemonType.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1) + new AttackMove(MoveId.WATERFALL, PokemonType.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1) // .attr(FlinchAttr), - new AttackMove(MoveId.CLAMP, PokemonType.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 1) + new AttackMove(MoveId.CLAMP, PokemonType.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 1) // .attr(TrapAttr, BattlerTagType.CLAMP), - new AttackMove(MoveId.SWIFT, PokemonType.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1) + new AttackMove(MoveId.SWIFT, PokemonType.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1) // .target(MoveTarget.ALL_NEAR_ENEMIES), new ChargingAttackMove(MoveId.SKULL_BASH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:loweredItsHead", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.DEF ], 1, true), + .chargeAttr(StatStageChangeAttr, [Stat.DEF], 1, true), new AttackMove(MoveId.SPIKE_CANNON, PokemonType.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, 0, 1) .attr(MultiHitAttr) .makesContact(false), - new AttackMove(MoveId.CONSTRICT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 10, 100, 35, 10, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1), - new SelfStatusMove(MoveId.AMNESIA, PokemonType.PSYCHIC, -1, 20, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], 2, true), - new StatusMove(MoveId.KINESIS, PokemonType.PSYCHIC, 80, 15, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + new AttackMove(MoveId.CONSTRICT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 10, 100, 35, 10, 0, 1) // + .attr(StatStageChangeAttr, [Stat.SPD], -1), + new SelfStatusMove(MoveId.AMNESIA, PokemonType.PSYCHIC, -1, 20, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.SPDEF], 2, true), + new StatusMove(MoveId.KINESIS, PokemonType.PSYCHIC, 80, 15, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.ACC], -1) .reflectable(), - new SelfStatusMove(MoveId.SOFT_BOILED, PokemonType.NORMAL, -1, 5, -1, 0, 1) + new SelfStatusMove(MoveId.SOFT_BOILED, PokemonType.NORMAL, -1, 5, -1, 0, 1) // .attr(HealAttr, 0.5) .triageMove(), new AttackMove(MoveId.HIGH_JUMP_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 130, 90, 10, -1, 0, 1) @@ -8983,10 +9545,10 @@ export function initMoves() { * Does not copy the target's rage fist hit count * Does not copy the target's volatile status conditions (ie BattlerTags) * Renders user typeless when copying typeless opponent (should revert to original typing) - */ + */ .edgeCase(), new AttackMove(MoveId.BUBBLE, PokemonType.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.DIZZY_PUNCH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, 20, 0, 1) .attr(ConfuseAttr) @@ -8996,28 +9558,28 @@ export function initMoves() { .powderMove() .reflectable(), new StatusMove(MoveId.FLASH, PokemonType.NORMAL, 100, 20, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + .attr(StatStageChangeAttr, [Stat.ACC], -1) .reflectable(), - new AttackMove(MoveId.PSYWAVE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1) + new AttackMove(MoveId.PSYWAVE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1) // .attr(RandomLevelDamageAttr), new SelfStatusMove(MoveId.SPLASH, PokemonType.NORMAL, -1, 40, -1, 0, 1) .attr(MessageAttr, i18next.t("moveTriggers:splash")) .affectedByGravity(), - new SelfStatusMove(MoveId.ACID_ARMOR, PokemonType.POISON, -1, 20, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true), - new AttackMove(MoveId.CRABHAMMER, PokemonType.WATER, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 1) + new SelfStatusMove(MoveId.ACID_ARMOR, PokemonType.POISON, -1, 20, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.DEF], 2, true), + new AttackMove(MoveId.CRABHAMMER, PokemonType.WATER, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 1) // .attr(HighCritAttr), new AttackMove(MoveId.EXPLOSION, PokemonType.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, -1, 0, 1) .condition(failIfDampCondition, 3) .attr(SacrificialAttr) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), - new AttackMove(MoveId.FURY_SWIPES, PokemonType.NORMAL, MoveCategory.PHYSICAL, 18, 80, 15, -1, 0, 1) + new AttackMove(MoveId.FURY_SWIPES, PokemonType.NORMAL, MoveCategory.PHYSICAL, 18, 80, 15, -1, 0, 1) // .attr(MultiHitAttr), new AttackMove(MoveId.BONEMERANG, PokemonType.GROUND, MoveCategory.PHYSICAL, 50, 90, 10, -1, 0, 1) .attr(MultiHitAttr, MultiHitType._2) .makesContact(false), - new SelfStatusMove(MoveId.REST, PokemonType.PSYCHIC, -1, 5, -1, 0, 1) + new SelfStatusMove(MoveId.REST, PokemonType.PSYCHIC, -1, 5, -1, 0, 1) // .attr(RestAttr, 3) .triageMove(), new AttackMove(MoveId.ROCK_SLIDE, PokemonType.ROCK, MoveCategory.PHYSICAL, 75, 90, 10, 30, 0, 1) @@ -9027,25 +9589,27 @@ export function initMoves() { new AttackMove(MoveId.HYPER_FANG, PokemonType.NORMAL, MoveCategory.PHYSICAL, 80, 90, 15, 10, 0, 1) .attr(FlinchAttr) .bitingMove(), - new SelfStatusMove(MoveId.SHARPEN, PokemonType.NORMAL, -1, 30, -1, 0, 1) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true), - new SelfStatusMove(MoveId.CONVERSION, PokemonType.NORMAL, -1, 30, -1, 0, 1) + new SelfStatusMove(MoveId.SHARPEN, PokemonType.NORMAL, -1, 30, -1, 0, 1) // + .attr(StatStageChangeAttr, [Stat.ATK], 1, true), + new SelfStatusMove(MoveId.CONVERSION, PokemonType.NORMAL, -1, 30, -1, 0, 1) // .attr(FirstMoveTypeAttr), - new AttackMove(MoveId.TRI_ATTACK, PokemonType.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, 20, 0, 1) - .attr(MultiStatusEffectAttr, [ StatusEffect.BURN, StatusEffect.FREEZE, StatusEffect.PARALYSIS ]), - new AttackMove(MoveId.SUPER_FANG, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 90, 10, -1, 0, 1) + new AttackMove(MoveId.TRI_ATTACK, PokemonType.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, 20, 0, 1) // + .attr(MultiStatusEffectAttr, [StatusEffect.BURN, StatusEffect.FREEZE, StatusEffect.PARALYSIS]), + new AttackMove(MoveId.SUPER_FANG, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 90, 10, -1, 0, 1) // .attr(TargetHalfHpDamageAttr), new AttackMove(MoveId.SLASH, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 1) .attr(HighCritAttr) .slicingMove(), - new SelfStatusMove(MoveId.SUBSTITUTE, PokemonType.NORMAL, -1, 10, -1, 0, 1) + new SelfStatusMove(MoveId.SUBSTITUTE, PokemonType.NORMAL, -1, 10, -1, 0, 1) // .attr(AddSubstituteAttr, 0.25, false), new AttackMove(MoveId.STRUGGLE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1) .attr(RecoilAttr, true, 0.25, true) .attr(TypelessAttr) - .attr(PreMoveMessageAttr, (user: Pokemon) => i18next.t("moveTriggers:struggle", { pokemonName: getPokemonNameWithAffix(user) })) + .attr(PreMoveMessageAttr, (user: Pokemon) => + i18next.t("moveTriggers:struggle", { pokemonName: getPokemonNameWithAffix(user) }), + ) .target(MoveTarget.RANDOM_NEAR_ENEMY), - new StatusMove(MoveId.SKETCH, PokemonType.NORMAL, -1, 1, -1, 0, 2) + new StatusMove(MoveId.SKETCH, PokemonType.NORMAL, -1, 1, -1, 0, 2) // .ignoresSubstitute() .attr(SketchAttr), new AttackMove(MoveId.TRIPLE_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, 0, 2) @@ -9055,8 +9619,8 @@ export function initMoves() { new AttackMove(MoveId.THIEF, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2) .attr(StealHeldItemChanceAttr, 0.3) .edgeCase(), - // Should not be able to steal held item if user faints due to Rough Skin, Iron Barbs, etc. - // Should be able to steal items from pokemon with Sticky Hold if the damage causes them to faint + // Should not be able to steal held item if user faints due to Rough Skin, Iron Barbs, etc. + // Should be able to steal items from pokemon with Sticky Hold if the damage causes them to faint new StatusMove(MoveId.SPIDER_WEB, PokemonType.BUG, -1, 10, -1, 0, 2) .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1) @@ -9064,7 +9628,10 @@ export function initMoves() { new StatusMove(MoveId.MIND_READER, PokemonType.NORMAL, -1, 5, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_ACCURACY, true, false, 2) .attr(MessageAttr, (user, target) => - i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }) + i18next.t("moveTriggers:tookAimAtTarget", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + }), ), new StatusMove(MoveId.NIGHTMARE, PokemonType.GHOST, 100, 15, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE) @@ -9082,7 +9649,7 @@ export function initMoves() { .ignoresSubstitute() .ignoresProtect() .target(MoveTarget.CURSE), - new AttackMove(MoveId.FLAIL, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2) + new AttackMove(MoveId.FLAIL, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2) // .attr(LowHpPowerAttr), new StatusMove(MoveId.CONVERSION_2, PokemonType.NORMAL, -1, 30, -1, 0, 2) .attr(ResistLastMoveTypeAttr) @@ -9092,11 +9659,11 @@ export function initMoves() { .windMove() .attr(HighCritAttr), new StatusMove(MoveId.COTTON_SPORE, PokemonType.GRASS, 100, 40, -1, 0, 2) - .attr(StatStageChangeAttr, [ Stat.SPD ], -2) + .attr(StatStageChangeAttr, [Stat.SPD], -2) .powderMove() .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), - new AttackMove(MoveId.REVERSAL, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2) + new AttackMove(MoveId.REVERSAL, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2) // .attr(LowHpPowerAttr), new StatusMove(MoveId.SPITE, PokemonType.GHOST, 100, 10, -1, 0, 2) .ignoresSubstitute() @@ -9108,26 +9675,31 @@ export function initMoves() { new SelfStatusMove(MoveId.PROTECT, PokemonType.NORMAL, -1, 10, -1, 4, 2) .attr(ProtectAttr) .condition(failIfLastCondition, 3), - new AttackMove(MoveId.MACH_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2) + new AttackMove(MoveId.MACH_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2) // .punchingMove(), new StatusMove(MoveId.SCARY_FACE, PokemonType.NORMAL, 100, 10, -1, 0, 2) - .attr(StatStageChangeAttr, [ Stat.SPD ], -2) + .attr(StatStageChangeAttr, [Stat.SPD], -2) .reflectable(), new AttackMove(MoveId.FEINT_ATTACK, PokemonType.DARK, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 2), - new StatusMove(MoveId.SWEET_KISS, PokemonType.FAIRY, 75, 10, -1, 0, 2) + new StatusMove(MoveId.SWEET_KISS, PokemonType.FAIRY, 75, 10, -1, 0, 2) // .attr(ConfuseAttr) .reflectable(), - new SelfStatusMove(MoveId.BELLY_DRUM, PokemonType.NORMAL, -1, 10, -1, 0, 2) - .attr(CutHpStatStageBoostAttr, [ Stat.ATK ], 12, 2, (user) => { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) })); + new SelfStatusMove(MoveId.BELLY_DRUM, PokemonType.NORMAL, -1, 10, -1, 0, 2) // + .attr(CutHpStatStageBoostAttr, [Stat.ATK], 12, 2, user => { + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { + pokemonName: getPokemonNameWithAffix(user), + statName: i18next.t(getStatKey(Stat.ATK)), + }), + ); }), new AttackMove(MoveId.SLUDGE_BOMB, PokemonType.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2) .attr(StatusEffectAttr, StatusEffect.POISON) .ballBombMove(), - new AttackMove(MoveId.MUD_SLAP, PokemonType.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 100, 0, 2) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1), + new AttackMove(MoveId.MUD_SLAP, PokemonType.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 100, 0, 2) // + .attr(StatStageChangeAttr, [Stat.ACC], -1), new AttackMove(MoveId.OCTAZOOKA, PokemonType.WATER, MoveCategory.SPECIAL, 65, 85, 10, 50, 0, 2) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + .attr(StatStageChangeAttr, [Stat.ACC], -1) .ballBombMove(), new StatusMove(MoveId.SPIKES, PokemonType.GROUND, -1, 20, -1, 0, 2) .attr(AddArenaTrapTagAttr, ArenaTagType.SPIKES) @@ -9144,25 +9716,23 @@ export function initMoves() { .ignoresProtect() .attr(DestinyBondAttr) .condition(failAgainstFinalBossCondition, 2) - .condition((user, target, move) => { - // Retrieves user's previous move, returns empty array if no moves have been used + .condition((user, _target, move) => { const lastTurnMove = user.getLastXMoves(1); - // Checks last move and allows destiny bond to be used if: - // - no previous moves have been made - // - the previous move used was not destiny bond - // - the previous move was unsuccessful - return lastTurnMove.length === 0 || lastTurnMove[0].move !== move.id || lastTurnMove[0].result !== MoveResult.SUCCESS; + return ( + lastTurnMove.length === 0 || lastTurnMove[0].move !== move.id || lastTurnMove[0].result !== MoveResult.SUCCESS + ); }), new StatusMove(MoveId.PERISH_SONG, PokemonType.NORMAL, -1, 5, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.PERISH_SONG, false, true, 4) .attr(MessageAttr, (_user, target) => - i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: 3 })) + i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: 3 }), + ) .ignoresProtect() .soundBased() .condition(failOnBossCondition) .target(MoveTarget.ALL), new AttackMove(MoveId.ICY_WIND, PokemonType.ICE, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 2) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), new SelfStatusMove(MoveId.DETECT, PokemonType.FIGHTING, -1, 5, -1, 4, 2) @@ -9174,7 +9744,10 @@ export function initMoves() { new StatusMove(MoveId.LOCK_ON, PokemonType.NORMAL, -1, 5, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_ACCURACY, true, false, 2) .attr(MessageAttr, (user, target) => - i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) }) + i18next.t("moveTriggers:tookAimAtTarget", { + pokemonName: getPokemonNameWithAffix(user), + targetName: getPokemonNameWithAffix(target), + }), ), new AttackMove(MoveId.OUTRAGE, PokemonType.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 2) .attr(FrenzyAttr) @@ -9191,27 +9764,27 @@ export function initMoves() { .attr(ProtectAttr, BattlerTagType.ENDURING) .condition(failIfLastCondition, 3), new StatusMove(MoveId.CHARM, PokemonType.FAIRY, 100, 20, -1, 0, 2) - .attr(StatStageChangeAttr, [ Stat.ATK ], -2) + .attr(StatStageChangeAttr, [Stat.ATK], -2) .reflectable(), new AttackMove(MoveId.ROLLOUT, PokemonType.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2) .partial() // Does not lock the user, also does not increase damage properly .attr(ConsecutiveUseDoublePowerAttr, 5, true, true, MoveId.DEFENSE_CURL), - new AttackMove(MoveId.FALSE_SWIPE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2) + new AttackMove(MoveId.FALSE_SWIPE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2) // .attr(SurviveDamageAttr), new StatusMove(MoveId.SWAGGER, PokemonType.NORMAL, 85, 15, -1, 0, 2) - .attr(StatStageChangeAttr, [ Stat.ATK ], 2) + .attr(StatStageChangeAttr, [Stat.ATK], 2) .attr(ConfuseAttr) .reflectable(), - new SelfStatusMove(MoveId.MILK_DRINK, PokemonType.NORMAL, -1, 5, -1, 0, 2) + new SelfStatusMove(MoveId.MILK_DRINK, PokemonType.NORMAL, -1, 5, -1, 0, 2) // .attr(HealAttr, 0.5) .triageMove(), - new AttackMove(MoveId.SPARK, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 2) + new AttackMove(MoveId.SPARK, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 2) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new AttackMove(MoveId.FURY_CUTTER, PokemonType.BUG, MoveCategory.PHYSICAL, 40, 95, 20, -1, 0, 2) .attr(ConsecutiveUseDoublePowerAttr, 3, true) .slicingMove(), - new AttackMove(MoveId.STEEL_WING, PokemonType.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), + new AttackMove(MoveId.STEEL_WING, PokemonType.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2) // + .attr(StatStageChangeAttr, [Stat.DEF], 1, true), new StatusMove(MoveId.MEAN_LOOK, PokemonType.NORMAL, -1, 5, -1, 0, 2) .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1) @@ -9219,7 +9792,7 @@ export function initMoves() { new StatusMove(MoveId.ATTRACT, PokemonType.NORMAL, 100, 15, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.INFATUATED) .ignoresSubstitute() - .condition((user, target, move) => user.isOppositeGender(target)) + .condition((user, target, _move) => user.isOppositeGender(target)) .reflectable(), new SelfStatusMove(MoveId.SLEEP_TALK, PokemonType.NORMAL, -1, 10, -1, 0, 2) .attr(BypassSleepAttr) @@ -9230,12 +9803,12 @@ export function initMoves() { .attr(PartyStatusCureAttr, i18next.t("moveTriggers:bellChimed"), AbilityId.SOUNDPROOF) .soundBased() .target(MoveTarget.PARTY), - new AttackMove(MoveId.RETURN, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 2) + new AttackMove(MoveId.RETURN, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 2) // .attr(FriendshipPowerAttr), new AttackMove(MoveId.PRESENT, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 90, 15, -1, 0, 2) .attr(PresentPowerAttr) .makesContact(false), - new AttackMove(MoveId.FRUSTRATION, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 2) + new AttackMove(MoveId.FRUSTRATION, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 2) // .attr(FriendshipPowerAttr, true), new StatusMove(MoveId.SAFEGUARD, PokemonType.NORMAL, -1, 25, -1, 0, 2) .target(MoveTarget.USER_SIDE) @@ -9250,7 +9823,9 @@ export function initMoves() { new AttackMove(MoveId.MAGNITUDE, PokemonType.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, 0, 2) .attr(PreMoveMessageAttr, magnitudeMessageFunc) .attr(MagnitudePowerAttr) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1, + ) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), @@ -9258,7 +9833,7 @@ export function initMoves() { .attr(ConfuseAttr) .punchingMove(), new AttackMove(MoveId.MEGAHORN, PokemonType.BUG, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 2), - new AttackMove(MoveId.DRAGON_BREATH, PokemonType.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, 30, 0, 2) + new AttackMove(MoveId.DRAGON_BREATH, PokemonType.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, 30, 0, 2) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new SelfStatusMove(MoveId.BATON_PASS, PokemonType.NORMAL, -1, 40, -1, 0, 2) .attr(ForceSwitchOutAttr, true, SwitchType.BATON_PASS) @@ -9267,50 +9842,54 @@ export function initMoves() { new StatusMove(MoveId.ENCORE, PokemonType.NORMAL, 100, 5, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true) .ignoresSubstitute() - .condition((user, target, move) => new EncoreTag(user.id).canAdd(target)) + .condition((user, target, _move) => new EncoreTag(user.id).canAdd(target)) .reflectable() // Can lock infinitely into struggle; has incorrect interactions with Blood Moon/Gigaton Hammer // Also may or may not incorrectly select targets for replacement move (needs verification) .edgeCase(), - new AttackMove(MoveId.PURSUIT, PokemonType.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) + new AttackMove(MoveId.PURSUIT, PokemonType.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) // .partial(), // No effect implemented new AttackMove(MoveId.RAPID_SPIN, PokemonType.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true) - .attr(RemoveBattlerTagAttr, [ - BattlerTagType.BIND, - BattlerTagType.WRAP, - BattlerTagType.FIRE_SPIN, - BattlerTagType.WHIRLPOOL, - BattlerTagType.CLAMP, - BattlerTagType.SAND_TOMB, - BattlerTagType.MAGMA_STORM, - BattlerTagType.SNAP_TRAP, - BattlerTagType.THUNDER_CAGE, - BattlerTagType.SEEDED, - BattlerTagType.INFESTATION - ], true) + .attr(StatStageChangeAttr, [Stat.SPD], 1, true) + .attr( + RemoveBattlerTagAttr, + [ + BattlerTagType.BIND, + BattlerTagType.WRAP, + BattlerTagType.FIRE_SPIN, + BattlerTagType.WHIRLPOOL, + BattlerTagType.CLAMP, + BattlerTagType.SAND_TOMB, + BattlerTagType.MAGMA_STORM, + BattlerTagType.SNAP_TRAP, + BattlerTagType.THUNDER_CAGE, + BattlerTagType.SEEDED, + BattlerTagType.INFESTATION, + ], + true, + ) .attr(RemoveArenaTrapAttr), new StatusMove(MoveId.SWEET_SCENT, PokemonType.NORMAL, 100, 20, -1, 0, 2) - .attr(StatStageChangeAttr, [ Stat.EVA ], -2) + .attr(StatStageChangeAttr, [Stat.EVA], -2) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), - new AttackMove(MoveId.IRON_TAIL, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1), - new AttackMove(MoveId.METAL_CLAW, PokemonType.STEEL, MoveCategory.PHYSICAL, 50, 95, 35, 10, 0, 2) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true), + new AttackMove(MoveId.IRON_TAIL, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2) // + .attr(StatStageChangeAttr, [Stat.DEF], -1), + new AttackMove(MoveId.METAL_CLAW, PokemonType.STEEL, MoveCategory.PHYSICAL, 50, 95, 35, 10, 0, 2) // + .attr(StatStageChangeAttr, [Stat.ATK], 1, true), new AttackMove(MoveId.VITAL_THROW, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 70, -1, 10, -1, -1, 2), - new SelfStatusMove(MoveId.MORNING_SUN, PokemonType.NORMAL, -1, 5, -1, 0, 2) + new SelfStatusMove(MoveId.MORNING_SUN, PokemonType.NORMAL, -1, 5, -1, 0, 2) // .attr(PlantHealAttr) .triageMove(), - new SelfStatusMove(MoveId.SYNTHESIS, PokemonType.GRASS, -1, 5, -1, 0, 2) + new SelfStatusMove(MoveId.SYNTHESIS, PokemonType.GRASS, -1, 5, -1, 0, 2) // .attr(PlantHealAttr) .triageMove(), - new SelfStatusMove(MoveId.MOONLIGHT, PokemonType.FAIRY, -1, 5, -1, 0, 2) + new SelfStatusMove(MoveId.MOONLIGHT, PokemonType.FAIRY, -1, 5, -1, 0, 2) // .attr(PlantHealAttr) .triageMove(), - new AttackMove(MoveId.HIDDEN_POWER, PokemonType.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 2) + new AttackMove(MoveId.HIDDEN_POWER, PokemonType.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 2) // .attr(HiddenPowerTypeAttr), - new AttackMove(MoveId.CROSS_CHOP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2) + new AttackMove(MoveId.CROSS_CHOP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2) // .attr(HighCritAttr), new AttackMove(MoveId.TWISTER, PokemonType.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING) @@ -9324,31 +9903,29 @@ export function initMoves() { .attr(WeatherChangeAttr, WeatherType.SUNNY) .target(MoveTarget.BOTH_SIDES), new AttackMove(MoveId.CRUNCH, PokemonType.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 2) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) + .attr(StatStageChangeAttr, [Stat.DEF], -1) .bitingMove(), new AttackMove(MoveId.MIRROR_COAT, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 20, -1, -5, 2) .attr(CounterDamageAttr, 2, MoveCategory.SPECIAL) .attr(CounterRedirectAttr, MoveCategory.SPECIAL) .condition(counterAttackConditionSpecial, 3) .target(MoveTarget.ATTACKER), - new StatusMove(MoveId.PSYCH_UP, PokemonType.NORMAL, -1, 10, -1, 0, 2) + new StatusMove(MoveId.PSYCH_UP, PokemonType.NORMAL, -1, 10, -1, 0, 2) // .ignoresSubstitute() .attr(CopyStatsAttr), new AttackMove(MoveId.EXTREME_SPEED, PokemonType.NORMAL, MoveCategory.PHYSICAL, 80, 100, 5, -1, 2, 2), - new AttackMove(MoveId.ANCIENT_POWER, PokemonType.ROCK, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 2) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true), + new AttackMove(MoveId.ANCIENT_POWER, PokemonType.ROCK, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 2) // + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, true), new AttackMove(MoveId.SHADOW_BALL, PokemonType.GHOST, MoveCategory.SPECIAL, 80, 100, 15, 20, 0, 2) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) + .attr(StatStageChangeAttr, [Stat.SPDEF], -1) .ballBombMove(), new AttackMove(MoveId.FUTURE_SIGHT, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) .attr(DelayedAttackAttr, ChargeAnim.FUTURE_SIGHT_CHARGING, "moveTriggers:foresawAnAttack") .ignoresProtect() - /* - * Should not apply abilities or held items if user is off the field - */ + // Should not apply abilities or held items if user is off the field .edgeCase(), - new AttackMove(MoveId.ROCK_SMASH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1), + new AttackMove(MoveId.ROCK_SMASH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2) // + .attr(StatStageChangeAttr, [Stat.DEF], -1), new AttackMove(MoveId.WHIRLPOOL, PokemonType.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2) .attr(TrapAttr, BattlerTagType.WHIRLPOOL) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERWATER), @@ -9362,19 +9939,20 @@ export function initMoves() { new AttackMove(MoveId.UPROAR, PokemonType.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 3) .soundBased() .target(MoveTarget.RANDOM_NEAR_ENEMY) - .partial(), // Does not lock the user, does not stop Pokemon from sleeping + // Does not lock the user, does not stop Pokemon from sleeping // Likely can make use of FrenzyAttr and an ArenaTag (just without the FrenzyMissFunc) + .partial(), new SelfStatusMove(MoveId.STOCKPILE, PokemonType.NORMAL, -1, 20, -1, 0, 3) .condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3, 3) .attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true), new AttackMove(MoveId.SPIT_UP, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 3) .condition(hasStockpileStacksCondition, 3) .attr(SpitUpPowerAttr, 100) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.STOCKPILING ], true), + .attr(RemoveBattlerTagAttr, [BattlerTagType.STOCKPILING], true), new SelfStatusMove(MoveId.SWALLOW, PokemonType.NORMAL, -1, 10, -1, 0, 3) .condition(hasStockpileStacksCondition, 3) .attr(SwallowHealAttr) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.STOCKPILING ], true) + .attr(RemoveBattlerTagAttr, [BattlerTagType.STOCKPILING], true) .triageMove() // TODO: Verify if using Swallow at full HP still consumes stacks or not .edgeCase(), @@ -9392,7 +9970,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1) .reflectable(), new StatusMove(MoveId.FLATTER, PokemonType.DARK, 100, 15, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 1) + .attr(StatStageChangeAttr, [Stat.SPATK], 1) .attr(ConfuseAttr) .reflectable(), new StatusMove(MoveId.WILL_O_WISP, PokemonType.FIRE, 85, 15, -1, 0, 3) @@ -9400,25 +9978,40 @@ export function initMoves() { .reflectable(), new StatusMove(MoveId.MEMENTO, PokemonType.DARK, 100, 10, -1, 0, 3) .attr(SacrificialAttrOnHit) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -2), + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], -2), new AttackMove(MoveId.FACADE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 3) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.status - && (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1) + .attr(MovePowerMultiplierAttr, (user, _target, _move) => + user.status + && (user.status.effect === StatusEffect.BURN + || user.status.effect === StatusEffect.POISON + || user.status.effect === StatusEffect.TOXIC + || user.status.effect === StatusEffect.PARALYSIS) + ? 2 + : 1, + ) .attr(BypassBurnDamageReductionAttr), new AttackMove(MoveId.FOCUS_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3) - .attr(MessageHeaderAttr, (user) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) })) - .attr(PreUseInterruptAttr, (user) => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), user => user.turnData.attacksReceived.some(r => r.damage > 0)) + .attr(MessageHeaderAttr, user => + i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) }), + ) + .attr( + PreUseInterruptAttr, + user => i18next.t("moveTriggers:lostFocus", { pokemonName: getPokemonNameWithAffix(user) }), + user => user.turnData.attacksReceived.some(r => r.damage > 0), + ) .punchingMove(), new AttackMove(MoveId.SMELLING_SALTS, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1, + ) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS), new SelfStatusMove(MoveId.FOLLOW_ME, PokemonType.NORMAL, -1, 20, -1, 2, 3) .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true) .condition(failIfSingleBattle, 3), - new StatusMove(MoveId.NATURE_POWER, PokemonType.NORMAL, -1, 20, -1, 0, 3) + new StatusMove(MoveId.NATURE_POWER, PokemonType.NORMAL, -1, 20, -1, 0, 3) // .attr(NaturePowerAttr), new SelfStatusMove(MoveId.CHARGE, PokemonType.ELECTRIC, -1, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1, true) + .attr(StatStageChangeAttr, [Stat.SPDEF], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false), new StatusMove(MoveId.TAUNT, PokemonType.DARK, 100, 20, -1, 0, 3) .ignoresSubstitute() @@ -9431,24 +10024,24 @@ export function initMoves() { .condition(failIfSingleBattle) // should stack multiplicatively if used multiple times in 1 turn .edgeCase(), - new StatusMove(MoveId.TRICK, PokemonType.PSYCHIC, 100, 10, -1, 0, 3) + new StatusMove(MoveId.TRICK, PokemonType.PSYCHIC, 100, 10, -1, 0, 3) // .unimplemented(), new StatusMove(MoveId.ROLE_PLAY, PokemonType.PSYCHIC, -1, 10, -1, 0, 3) .ignoresSubstitute() // TODO: Enable / remove once balance reaches a consensus on ability overrides during boss fights // .condition(failAgainstFinalBossCondition, 3) .attr(AbilityCopyAttr), - new SelfStatusMove(MoveId.WISH, PokemonType.NORMAL, -1, 10, -1, 0, 3) + new SelfStatusMove(MoveId.WISH, PokemonType.NORMAL, -1, 10, -1, 0, 3) // .attr(WishAttr) .triageMove(), - new SelfStatusMove(MoveId.ASSIST, PokemonType.NORMAL, -1, 20, -1, 0, 3) + new SelfStatusMove(MoveId.ASSIST, PokemonType.NORMAL, -1, 20, -1, 0, 3) // .attr(RandomMovesetMoveAttr, invalidAssistMoves, true), new SelfStatusMove(MoveId.INGRAIN, PokemonType.GRASS, -1, 20, -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLOATING ], true), - new AttackMove(MoveId.SUPERPOWER, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1, true), + .attr(RemoveBattlerTagAttr, [BattlerTagType.FLOATING], true), + new AttackMove(MoveId.SUPERPOWER, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF], -1, true), new SelfStatusMove(MoveId.MAGIC_COAT, PokemonType.PSYCHIC, -1, 15, -1, 4, 3) .attr(AddBattlerTagAttr, BattlerTagType.MAGIC_COAT, true, true, 0) .condition(failIfLastCondition, 3) @@ -9456,22 +10049,24 @@ export function initMoves() { // rely on move history // Also will not reflect roar / whirlwind if the target has ForceSwitchOutImmunityAbAttr .edgeCase(), - new SelfStatusMove(MoveId.RECYCLE, PokemonType.NORMAL, -1, 10, -1, 0, 3) + new SelfStatusMove(MoveId.RECYCLE, PokemonType.NORMAL, -1, 10, -1, 0, 3) // .unimplemented(), - new AttackMove(MoveId.REVENGE, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 3) + new AttackMove(MoveId.REVENGE, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 3) // .attr(TurnDamagedDoublePowerAttr), - new AttackMove(MoveId.BRICK_BREAK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, -1, 0, 3) + new AttackMove(MoveId.BRICK_BREAK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, -1, 0, 3) // .attr(RemoveScreensAttr), new StatusMove(MoveId.YAWN, PokemonType.NORMAL, -1, 10, -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true) - .condition((user, target, move) => !target.status && !target.isSafeguarded(user)) + .condition((user, target, _move) => !target.status && !target.isSafeguarded(user)) .reflectable(), new AttackMove(MoveId.KNOCK_OFF, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1, + ) .attr(RemoveHeldItemAttr, false) - .edgeCase(), // Should not be able to remove held item if user faints due to Rough Skin, Iron Barbs, etc. // Should be able to remove items from pokemon with Sticky Hold if the damage causes them to faint + .edgeCase(), new AttackMove(MoveId.ENDEAVOR, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3) .attr(MatchHpAttr) .condition(failOnBossCondition), @@ -9488,11 +10083,23 @@ export function initMoves() { // .condition(failAgainstFinalBossCondition, 2) .target(MoveTarget.ENEMY_SIDE), new SelfStatusMove(MoveId.REFRESH, PokemonType.NORMAL, -1, 20, -1, 0, 3) - .attr(HealStatusEffectAttr, true, [ StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN ]) - .condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)), - new SelfStatusMove(MoveId.GRUDGE, PokemonType.GHOST, -1, 5, -1, 0, 3) + .attr(HealStatusEffectAttr, true, [ + StatusEffect.PARALYSIS, + StatusEffect.POISON, + StatusEffect.TOXIC, + StatusEffect.BURN, + ]) + .condition( + (user, _target, _move) => + !!user.status + && (user.status.effect === StatusEffect.PARALYSIS + || user.status.effect === StatusEffect.POISON + || user.status.effect === StatusEffect.TOXIC + || user.status.effect === StatusEffect.BURN), + ), + new SelfStatusMove(MoveId.GRUDGE, PokemonType.GHOST, -1, 5, -1, 0, 3) // .attr(AddBattlerTagAttr, BattlerTagType.GRUDGE, true, undefined, 1), - new SelfStatusMove(MoveId.SNATCH, PokemonType.DARK, -1, 10, -1, 4, 3) + new SelfStatusMove(MoveId.SNATCH, PokemonType.DARK, -1, 10, -1, 4, 3) // .unimplemented(), new AttackMove(MoveId.SECRET_POWER, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) @@ -9501,19 +10108,19 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERWATER) .chargeAttr(GulpMissileTagAttr), - new AttackMove(MoveId.ARM_THRUST, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3) + new AttackMove(MoveId.ARM_THRUST, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3) // .attr(MultiHitAttr), - new SelfStatusMove(MoveId.CAMOUFLAGE, PokemonType.NORMAL, -1, 20, -1, 0, 3) + new SelfStatusMove(MoveId.CAMOUFLAGE, PokemonType.NORMAL, -1, 20, -1, 0, 3) // .attr(CopyBiomeTypeAttr), - new SelfStatusMove(MoveId.TAIL_GLOW, PokemonType.BUG, -1, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 3, true), - new AttackMove(MoveId.LUSTER_PURGE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1), + new SelfStatusMove(MoveId.TAIL_GLOW, PokemonType.BUG, -1, 20, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.SPATK], 3, true), + new AttackMove(MoveId.LUSTER_PURGE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -1), new AttackMove(MoveId.MIST_BALL, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1) + .attr(StatStageChangeAttr, [Stat.SPATK], -1) .ballBombMove(), new StatusMove(MoveId.FEATHER_DANCE, PokemonType.FLYING, 100, 15, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK ], -2) + .attr(StatStageChangeAttr, [Stat.ATK], -2) .danceMove() .reflectable(), new StatusMove(MoveId.TEETER_DANCE, PokemonType.NORMAL, 100, 20, -1, 0, 3) @@ -9531,9 +10138,9 @@ export function initMoves() { .partial() // Does not lock the user properly, does not increase damage correctly .attr(ConsecutiveUseDoublePowerAttr, 5, true, true, MoveId.DEFENSE_CURL) .ballBombMove(), - new AttackMove(MoveId.NEEDLE_ARM, PokemonType.GRASS, MoveCategory.PHYSICAL, 60, 100, 15, 30, 0, 3) + new AttackMove(MoveId.NEEDLE_ARM, PokemonType.GRASS, MoveCategory.PHYSICAL, 60, 100, 15, 30, 0, 3) // .attr(FlinchAttr), - new SelfStatusMove(MoveId.SLACK_OFF, PokemonType.NORMAL, -1, 5, -1, 0, 3) + new SelfStatusMove(MoveId.SLACK_OFF, PokemonType.NORMAL, -1, 5, -1, 0, 3) // .attr(HealAttr, 0.5) .triageMove(), new AttackMove(MoveId.HYPER_VOICE, PokemonType.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 3) @@ -9542,25 +10149,34 @@ export function initMoves() { new AttackMove(MoveId.POISON_FANG, PokemonType.POISON, MoveCategory.PHYSICAL, 50, 100, 15, 50, 0, 3) .attr(StatusEffectAttr, StatusEffect.TOXIC) .bitingMove(), - new AttackMove(MoveId.CRUSH_CLAW, PokemonType.NORMAL, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 3) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1), - new AttackMove(MoveId.BLAST_BURN, PokemonType.FIRE, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3) + new AttackMove(MoveId.CRUSH_CLAW, PokemonType.NORMAL, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 3) // + .attr(StatStageChangeAttr, [Stat.DEF], -1), + new AttackMove(MoveId.BLAST_BURN, PokemonType.FIRE, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3) // .attr(RechargeAttr), - new AttackMove(MoveId.HYDRO_CANNON, PokemonType.WATER, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3) + new AttackMove(MoveId.HYDRO_CANNON, PokemonType.WATER, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3) // .attr(RechargeAttr), new AttackMove(MoveId.METEOR_MASH, PokemonType.STEEL, MoveCategory.PHYSICAL, 90, 90, 10, 20, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK], 1, true) .punchingMove(), - new AttackMove(MoveId.ASTONISH, PokemonType.GHOST, MoveCategory.PHYSICAL, 30, 100, 15, 30, 0, 3) + new AttackMove(MoveId.ASTONISH, PokemonType.GHOST, MoveCategory.PHYSICAL, 30, 100, 15, 30, 0, 3) // .attr(FlinchAttr), new AttackMove(MoveId.WEATHER_BALL, PokemonType.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 3) .attr(WeatherBallTypeAttr) - .attr(MovePowerMultiplierAttr, (user, target, move) => { + .attr(MovePowerMultiplierAttr, (_user, _target, _move) => { const weather = globalScene.arena.weather; if (!weather) { return 1; } - const weatherTypes = [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN ]; + const weatherTypes = [ + WeatherType.SUNNY, + WeatherType.RAIN, + WeatherType.SANDSTORM, + WeatherType.HAIL, + WeatherType.SNOW, + WeatherType.FOG, + WeatherType.HEAVY_RAIN, + WeatherType.HARSH_SUN, + ]; if (weatherTypes.includes(weather.weatherType) && !weather.isEffectSuppressed()) { return 2; } @@ -9571,7 +10187,7 @@ export function initMoves() { .attr(PartyStatusCureAttr, i18next.t("moveTriggers:soothingAromaWaftedThroughArea"), AbilityId.SAP_SIPPER) .target(MoveTarget.PARTY), new StatusMove(MoveId.FAKE_TEARS, PokemonType.DARK, 100, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2) + .attr(StatStageChangeAttr, [Stat.SPDEF], -2) .reflectable(), new AttackMove(MoveId.AIR_CUTTER, PokemonType.FLYING, MoveCategory.SPECIAL, 60, 95, 25, -1, 0, 3) .attr(HighCritAttr) @@ -9579,20 +10195,20 @@ export function initMoves() { .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.OVERHEAT, PokemonType.FIRE, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true) + .attr(StatStageChangeAttr, [Stat.SPATK], -2, true) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), new StatusMove(MoveId.ODOR_SLEUTH, PokemonType.NORMAL, -1, 40, -1, 0, 3) .attr(ExposedMoveAttr, BattlerTagType.IGNORE_GHOST) .ignoresSubstitute() .reflectable(), new AttackMove(MoveId.ROCK_TOMB, PokemonType.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .makesContact(false), new AttackMove(MoveId.SILVER_WIND, PokemonType.BUG, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, true) .windMove(), new StatusMove(MoveId.METAL_SOUND, PokemonType.STEEL, 85, 40, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2) + .attr(StatStageChangeAttr, [Stat.SPDEF], -2) .soundBased() .reflectable(), new StatusMove(MoveId.GRASS_WHISTLE, PokemonType.GRASS, 55, 15, -1, 0, 3) @@ -9600,18 +10216,18 @@ export function initMoves() { .soundBased() .reflectable(), new StatusMove(MoveId.TICKLE, PokemonType.NORMAL, 100, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF], -1) .reflectable(), - new SelfStatusMove(MoveId.COSMIC_POWER, PokemonType.PSYCHIC, -1, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, true), + new SelfStatusMove(MoveId.COSMIC_POWER, PokemonType.PSYCHIC, -1, 20, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], 1, true), new AttackMove(MoveId.WATER_SPOUT, PokemonType.WATER, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 3) .attr(HpPowerAttr) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.SIGNAL_BEAM, PokemonType.BUG, MoveCategory.SPECIAL, 75, 100, 15, 10, 0, 3) + new AttackMove(MoveId.SIGNAL_BEAM, PokemonType.BUG, MoveCategory.SPECIAL, 75, 100, 15, 10, 0, 3) // .attr(ConfuseAttr), - new AttackMove(MoveId.SHADOW_PUNCH, PokemonType.GHOST, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 3) + new AttackMove(MoveId.SHADOW_PUNCH, PokemonType.GHOST, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 3) // .punchingMove(), - new AttackMove(MoveId.EXTRASENSORY, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3) + new AttackMove(MoveId.EXTRASENSORY, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3) // .attr(FlinchAttr), new AttackMove(MoveId.SKY_UPPERCUT, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3) .attr(HitsTagAttr, BattlerTagType.FLYING) @@ -9624,47 +10240,47 @@ export function initMoves() { .attr(OneHitKOAttr) .attr(SheerColdAccuracyAttr), new AttackMove(MoveId.MUDDY_WATER, PokemonType.WATER, MoveCategory.SPECIAL, 90, 85, 10, 30, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + .attr(StatStageChangeAttr, [Stat.ACC], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.BULLET_SEED, PokemonType.GRASS, MoveCategory.PHYSICAL, 25, 100, 30, -1, 0, 3) .attr(MultiHitAttr) .makesContact(false) .ballBombMove(), - new AttackMove(MoveId.AERIAL_ACE, PokemonType.FLYING, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 3) + new AttackMove(MoveId.AERIAL_ACE, PokemonType.FLYING, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 3) // .slicingMove(), new AttackMove(MoveId.ICICLE_SPEAR, PokemonType.ICE, MoveCategory.PHYSICAL, 25, 100, 30, -1, 0, 3) .attr(MultiHitAttr) .makesContact(false), - new SelfStatusMove(MoveId.IRON_DEFENSE, PokemonType.STEEL, -1, 15, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true), + new SelfStatusMove(MoveId.IRON_DEFENSE, PokemonType.STEEL, -1, 15, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.DEF], 2, true), new StatusMove(MoveId.BLOCK, PokemonType.NORMAL, -1, 5, -1, 0, 3) .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1) .reflectable(), new StatusMove(MoveId.HOWL, PokemonType.NORMAL, -1, 40, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1) + .attr(StatStageChangeAttr, [Stat.ATK], 1) .soundBased() .target(MoveTarget.USER_AND_ALLIES), new AttackMove(MoveId.DRAGON_CLAW, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 3), - new AttackMove(MoveId.FRENZY_PLANT, PokemonType.GRASS, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3) + new AttackMove(MoveId.FRENZY_PLANT, PokemonType.GRASS, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3) // .attr(RechargeAttr), - new SelfStatusMove(MoveId.BULK_UP, PokemonType.FIGHTING, -1, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1, true), + new SelfStatusMove(MoveId.BULK_UP, PokemonType.FIGHTING, -1, 20, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF], 1, true), new ChargingAttackMove(MoveId.BOUNCE, PokemonType.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, 30, 0, 3) .chargeText(i18next.t("moveTriggers:sprangUp", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .affectedByGravity(), - new AttackMove(MoveId.MUD_SHOT, PokemonType.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1), + new AttackMove(MoveId.MUD_SHOT, PokemonType.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 3) // + .attr(StatStageChangeAttr, [Stat.SPD], -1), new AttackMove(MoveId.POISON_TAIL, PokemonType.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 10, 0, 3) .attr(HighCritAttr) .attr(StatusEffectAttr, StatusEffect.POISON), new AttackMove(MoveId.COVET, PokemonType.NORMAL, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 3) .attr(StealHeldItemChanceAttr, 0.3) - .edgeCase(), // Should not be able to steal held item if user faints due to Rough Skin, Iron Barbs, etc. // Should be able to steal items from pokemon with Sticky Hold if the damage causes them to faint + .edgeCase(), new AttackMove(MoveId.VOLT_TACKLE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 3) .attr(RecoilAttr, false, 0.33) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) @@ -9674,13 +10290,13 @@ export function initMoves() { .ignoresProtect() .attr(AddArenaTagAttr, ArenaTagType.WATER_SPORT, 5) .target(MoveTarget.BOTH_SIDES), - new SelfStatusMove(MoveId.CALM_MIND, PokemonType.PSYCHIC, -1, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF ], 1, true), + new SelfStatusMove(MoveId.CALM_MIND, PokemonType.PSYCHIC, -1, 20, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.SPATK, Stat.SPDEF], 1, true), new AttackMove(MoveId.LEAF_BLADE, PokemonType.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 3) .attr(HighCritAttr) .slicingMove(), new SelfStatusMove(MoveId.DRAGON_DANCE, PokemonType.DRAGON, -1, 20, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPD], 1, true) .danceMove(), new AttackMove(MoveId.ROCK_BLAST, PokemonType.ROCK, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 3) .attr(MultiHitAttr) @@ -9693,12 +10309,10 @@ export function initMoves() { new AttackMove(MoveId.DOOM_DESIRE, PokemonType.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) .attr(DelayedAttackAttr, ChargeAnim.DOOM_DESIRE_CHARGING, "moveTriggers:choseDoomDesireAsDestiny") .ignoresProtect() - /* - * Should not apply abilities or held items if user is off the field - */ + // Should not apply abilities or held items if user is off the field .edgeCase(), - new AttackMove(MoveId.PSYCHO_BOOST, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), + new AttackMove(MoveId.PSYCHO_BOOST, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3) // + .attr(StatStageChangeAttr, [Stat.SPATK], -2, true), new SelfStatusMove(MoveId.ROOST, PokemonType.FLYING, -1, 5, -1, 0, 4) .attr(HealAttr, 0.5) .attr(AddBattlerTagAttr, BattlerTagType.ROOSTED, true, false) @@ -9713,10 +10327,12 @@ export function initMoves() { .ignoresSubstitute() .reflectable(), new AttackMove(MoveId.WAKE_UP_SLAP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4) - .attr(MovePowerMultiplierAttr, (user, target, move) => targetSleptOrComatoseCondition(user, target, move) ? 2 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => + targetSleptOrComatoseCondition(user, target, move) ? 2 : 1, + ) .attr(HealStatusEffectAttr, false, StatusEffect.SLEEP), new AttackMove(MoveId.HAMMER_ARM, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1, true) + .attr(StatStageChangeAttr, [Stat.SPD], -1, true) .punchingMove(), new AttackMove(MoveId.GYRO_BALL, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) .attr(GyroBallPowerAttr) @@ -9725,22 +10341,26 @@ export function initMoves() { .attr(SacrificialFullRestoreAttr, false, "moveTriggers:sacrificialFullRestore") .triageMove() .condition(failIfLastInPartyCondition), - new AttackMove(MoveId.BRINE, PokemonType.WATER, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 4) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHpRatio() < 0.5 ? 2 : 1), + new AttackMove(MoveId.BRINE, PokemonType.WATER, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 4) // + .attr(MovePowerMultiplierAttr, (_user, target, _move) => (target.getHpRatio() < 0.5 ? 2 : 1)), new AttackMove(MoveId.NATURAL_GIFT, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 4) .makesContact(false) - .unimplemented(), /* NOTE: To whoever tries to implement this, reminder to push to battleData.berriesEaten and enable the harvest test.. Do NOT push to berriesEatenLast or else cud chew will puke the berry. */ + .unimplemented(), new AttackMove(MoveId.FEINT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 30, 100, 10, -1, 2, 4) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.PROTECTED ]) - .attr(RemoveArenaTagsAttr, [ ArenaTagType.QUICK_GUARD, ArenaTagType.WIDE_GUARD, ArenaTagType.MAT_BLOCK, ArenaTagType.CRAFTY_SHIELD ], false) + .attr(RemoveBattlerTagAttr, [BattlerTagType.PROTECTED]) + .attr( + RemoveArenaTagsAttr, + [ArenaTagType.QUICK_GUARD, ArenaTagType.WIDE_GUARD, ArenaTagType.MAT_BLOCK, ArenaTagType.CRAFTY_SHIELD], + false, + ) .makesContact(false) .ignoresProtect(), - new AttackMove(MoveId.PLUCK, PokemonType.FLYING, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4) + new AttackMove(MoveId.PLUCK, PokemonType.FLYING, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4) // .attr(StealEatBerryAttr), new StatusMove(MoveId.TAILWIND, PokemonType.FLYING, -1, 15, -1, 0, 4) .windMove() @@ -9755,16 +10375,21 @@ export function initMoves() { .condition(counterAttackConditionBoth, 3) .makesContact(false) .target(MoveTarget.ATTACKER), - new AttackMove(MoveId.U_TURN, PokemonType.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4) + new AttackMove(MoveId.U_TURN, PokemonType.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4) // .attr(ForceSwitchOutAttr, true), - new AttackMove(MoveId.CLOSE_COMBAT, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), + new AttackMove(MoveId.CLOSE_COMBAT, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) // + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], -1, true), new AttackMove(MoveId.PAYBACK, PokemonType.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4) // Payback boosts power on item use - .attr(MovePowerMultiplierAttr, (_user, target) => target.turnData.acted || globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.BALL ? 2 : 1), - new AttackMove(MoveId.ASSURANCE, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.turnData.damageTaken > 0 ? 2 : 1), - new StatusMove(MoveId.EMBARGO, PokemonType.DARK, 100, 15, -1, 0, 4) + .attr(MovePowerMultiplierAttr, (_user, target) => + target.turnData.acted + || globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.BALL + ? 2 + : 1, + ), + new AttackMove(MoveId.ASSURANCE, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4) // + .attr(MovePowerMultiplierAttr, (_user, target, _move) => (target.turnData.damageTaken > 0 ? 2 : 1)), + new StatusMove(MoveId.EMBARGO, PokemonType.DARK, 100, 15, -1, 0, 4) // .reflectable() .unimplemented(), new AttackMove(MoveId.FLING, PokemonType.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 4) @@ -9784,9 +10409,9 @@ export function initMoves() { new AttackMove(MoveId.WRING_OUT, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 4) .attr(OpponentHighHpPowerAttr, 120) .makesContact(), - new SelfStatusMove(MoveId.POWER_TRICK, PokemonType.PSYCHIC, -1, 10, -1, 0, 4) + new SelfStatusMove(MoveId.POWER_TRICK, PokemonType.PSYCHIC, -1, 10, -1, 0, 4) // .attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true), - new StatusMove(MoveId.GASTRO_ACID, PokemonType.POISON, 100, 10, -1, 0, 4) + new StatusMove(MoveId.GASTRO_ACID, PokemonType.POISON, 100, 10, -1, 0, 4) // .attr(SuppressAbilitiesAttr) .reflectable(), new StatusMove(MoveId.LUCKY_CHANT, PokemonType.NORMAL, -1, 30, -1, 0, 4) @@ -9796,13 +10421,13 @@ export function initMoves() { .ignoresSubstitute() .target(MoveTarget.NEAR_ENEMY) .unimplemented(), - new SelfStatusMove(MoveId.COPYCAT, PokemonType.NORMAL, -1, 20, -1, 0, 4) + new SelfStatusMove(MoveId.COPYCAT, PokemonType.NORMAL, -1, 20, -1, 0, 4) // .attr(CopyMoveAttr, false, invalidCopycatMoves), new StatusMove(MoveId.POWER_SWAP, PokemonType.PSYCHIC, -1, 10, 100, 0, 4) - .attr(SwapStatStagesAttr, [ Stat.ATK, Stat.SPATK ]) + .attr(SwapStatStagesAttr, [Stat.ATK, Stat.SPATK]) .ignoresSubstitute(), new StatusMove(MoveId.GUARD_SWAP, PokemonType.PSYCHIC, -1, 10, 100, 0, 4) - .attr(SwapStatStagesAttr, [ Stat.DEF, Stat.SPDEF ]) + .attr(SwapStatStagesAttr, [Stat.DEF, Stat.SPDEF]) .ignoresSubstitute(), new AttackMove(MoveId.PUNISHMENT, PokemonType.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) .makesContact(true) @@ -9815,7 +10440,7 @@ export function initMoves() { // TODO: Enable / remove once balance reaches a consensus on ability overrides during boss fights // .condition(failAgainstFinalBossCondition, 3) .reflectable(), - new AttackMove(MoveId.SUCKER_PUNCH, PokemonType.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, 1, 4) + new AttackMove(MoveId.SUCKER_PUNCH, PokemonType.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, 1, 4) // .condition(failIfTargetNotAttackingCondition, 3), new StatusMove(MoveId.TOXIC_SPIKES, PokemonType.POISON, -1, 20, -1, 0, 4) .attr(AddArenaTrapTagAttr, ArenaTagType.TOXIC_SPIKES) @@ -9824,25 +10449,31 @@ export function initMoves() { new StatusMove(MoveId.HEART_SWAP, PokemonType.PSYCHIC, -1, 10, -1, 0, 4) .attr(SwapStatStagesAttr, BATTLE_STATS) .ignoresSubstitute(), - new SelfStatusMove(MoveId.AQUA_RING, PokemonType.WATER, -1, 20, -1, 0, 4) + new SelfStatusMove(MoveId.AQUA_RING, PokemonType.WATER, -1, 20, -1, 0, 4) // .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true), new SelfStatusMove(MoveId.MAGNET_RISE, PokemonType.ELECTRIC, -1, 10, -1, 0, 4) .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, true, true, 5) - .condition(user => [ BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag)), 3) + .condition( + user => + [BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN].every( + tag => !user.getTag(tag), + ), + 3, + ) .affectedByGravity(), new AttackMove(MoveId.FLARE_BLITZ, PokemonType.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4) .attr(RecoilAttr, false, 0.33) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .recklessMove(), - new AttackMove(MoveId.FORCE_PALM, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, 30, 0, 4) + new AttackMove(MoveId.FORCE_PALM, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, 30, 0, 4) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), new AttackMove(MoveId.AURA_SPHERE, PokemonType.FIGHTING, MoveCategory.SPECIAL, 80, -1, 20, -1, 0, 4) .pulseMove() .ballBombMove(), - new SelfStatusMove(MoveId.ROCK_POLISH, PokemonType.ROCK, -1, 20, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), - new AttackMove(MoveId.POISON_JAB, PokemonType.POISON, MoveCategory.PHYSICAL, 80, 100, 20, 30, 0, 4) + new SelfStatusMove(MoveId.ROCK_POLISH, PokemonType.ROCK, -1, 20, -1, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPD], 2, true), + new AttackMove(MoveId.POISON_JAB, PokemonType.POISON, MoveCategory.PHYSICAL, 80, 100, 20, 30, 0, 4) // .attr(StatusEffectAttr, StatusEffect.POISON), new AttackMove(MoveId.DARK_PULSE, PokemonType.DARK, MoveCategory.SPECIAL, 80, 100, 15, 20, 0, 4) .attr(FlinchAttr) @@ -9857,12 +10488,12 @@ export function initMoves() { new AttackMove(MoveId.AIR_SLASH, PokemonType.FLYING, MoveCategory.SPECIAL, 75, 95, 15, 30, 0, 4) .attr(FlinchAttr) .slicingMove(), - new AttackMove(MoveId.X_SCISSOR, PokemonType.BUG, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 4) + new AttackMove(MoveId.X_SCISSOR, PokemonType.BUG, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 4) // .slicingMove(), new AttackMove(MoveId.BUG_BUZZ, PokemonType.BUG, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) + .attr(StatStageChangeAttr, [Stat.SPDEF], -1) .soundBased(), - new AttackMove(MoveId.DRAGON_PULSE, PokemonType.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4) + new AttackMove(MoveId.DRAGON_PULSE, PokemonType.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4) // .pulseMove(), new AttackMove(MoveId.DRAGON_RUSH, PokemonType.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4) .attr(AlwaysHitMinimizeAttr) @@ -9875,29 +10506,29 @@ export function initMoves() { .triageMove(), new AttackMove(MoveId.VACUUM_WAVE, PokemonType.FIGHTING, MoveCategory.SPECIAL, 40, 100, 30, -1, 1, 4), new AttackMove(MoveId.FOCUS_BLAST, PokemonType.FIGHTING, MoveCategory.SPECIAL, 120, 70, 5, 10, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) + .attr(StatStageChangeAttr, [Stat.SPDEF], -1) .ballBombMove(), new AttackMove(MoveId.ENERGY_BALL, PokemonType.GRASS, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) + .attr(StatStageChangeAttr, [Stat.SPDEF], -1) .ballBombMove(), new AttackMove(MoveId.BRAVE_BIRD, PokemonType.FLYING, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 4) .attr(RecoilAttr, false, 0.33) .recklessMove(), - new AttackMove(MoveId.EARTH_POWER, PokemonType.GROUND, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1), - new StatusMove(MoveId.SWITCHEROO, PokemonType.DARK, 100, 10, -1, 0, 4) + new AttackMove(MoveId.EARTH_POWER, PokemonType.GROUND, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -1), + new StatusMove(MoveId.SWITCHEROO, PokemonType.DARK, 100, 10, -1, 0, 4) // .unimplemented(), - new AttackMove(MoveId.GIGA_IMPACT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4) + new AttackMove(MoveId.GIGA_IMPACT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4) // .attr(RechargeAttr), - new SelfStatusMove(MoveId.NASTY_PLOT, PokemonType.DARK, -1, 20, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 2, true), - new AttackMove(MoveId.BULLET_PUNCH, PokemonType.STEEL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4) + new SelfStatusMove(MoveId.NASTY_PLOT, PokemonType.DARK, -1, 20, -1, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPATK], 2, true), + new AttackMove(MoveId.BULLET_PUNCH, PokemonType.STEEL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4) // .punchingMove(), - new AttackMove(MoveId.AVALANCHE, PokemonType.ICE, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 4) + new AttackMove(MoveId.AVALANCHE, PokemonType.ICE, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 4) // .attr(TurnDamagedDoublePowerAttr), - new AttackMove(MoveId.ICE_SHARD, PokemonType.ICE, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4) + new AttackMove(MoveId.ICE_SHARD, PokemonType.ICE, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4) // .makesContact(false), - new AttackMove(MoveId.SHADOW_CLAW, PokemonType.GHOST, MoveCategory.PHYSICAL, 70, 100, 15, -1, 0, 4) + new AttackMove(MoveId.SHADOW_CLAW, PokemonType.GHOST, MoveCategory.PHYSICAL, 70, 100, 15, -1, 0, 4) // .attr(HighCritAttr), new AttackMove(MoveId.THUNDER_FANG, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 65, 95, 15, 10, 0, 4) .attr(FlinchAttr) @@ -9913,42 +10544,42 @@ export function initMoves() { .bitingMove(), new AttackMove(MoveId.SHADOW_SNEAK, PokemonType.GHOST, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4), new AttackMove(MoveId.MUD_BOMB, PokemonType.GROUND, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1) + .attr(StatStageChangeAttr, [Stat.ACC], -1) .ballBombMove(), new AttackMove(MoveId.PSYCHO_CUT, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4) .attr(HighCritAttr) .slicingMove() .makesContact(false), - new AttackMove(MoveId.ZEN_HEADBUTT, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 20, 0, 4) + new AttackMove(MoveId.ZEN_HEADBUTT, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 20, 0, 4) // .attr(FlinchAttr), - new AttackMove(MoveId.MIRROR_SHOT, PokemonType.STEEL, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1), - new AttackMove(MoveId.FLASH_CANNON, PokemonType.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 10, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1), - new AttackMove(MoveId.ROCK_CLIMB, PokemonType.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 20, 0, 4) + new AttackMove(MoveId.MIRROR_SHOT, PokemonType.STEEL, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4) // + .attr(StatStageChangeAttr, [Stat.ACC], -1), + new AttackMove(MoveId.FLASH_CANNON, PokemonType.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 10, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -1), + new AttackMove(MoveId.ROCK_CLIMB, PokemonType.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 20, 0, 4) // .attr(ConfuseAttr), new StatusMove(MoveId.DEFOG, PokemonType.FLYING, -1, 15, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.EVA ], -1) + .attr(StatStageChangeAttr, [Stat.EVA], -1) .attr(ClearWeatherAttr, WeatherType.FOG) .attr(ClearTerrainAttr) .attr(RemoveScreensAttr, false) .attr(RemoveArenaTrapAttr, true) - .attr(RemoveArenaTagsAttr, [ ArenaTagType.MIST, ArenaTagType.SAFEGUARD ], false) + .attr(RemoveArenaTagsAttr, [ArenaTagType.MIST, ArenaTagType.SAFEGUARD], false) .reflectable(), new StatusMove(MoveId.TRICK_ROOM, PokemonType.PSYCHIC, -1, 5, -1, -7, 4) .attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5) .ignoresProtect() .target(MoveTarget.BOTH_SIDES), - new AttackMove(MoveId.DRACO_METEOR, PokemonType.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), + new AttackMove(MoveId.DRACO_METEOR, PokemonType.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPATK], -2, true), new AttackMove(MoveId.DISCHARGE, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(MoveId.LAVA_PLUME, PokemonType.FIRE, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4) .attr(StatusEffectAttr, StatusEffect.BURN) .target(MoveTarget.ALL_NEAR_OTHERS), - new AttackMove(MoveId.LEAF_STORM, PokemonType.GRASS, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), + new AttackMove(MoveId.LEAF_STORM, PokemonType.GRASS, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPATK], -2, true), new AttackMove(MoveId.POWER_WHIP, PokemonType.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 4), new AttackMove(MoveId.ROCK_WRECKER, PokemonType.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4) .attr(RechargeAttr) @@ -9961,7 +10592,7 @@ export function initMoves() { new AttackMove(MoveId.GUNK_SHOT, PokemonType.POISON, MoveCategory.PHYSICAL, 120, 80, 5, 30, 0, 4) .attr(StatusEffectAttr, StatusEffect.POISON) .makesContact(false), - new AttackMove(MoveId.IRON_HEAD, PokemonType.STEEL, MoveCategory.PHYSICAL, 80, 100, 15, 30, 0, 4) + new AttackMove(MoveId.IRON_HEAD, PokemonType.STEEL, MoveCategory.PHYSICAL, 80, 100, 15, 30, 0, 4) // .attr(FlinchAttr), new AttackMove(MoveId.MAGNET_BOMB, PokemonType.STEEL, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 4) .makesContact(false) @@ -9970,8 +10601,8 @@ export function initMoves() { .attr(HighCritAttr) .makesContact(false), new StatusMove(MoveId.CAPTIVATE, PokemonType.NORMAL, 100, 20, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2) - .condition((user, target, move) => target.isOppositeGender(user)) + .attr(StatStageChangeAttr, [Stat.SPATK], -2) + .condition((user, target, _move) => target.isOppositeGender(user)) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), new StatusMove(MoveId.STEALTH_ROCK, PokemonType.ROCK, -1, 20, -1, 0, 4) @@ -9984,12 +10615,12 @@ export function initMoves() { new AttackMove(MoveId.CHATTER, PokemonType.FLYING, MoveCategory.SPECIAL, 65, 100, 20, 100, 0, 4) .attr(ConfuseAttr) .soundBased(), - new AttackMove(MoveId.JUDGMENT, PokemonType.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 4) + new AttackMove(MoveId.JUDGMENT, PokemonType.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 4) // .attr(FormChangeItemTypeAttr), - new AttackMove(MoveId.BUG_BITE, PokemonType.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4) + new AttackMove(MoveId.BUG_BITE, PokemonType.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4) // .attr(StealEatBerryAttr), - new AttackMove(MoveId.CHARGE_BEAM, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 70, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true), + new AttackMove(MoveId.CHARGE_BEAM, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 70, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPATK], 1, true), new AttackMove(MoveId.WOOD_HAMMER, PokemonType.GRASS, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 4) .attr(RecoilAttr, false, 0.33) .recklessMove(), @@ -9997,44 +10628,44 @@ export function initMoves() { new AttackMove(MoveId.ATTACK_ORDER, PokemonType.BUG, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 4) .attr(HighCritAttr) .makesContact(false), - new SelfStatusMove(MoveId.DEFEND_ORDER, PokemonType.BUG, -1, 10, -1, 0, 4) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, true), - new SelfStatusMove(MoveId.HEAL_ORDER, PokemonType.BUG, -1, 5, -1, 0, 4) + new SelfStatusMove(MoveId.DEFEND_ORDER, PokemonType.BUG, -1, 10, -1, 0, 4) // + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], 1, true), + new SelfStatusMove(MoveId.HEAL_ORDER, PokemonType.BUG, -1, 5, -1, 0, 4) // .attr(HealAttr, 0.5) .triageMove(), new AttackMove(MoveId.HEAD_SMASH, PokemonType.ROCK, MoveCategory.PHYSICAL, 150, 80, 5, -1, 0, 4) .attr(RecoilAttr, false, 0.5) .recklessMove(), - new AttackMove(MoveId.DOUBLE_HIT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 35, 90, 10, -1, 0, 4) + new AttackMove(MoveId.DOUBLE_HIT, PokemonType.NORMAL, MoveCategory.PHYSICAL, 35, 90, 10, -1, 0, 4) // .attr(MultiHitAttr, MultiHitType._2), - new AttackMove(MoveId.ROAR_OF_TIME, PokemonType.DRAGON, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 4) + new AttackMove(MoveId.ROAR_OF_TIME, PokemonType.DRAGON, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 4) // .attr(RechargeAttr), - new AttackMove(MoveId.SPACIAL_REND, PokemonType.DRAGON, MoveCategory.SPECIAL, 100, 95, 5, -1, 0, 4) + new AttackMove(MoveId.SPACIAL_REND, PokemonType.DRAGON, MoveCategory.SPECIAL, 100, 95, 5, -1, 0, 4) // .attr(HighCritAttr), new SelfStatusMove(MoveId.LUNAR_DANCE, PokemonType.PSYCHIC, -1, 10, -1, 0, 4) .attr(SacrificialFullRestoreAttr, true, "moveTriggers:lunarDanceRestore") .danceMove() .triageMove() .condition(failIfLastInPartyCondition), - new AttackMove(MoveId.CRUSH_GRIP, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) + new AttackMove(MoveId.CRUSH_GRIP, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) // .attr(OpponentHighHpPowerAttr, 120), - new AttackMove(MoveId.MAGMA_STORM, PokemonType.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, 0, 4) + new AttackMove(MoveId.MAGMA_STORM, PokemonType.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, 0, 4) // .attr(TrapAttr, BattlerTagType.MAGMA_STORM), - new StatusMove(MoveId.DARK_VOID, PokemonType.DARK, 80, 10, -1, 0, 4) //Accuracy from Generations 4-6 + new StatusMove(MoveId.DARK_VOID, PokemonType.DARK, 80, 10, -1, 0, 4) // Accuracy from Generations 4-6 .attr(StatusEffectAttr, StatusEffect.SLEEP) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), - new AttackMove(MoveId.SEED_FLARE, PokemonType.GRASS, MoveCategory.SPECIAL, 120, 85, 5, 40, 0, 4) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), + new AttackMove(MoveId.SEED_FLARE, PokemonType.GRASS, MoveCategory.SPECIAL, 120, 85, 5, 40, 0, 4) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -2), new AttackMove(MoveId.OMINOUS_WIND, PokemonType.GHOST, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 4) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, true) .windMove(), new ChargingAttackMove(MoveId.SHADOW_FORCE, PokemonType.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) .ignoresProtect(), - new SelfStatusMove(MoveId.HONE_CLAWS, PokemonType.DARK, -1, 15, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true), + new SelfStatusMove(MoveId.HONE_CLAWS, PokemonType.DARK, -1, 15, -1, 0, 5) // + .attr(StatStageChangeAttr, [Stat.ATK, Stat.ACC], 1, true), new StatusMove(MoveId.WIDE_GUARD, PokemonType.ROCK, -1, 10, -1, 3, 5) .target(MoveTarget.USER_SIDE) .attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true) @@ -10042,30 +10673,51 @@ export function initMoves() { new StatusMove(MoveId.GUARD_SPLIT, PokemonType.PSYCHIC, -1, 10, -1, 0, 5) // TODO: Enable / remove once balance reaches a consensus on imprison interaction during the final boss fight // .condition(failAgainstFinalBossCondition, 2) - .attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"), - new StatusMove(MoveId.POWER_SPLIT, PokemonType.PSYCHIC, -1, 10, -1, 0, 5) - .attr(AverageStatsAttr, [ Stat.ATK, Stat.SPATK ], "moveTriggers:sharedPower"), - // TODO: Enable / remove once balance reaches a consensus on imprison interaction during the final boss fight - // .condition(failAgainstFinalBossCondition, 2) + .attr(AverageStatsAttr, [Stat.DEF, Stat.SPDEF], "moveTriggers:sharedGuard"), + new StatusMove(MoveId.POWER_SPLIT, PokemonType.PSYCHIC, -1, 10, -1, 0, 5) // + .attr(AverageStatsAttr, [Stat.ATK, Stat.SPATK], "moveTriggers:sharedPower"), + // TODO: Enable / remove once balance reaches a consensus on imprison interaction during the final boss fight + // .condition(failAgainstFinalBossCondition, 2) new StatusMove(MoveId.WONDER_ROOM, PokemonType.PSYCHIC, -1, 10, -1, 0, 5) .ignoresProtect() .target(MoveTarget.BOTH_SIDES) .unimplemented(), - new AttackMove(MoveId.PSYSHOCK, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5) + new AttackMove(MoveId.PSYSHOCK, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5) // .attr(DefDefAttr), - new AttackMove(MoveId.VENOSHOCK, PokemonType.POISON, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1), + new AttackMove(MoveId.VENOSHOCK, PokemonType.POISON, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5) // + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) + ? 2 + : 1, + ), new SelfStatusMove(MoveId.AUTOTOMIZE, PokemonType.STEEL, -1, 15, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true) + .attr(StatStageChangeAttr, [Stat.SPD], 2, true) .attr(AddBattlerTagAttr, BattlerTagType.AUTOTOMIZED, true), new SelfStatusMove(MoveId.RAGE_POWDER, PokemonType.BUG, -1, 20, -1, 2, 5) .powderMove() .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true), new StatusMove(MoveId.TELEKINESIS, PokemonType.PSYCHIC, -1, 15, -1, 0, 5) .affectedByGravity() - .condition((_user, target, _move) => ![ SpeciesId.DIGLETT, SpeciesId.DUGTRIO, SpeciesId.ALOLA_DIGLETT, SpeciesId.ALOLA_DUGTRIO, SpeciesId.SANDYGAST, SpeciesId.PALOSSAND, SpeciesId.WIGLETT, SpeciesId.WUGTRIO ].includes(target.species.speciesId)) - .condition((_user, target, _move) => !(target.species.speciesId === SpeciesId.GENGAR && target.getFormKey() === "mega")) - .condition((_user, target, _move) => target.getTag(BattlerTagType.INGRAIN) == null && target.getTag(BattlerTagType.IGNORE_FLYING) == null) + .condition( + (_user, target, _move) => + ![ + SpeciesId.DIGLETT, + SpeciesId.DUGTRIO, + SpeciesId.ALOLA_DIGLETT, + SpeciesId.ALOLA_DUGTRIO, + SpeciesId.SANDYGAST, + SpeciesId.PALOSSAND, + SpeciesId.WIGLETT, + SpeciesId.WUGTRIO, + ].includes(target.species.speciesId), + ) + .condition( + (_user, target, _move) => !(target.species.speciesId === SpeciesId.GENGAR && target.getFormKey() === "mega"), + ) + .condition( + (_user, target, _move) => + target.getTag(BattlerTagType.INGRAIN) == null && target.getTag(BattlerTagType.IGNORE_FLYING) == null, + ) .attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3) .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3) .reflectable(), @@ -10076,18 +10728,18 @@ export function initMoves() { new AttackMove(MoveId.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, 0, 5) .attr(FallDownAttr) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) + .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS]) .attr(HitsTagAttr, BattlerTagType.FLYING) .makesContact(false), - new AttackMove(MoveId.STORM_THROW, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) + new AttackMove(MoveId.STORM_THROW, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) // .attr(CritOnlyAttr), - new AttackMove(MoveId.FLAME_BURST, PokemonType.FIRE, MoveCategory.SPECIAL, 70, 100, 15, -1, 0, 5) + new AttackMove(MoveId.FLAME_BURST, PokemonType.FIRE, MoveCategory.SPECIAL, 70, 100, 15, -1, 0, 5) // .attr(FlameBurstAttr), new AttackMove(MoveId.SLUDGE_WAVE, PokemonType.POISON, MoveCategory.SPECIAL, 95, 100, 10, 10, 0, 5) .attr(StatusEffectAttr, StatusEffect.POISON) .target(MoveTarget.ALL_NEAR_OTHERS), new SelfStatusMove(MoveId.QUIVER_DANCE, PokemonType.BUG, -1, 20, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, true) .danceMove(), new AttackMove(MoveId.HEAVY_SLAM, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5) .attr(AlwaysHitMinimizeAttr) @@ -10103,16 +10755,16 @@ export function initMoves() { new StatusMove(MoveId.SOAK, PokemonType.WATER, 100, 20, -1, 0, 5) .attr(ChangeTypeAttr, PokemonType.WATER) .reflectable(), - new AttackMove(MoveId.FLAME_CHARGE, PokemonType.FIRE, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true), - new SelfStatusMove(MoveId.COIL, PokemonType.POISON, -1, 20, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.ACC ], 1, true), - new AttackMove(MoveId.LOW_SWEEP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 20, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1), + new AttackMove(MoveId.FLAME_CHARGE, PokemonType.FIRE, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 5) // + .attr(StatStageChangeAttr, [Stat.SPD], 1, true), + new SelfStatusMove(MoveId.COIL, PokemonType.POISON, -1, 20, -1, 0, 5) // + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.ACC], 1, true), + new AttackMove(MoveId.LOW_SWEEP, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 20, 100, 0, 5) // + .attr(StatStageChangeAttr, [Stat.SPD], -1), new AttackMove(MoveId.ACID_SPRAY, PokemonType.POISON, MoveCategory.SPECIAL, 40, 100, 20, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2) + .attr(StatStageChangeAttr, [Stat.SPDEF], -2) .ballBombMove(), - new AttackMove(MoveId.FOUL_PLAY, PokemonType.DARK, MoveCategory.PHYSICAL, 95, 100, 15, -1, 0, 5) + new AttackMove(MoveId.FOUL_PLAY, PokemonType.DARK, MoveCategory.PHYSICAL, 95, 100, 15, -1, 0, 5) // .attr(TargetAtkUserAtkAttr), new StatusMove(MoveId.SIMPLE_BEAM, PokemonType.NORMAL, 100, 15, -1, 0, 5) .attr(AbilityChangeAttr, AbilityId.SIMPLE) @@ -10129,7 +10781,7 @@ export function initMoves() { .ignoresSubstitute() .target(MoveTarget.NEAR_OTHER) .condition(failIfSingleBattle) - .condition((user, target, move) => !target.turnData.acted) + .condition((_user, target, _move) => !target.turnData.acted) .attr(AfterYouAttr), new AttackMove(MoveId.ROUND, PokemonType.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) .attr(CueNextRoundAttr) @@ -10138,17 +10790,17 @@ export function initMoves() { new AttackMove(MoveId.ECHOED_VOICE, PokemonType.NORMAL, MoveCategory.SPECIAL, 40, 100, 15, -1, 0, 5) .attr(ConsecutiveUseMultiBasePowerAttr, 5, false) .soundBased(), - new AttackMove(MoveId.CHIP_AWAY, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 5) + new AttackMove(MoveId.CHIP_AWAY, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 5) // .attr(IgnoreOpponentStatStagesAttr), - new AttackMove(MoveId.CLEAR_SMOG, PokemonType.POISON, MoveCategory.SPECIAL, 50, -1, 15, -1, 0, 5) + new AttackMove(MoveId.CLEAR_SMOG, PokemonType.POISON, MoveCategory.SPECIAL, 50, -1, 15, -1, 0, 5) // .attr(ResetStatsAttr, false), - new AttackMove(MoveId.STORED_POWER, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, -1, 0, 5) + new AttackMove(MoveId.STORED_POWER, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, -1, 0, 5) // .attr(PositiveStatStagePowerAttr), new StatusMove(MoveId.QUICK_GUARD, PokemonType.FIGHTING, -1, 15, -1, 3, 5) .target(MoveTarget.USER_SIDE) .attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true) .condition(failIfLastCondition, 3), - new SelfStatusMove(MoveId.ALLY_SWITCH, PokemonType.PSYCHIC, -1, 15, -1, 2, 5) + new SelfStatusMove(MoveId.ALLY_SWITCH, PokemonType.PSYCHIC, -1, 15, -1, 2, 5) // .ignoresProtect() .unimplemented(), new AttackMove(MoveId.SCALD, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 5) @@ -10156,22 +10808,22 @@ export function initMoves() { .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN), new SelfStatusMove(MoveId.SHELL_SMASH, PokemonType.NORMAL, -1, 15, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, true) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK, Stat.SPD], 2, true) + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], -1, true), new StatusMove(MoveId.HEAL_PULSE, PokemonType.PSYCHIC, -1, 10, -1, 0, 5) .attr(HealAttr, 0.5, false, false) .pulseMove() .triageMove() .reflectable(), - new AttackMove(MoveId.HEX, PokemonType.GHOST, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5) - .attr( - MovePowerMultiplierAttr, - (user, target, move) => target.status || target.hasAbility(AbilityId.COMATOSE) ? 2 : 1), + new AttackMove(MoveId.HEX, PokemonType.GHOST, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5) // + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + target.status || target.hasAbility(AbilityId.COMATOSE) ? 2 : 1, + ), new ChargingAttackMove(MoveId.SKY_DROP, PokemonType.FLYING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) .chargeText(i18next.t("moveTriggers:tookTargetIntoSky", { pokemonName: "{USER}", targetName: "{TARGET}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .affectedByGravity() - .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) + .condition((_user, target, _move) => !target.getTag(BattlerTagType.SUBSTITUTE)) /* * Cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/: * Should immobilize and give target semi-invulnerability @@ -10181,34 +10833,40 @@ export function initMoves() { */ .partial(), new SelfStatusMove(MoveId.SHIFT_GEAR, PokemonType.STEEL, -1, 10, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) - .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), + .attr(StatStageChangeAttr, [Stat.ATK], 1, true) + .attr(StatStageChangeAttr, [Stat.SPD], 2, true), new AttackMove(MoveId.CIRCLE_THROW, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) .attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH) .hidesTarget(), new AttackMove(MoveId.INCINERATE, PokemonType.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) .target(MoveTarget.ALL_NEAR_ENEMIES) .attr(RemoveHeldItemAttr, true) - .edgeCase(), // Should be able to remove items from pokemon with Sticky Hold if the damage causes them to faint + .edgeCase(), new StatusMove(MoveId.QUASH, PokemonType.DARK, 100, 15, -1, 0, 5) .condition(failIfSingleBattle) - .condition((user, target, move) => !target.turnData.acted) + .condition((_user, target, _move) => !target.turnData.acted) .attr(ForceLastAttr), - new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) - .attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))), - new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5) + new AttackMove(MoveId.ACROBATICS, PokemonType.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5) // + .attr(MovePowerMultiplierAttr, (user, _target, _move) => { + const itemCount = user + .getHeldItems() + .filter(i => i.isTransferable) + .reduce((v, m) => v + m.stackCount, 0); + return Math.max(1, 2 - 0.2 * itemCount); + }), + new StatusMove(MoveId.REFLECT_TYPE, PokemonType.NORMAL, -1, 15, -1, 0, 5) // .ignoresSubstitute() .attr(CopyTypeAttr), - new AttackMove(MoveId.RETALIATE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 5, -1, 0, 5) - .attr(MovePowerMultiplierAttr, (user, target, move) => { + new AttackMove(MoveId.RETALIATE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 70, 100, 5, -1, 0, 5) // + .attr(MovePowerMultiplierAttr, (user, _target, _move) => { const turn = globalScene.currentBattle.turn; - const lastPlayerFaint = globalScene.currentBattle.playerFaintsHistory[globalScene.currentBattle.playerFaintsHistory.length - 1]; - const lastEnemyFaint = globalScene.currentBattle.enemyFaintsHistory[globalScene.currentBattle.enemyFaintsHistory.length - 1]; - return ( - (lastPlayerFaint !== undefined && turn - lastPlayerFaint.turn === 1 && user.isPlayer()) || - (lastEnemyFaint !== undefined && turn - lastEnemyFaint.turn === 1 && user.isEnemy()) - ) ? 2 : 1; + const lastPlayerFaint = globalScene.currentBattle.playerFaintsHistory.at(-1); + const lastEnemyFaint = globalScene.currentBattle.enemyFaintsHistory.at(-1); + return (lastPlayerFaint !== undefined && turn - lastPlayerFaint.turn === 1 && user.isPlayer()) + || (lastEnemyFaint !== undefined && turn - lastEnemyFaint.turn === 1 && user.isEnemy()) + ? 2 + : 1; }), new AttackMove(MoveId.FINAL_GAMBIT, PokemonType.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5) .attr(UserHpDamageAttr) @@ -10217,7 +10875,7 @@ export function initMoves() { .ignoresProtect() .ignoresSubstitute() .unimplemented(), - new AttackMove(MoveId.INFERNO, PokemonType.FIRE, MoveCategory.SPECIAL, 100, 50, 5, 100, 0, 5) + new AttackMove(MoveId.INFERNO, PokemonType.FIRE, MoveCategory.SPECIAL, 100, 50, 5, 100, 0, 5) // .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(MoveId.WATER_PLEDGE, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5) .attr(AwaitCombinedPledgeAttr) @@ -10243,34 +10901,36 @@ export function initMoves() { .attr(AddPledgeEffectAttr, ArenaTagType.GRASS_WATER_PLEDGE, MoveId.WATER_PLEDGE) .attr(AddPledgeEffectAttr, ArenaTagType.FIRE_GRASS_PLEDGE, MoveId.FIRE_PLEDGE) .attr(BypassRedirectAttr, true), - new AttackMove(MoveId.VOLT_SWITCH, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5) + new AttackMove(MoveId.VOLT_SWITCH, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5) // .attr(ForceSwitchOutAttr, true), new AttackMove(MoveId.STRUGGLE_BUG, PokemonType.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1) + .attr(StatStageChangeAttr, [Stat.SPATK], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.BULLDOZE, PokemonType.GROUND, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1, + ) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), - new AttackMove(MoveId.FROST_BREATH, PokemonType.ICE, MoveCategory.SPECIAL, 60, 90, 10, -1, 0, 5) + new AttackMove(MoveId.FROST_BREATH, PokemonType.ICE, MoveCategory.SPECIAL, 60, 90, 10, -1, 0, 5) // .attr(CritOnlyAttr), new AttackMove(MoveId.DRAGON_TAIL, PokemonType.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) .attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH) .hidesTarget(), - new SelfStatusMove(MoveId.WORK_UP, PokemonType.NORMAL, -1, 30, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true), + new SelfStatusMove(MoveId.WORK_UP, PokemonType.NORMAL, -1, 30, -1, 0, 5) // + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], 1, true), new AttackMove(MoveId.ELECTROWEB, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.WILD_CHARGE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 5) .attr(RecoilAttr) .recklessMove(), - new AttackMove(MoveId.DRILL_RUN, PokemonType.GROUND, MoveCategory.PHYSICAL, 80, 95, 10, -1, 0, 5) + new AttackMove(MoveId.DRILL_RUN, PokemonType.GROUND, MoveCategory.PHYSICAL, 80, 95, 10, -1, 0, 5) // .attr(HighCritAttr), - new AttackMove(MoveId.DUAL_CHOP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 40, 90, 15, -1, 0, 5) + new AttackMove(MoveId.DUAL_CHOP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 40, 90, 15, -1, 0, 5) // .attr(MultiHitAttr, MultiHitType._2), - new AttackMove(MoveId.HEART_STAMP, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 5) + new AttackMove(MoveId.HEART_STAMP, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 5) // .attr(FlinchAttr), new AttackMove(MoveId.HORN_LEECH, PokemonType.GRASS, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 5) .attr(HitHealAttr) @@ -10279,25 +10939,25 @@ export function initMoves() { .attr(IgnoreOpponentStatStagesAttr) .slicingMove(), new AttackMove(MoveId.RAZOR_SHELL, PokemonType.WATER, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 5) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) + .attr(StatStageChangeAttr, [Stat.DEF], -1) .slicingMove(), new AttackMove(MoveId.HEAT_CRASH, PokemonType.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5) .attr(AlwaysHitMinimizeAttr) .attr(CompareWeightPowerAttr) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED), - new AttackMove(MoveId.LEAF_TORNADO, PokemonType.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1), + new AttackMove(MoveId.LEAF_TORNADO, PokemonType.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5) // + .attr(StatStageChangeAttr, [Stat.ACC], -1), new AttackMove(MoveId.STEAMROLLER, PokemonType.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5) .attr(AlwaysHitMinimizeAttr) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) .attr(FlinchAttr), - new SelfStatusMove(MoveId.COTTON_GUARD, PokemonType.GRASS, -1, 10, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.DEF ], 3, true), - new AttackMove(MoveId.NIGHT_DAZE, PokemonType.DARK, MoveCategory.SPECIAL, 85, 95, 10, 40, 0, 5) - .attr(StatStageChangeAttr, [ Stat.ACC ], -1), - new AttackMove(MoveId.PSYSTRIKE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 5) + new SelfStatusMove(MoveId.COTTON_GUARD, PokemonType.GRASS, -1, 10, -1, 0, 5) // + .attr(StatStageChangeAttr, [Stat.DEF], 3, true), + new AttackMove(MoveId.NIGHT_DAZE, PokemonType.DARK, MoveCategory.SPECIAL, 85, 95, 10, 40, 0, 5) // + .attr(StatStageChangeAttr, [Stat.ACC], -1), + new AttackMove(MoveId.PSYSTRIKE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 5) // .attr(DefDefAttr), - new AttackMove(MoveId.TAIL_SLAP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, 0, 5) + new AttackMove(MoveId.TAIL_SLAP, PokemonType.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, 0, 5) // .attr(MultiHitAttr), new AttackMove(MoveId.HURRICANE, PokemonType.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5) .attr(ThunderAccuracyAttr) @@ -10307,13 +10967,13 @@ export function initMoves() { new AttackMove(MoveId.HEAD_CHARGE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5) .attr(RecoilAttr) .recklessMove(), - new AttackMove(MoveId.GEAR_GRIND, PokemonType.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, 0, 5) + new AttackMove(MoveId.GEAR_GRIND, PokemonType.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, 0, 5) // .attr(MultiHitAttr, MultiHitType._2), new AttackMove(MoveId.SEARING_SHOT, PokemonType.FIRE, MoveCategory.SPECIAL, 100, 100, 5, 30, 0, 5) .attr(StatusEffectAttr, StatusEffect.BURN) .ballBombMove() .target(MoveTarget.ALL_NEAR_OTHERS), - new AttackMove(MoveId.TECHNO_BLAST, PokemonType.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 5) + new AttackMove(MoveId.TECHNO_BLAST, PokemonType.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 5) // .attr(TechnoBlastTypeAttr), new AttackMove(MoveId.RELIC_SONG, PokemonType.NORMAL, MoveCategory.SPECIAL, 75, 100, 10, 10, 0, 5) .attr(StatusEffectAttr, StatusEffect.SLEEP) @@ -10323,14 +10983,14 @@ export function initMoves() { .attr(DefDefAttr) .slicingMove(), new AttackMove(MoveId.GLACIATE, PokemonType.ICE, MoveCategory.SPECIAL, 65, 95, 10, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.BOLT_STRIKE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 130, 85, 5, 20, 0, 5) + new AttackMove(MoveId.BOLT_STRIKE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 130, 85, 5, 20, 0, 5) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), - new AttackMove(MoveId.BLUE_FLARE, PokemonType.FIRE, MoveCategory.SPECIAL, 130, 85, 5, 20, 0, 5) + new AttackMove(MoveId.BLUE_FLARE, PokemonType.FIRE, MoveCategory.SPECIAL, 130, 85, 5, 20, 0, 5) // .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(MoveId.FIERY_DANCE, PokemonType.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 50, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) + .attr(StatStageChangeAttr, [Stat.SPATK], 1, true) .danceMove(), new ChargingAttackMove(MoveId.FREEZE_SHOCK, PokemonType.ICE, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 5) .chargeText(i18next.t("moveTriggers:becameCloakedInFreezingLight", { pokemonName: "{USER}" })) @@ -10340,14 +11000,14 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:becameCloakedInFreezingAir", { pokemonName: "{USER}" })) .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(MoveId.SNARL, PokemonType.DARK, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1) + .attr(StatStageChangeAttr, [Stat.SPATK], -1) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.ICICLE_CRASH, PokemonType.ICE, MoveCategory.PHYSICAL, 85, 90, 10, 30, 0, 5) .attr(FlinchAttr) .makesContact(false), - new AttackMove(MoveId.V_CREATE, PokemonType.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, 0, 5) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF, Stat.SPD ], -1, true), + new AttackMove(MoveId.V_CREATE, PokemonType.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, 0, 5) // + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF, Stat.SPD], -1, true), new AttackMove(MoveId.FUSION_FLARE, PokemonType.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 5) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(LastMoveDoublePowerAttr, MoveId.FUSION_BOLT), @@ -10364,21 +11024,25 @@ export function initMoves() { .attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true) .condition(new FirstMoveCondition(), 3) .condition(failIfLastCondition, 3), - new AttackMove(MoveId.BELCH, PokemonType.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6) + new AttackMove(MoveId.BELCH, PokemonType.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6) // .restriction(user => !user.battleData.hasEatenBerry, "battle:moveDisabledBelch", true), new StatusMove(MoveId.ROTOTILLER, PokemonType.GROUND, -1, 10, -1, 0, 6) .target(MoveTarget.ALL) - .condition((user, target, move) => { + .condition((_user, _target, _move) => { // If any fielded pokémon is grass-type and grounded. - return [ ...globalScene.getEnemyParty(), ...globalScene.getPlayerParty() ].some((poke) => poke.isOfType(PokemonType.GRASS) && poke.isGrounded()); + return [...globalScene.getEnemyParty(), ...globalScene.getPlayerParty()].some( + poke => poke.isOfType(PokemonType.GRASS) && poke.isGrounded(), + ); }) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => target.isOfType(PokemonType.GRASS) && target.isGrounded() }), + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], 1, false, { + condition: (_user, target, _move) => target.isOfType(PokemonType.GRASS) && target.isGrounded(), + }), new StatusMove(MoveId.STICKY_WEB, PokemonType.BUG, -1, 20, -1, 0, 6) .attr(AddArenaTrapTagAttr, ArenaTagType.STICKY_WEB) .target(MoveTarget.ENEMY_SIDE) .reflectable(), - new AttackMove(MoveId.FELL_STINGER, PokemonType.BUG, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 6) - .attr(PostVictoryStatStageChangeAttr, [ Stat.ATK ], 3, true ), + new AttackMove(MoveId.FELL_STINGER, PokemonType.BUG, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 6) // + .attr(PostVictoryStatStageChangeAttr, [Stat.ATK], 3, true), new ChargingAttackMove(MoveId.PHANTOM_FORCE, PokemonType.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) @@ -10387,7 +11051,7 @@ export function initMoves() { .attr(AddTypeAttr, PokemonType.GHOST) .reflectable(), new StatusMove(MoveId.NOBLE_ROAR, PokemonType.NORMAL, 100, 30, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], -1) .soundBased() .reflectable(), new StatusMove(MoveId.ION_DELUGE, PokemonType.ELECTRIC, -1, 25, -1, 1, 6) @@ -10411,11 +11075,11 @@ export function initMoves() { .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), new StatusMove(MoveId.PARTING_SHOT, PokemonType.DARK, 100, 20, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, { trigger: MoveEffectTrigger.PRE_APPLY }) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], -1, false, { trigger: MoveEffectTrigger.PRE_APPLY }) .attr(ForceSwitchOutAttr, true) .soundBased() .reflectable(), - new StatusMove(MoveId.TOPSY_TURVY, PokemonType.DARK, -1, 20, -1, 0, 6) + new StatusMove(MoveId.TOPSY_TURVY, PokemonType.DARK, -1, 20, -1, 0, 6) // .attr(InvertStatsAttr) .reflectable(), new AttackMove(MoveId.DRAINING_KISS, PokemonType.FAIRY, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 6) @@ -10428,21 +11092,24 @@ export function initMoves() { .condition(failIfLastCondition, 3), new StatusMove(MoveId.FLOWER_SHIELD, PokemonType.FAIRY, -1, 10, -1, 0, 6) .target(MoveTarget.ALL) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, { condition: (user, target, move) => target.getTypes().includes(PokemonType.GRASS) && !target.getTag(SemiInvulnerableTag) }), + .attr(StatStageChangeAttr, [Stat.DEF], 1, false, { + condition: (_user, target, _move) => + target.getTypes().includes(PokemonType.GRASS) && !target.getTag(SemiInvulnerableTag), + }), new StatusMove(MoveId.GRASSY_TERRAIN, PokemonType.GRASS, -1, 10, -1, 0, 6) .attr(TerrainChangeAttr, TerrainType.GRASSY) .target(MoveTarget.BOTH_SIDES), new StatusMove(MoveId.MISTY_TERRAIN, PokemonType.FAIRY, -1, 10, -1, 0, 6) .attr(TerrainChangeAttr, TerrainType.MISTY) .target(MoveTarget.BOTH_SIDES), - new StatusMove(MoveId.ELECTRIFY, PokemonType.ELECTRIC, -1, 20, -1, 0, 6) + new StatusMove(MoveId.ELECTRIFY, PokemonType.ELECTRIC, -1, 20, -1, 0, 6) // .attr(AddBattlerTagAttr, BattlerTagType.ELECTRIFIED, false, true), - new AttackMove(MoveId.PLAY_ROUGH, PokemonType.FAIRY, MoveCategory.PHYSICAL, 90, 90, 10, 10, 0, 6) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), - new AttackMove(MoveId.FAIRY_WIND, PokemonType.FAIRY, MoveCategory.SPECIAL, 40, 100, 30, -1, 0, 6) + new AttackMove(MoveId.PLAY_ROUGH, PokemonType.FAIRY, MoveCategory.PHYSICAL, 90, 90, 10, 10, 0, 6) // + .attr(StatStageChangeAttr, [Stat.ATK], -1), + new AttackMove(MoveId.FAIRY_WIND, PokemonType.FAIRY, MoveCategory.SPECIAL, 40, 100, 30, -1, 0, 6) // .windMove(), - new AttackMove(MoveId.MOONBLAST, PokemonType.FAIRY, MoveCategory.SPECIAL, 95, 100, 15, 30, 0, 6) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1), + new AttackMove(MoveId.MOONBLAST, PokemonType.FAIRY, MoveCategory.SPECIAL, 95, 100, 15, 30, 0, 6) // + .attr(StatStageChangeAttr, [Stat.SPATK], -1), new AttackMove(MoveId.BOOMBURST, PokemonType.NORMAL, MoveCategory.SPECIAL, 140, 100, 10, -1, 0, 6) .soundBased() .target(MoveTarget.ALL_NEAR_OTHERS), @@ -10455,15 +11122,15 @@ export function initMoves() { .attr(ProtectAttr, BattlerTagType.KINGS_SHIELD) .condition(failIfLastCondition, 3), new StatusMove(MoveId.PLAY_NICE, PokemonType.NORMAL, -1, 20, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1) + .attr(StatStageChangeAttr, [Stat.ATK], -1) .ignoresSubstitute() .reflectable(), new StatusMove(MoveId.CONFIDE, PokemonType.NORMAL, -1, 20, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1) + .attr(StatStageChangeAttr, [Stat.SPATK], -1) .soundBased() .reflectable(), new AttackMove(MoveId.DIAMOND_STORM, PokemonType.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6) - .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true, { firstTargetOnly: true }) + .attr(StatStageChangeAttr, [Stat.DEF], 2, true, { firstTargetOnly: true }) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.STEAM_ERUPTION, PokemonType.WATER, MoveCategory.SPECIAL, 110, 95, 5, 30, 0, 6) @@ -10477,21 +11144,24 @@ export function initMoves() { .attr(MultiHitAttr) .attr(WaterShurikenPowerAttr) .attr(WaterShurikenMultiHitTypeAttr), - new AttackMove(MoveId.MYSTICAL_FIRE, PokemonType.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1), + new AttackMove(MoveId.MYSTICAL_FIRE, PokemonType.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6) // + .attr(StatStageChangeAttr, [Stat.SPATK], -1), new SelfStatusMove(MoveId.SPIKY_SHIELD, PokemonType.GRASS, -1, 10, -1, 4, 6) .attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD) .condition(failIfLastCondition, 3), new StatusMove(MoveId.AROMATIC_MIST, PokemonType.FAIRY, -1, 20, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1) + .attr(StatStageChangeAttr, [Stat.SPDEF], 1) .ignoresSubstitute() .condition(failIfSingleBattle) .target(MoveTarget.NEAR_ALLY), new StatusMove(MoveId.EERIE_IMPULSE, PokemonType.ELECTRIC, 100, 15, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2) + .attr(StatStageChangeAttr, [Stat.SPATK], -2) .reflectable(), new StatusMove(MoveId.VENOM_DRENCH, PokemonType.POISON, 100, 20, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, { condition: (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC }) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK, Stat.SPD], -1, false, { + condition: (_user, target, _move) => + target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC, + }) .target(MoveTarget.ALL_NEAR_ENEMIES) .reflectable(), new StatusMove(MoveId.POWDER, PokemonType.BUG, 100, 20, -1, 1, 6) @@ -10501,19 +11171,26 @@ export function initMoves() { .reflectable(), new ChargingSelfStatusMove(MoveId.GEOMANCY, PokemonType.FAIRY, -1, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" })) - .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true), + .attr(StatStageChangeAttr, [Stat.SPATK, Stat.SPDEF, Stat.SPD], 2, true), new StatusMove(MoveId.MAGNETIC_FLUX, PokemonType.ELECTRIC, -1, 20, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, { condition: (user, target, move) => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => target.hasAbility(a, false)) }) + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], 1, false, { + condition: (_user, target, _move) => !![AbilityId.PLUS, AbilityId.MINUS].find(a => target.hasAbility(a, false)), + }) .ignoresSubstitute() .target(MoveTarget.USER_AND_ALLIES) - .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => p?.hasAbility(a, false)))), + .condition( + (user, _target, _move) => + !![user, user.getAlly()] + .filter(p => p?.isActive()) + .find(p => !![AbilityId.PLUS, AbilityId.MINUS].find(a => p?.hasAbility(a, false))), + ), new StatusMove(MoveId.HAPPY_HOUR, PokemonType.NORMAL, -1, 30, -1, 0, 6) // No animation .attr(AddArenaTagAttr, ArenaTagType.HAPPY_HOUR, 0, true) .target(MoveTarget.USER_SIDE), new StatusMove(MoveId.ELECTRIC_TERRAIN, PokemonType.ELECTRIC, -1, 10, -1, 0, 6) .attr(TerrainChangeAttr, TerrainType.ELECTRIC) .target(MoveTarget.BOTH_SIDES), - new AttackMove(MoveId.DAZZLING_GLEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6) + new AttackMove(MoveId.DAZZLING_GLEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6) // .target(MoveTarget.ALL_NEAR_ENEMIES), new SelfStatusMove(MoveId.CELEBRATE, PokemonType.NORMAL, -1, 40, -1, 0, 6) // NB: This needs a lambda function as the user will not be logged in by the time the moves are initialized @@ -10522,17 +11199,17 @@ export function initMoves() { .ignoresSubstitute() .target(MoveTarget.NEAR_ALLY), new StatusMove(MoveId.BABY_DOLL_EYES, PokemonType.FAIRY, 100, 30, -1, 1, 6) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1) + .attr(StatStageChangeAttr, [Stat.ATK], -1) .reflectable(), - new AttackMove(MoveId.NUZZLE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 20, 100, 20, 100, 0, 6) + new AttackMove(MoveId.NUZZLE, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 20, 100, 20, 100, 0, 6) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), - new AttackMove(MoveId.HOLD_BACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6) + new AttackMove(MoveId.HOLD_BACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6) // .attr(SurviveDamageAttr), new AttackMove(MoveId.INFESTATION, PokemonType.BUG, MoveCategory.SPECIAL, 20, 100, 20, -1, 0, 6) .makesContact() .attr(TrapAttr, BattlerTagType.INFESTATION), new AttackMove(MoveId.POWER_UP_PUNCH, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 20, 100, 0, 6) - .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK], 1, true) .punchingMove(), new AttackMove(MoveId.OBLIVION_WING, PokemonType.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6) .attr(HitHealAttr, 0.75) @@ -10543,7 +11220,7 @@ export function initMoves() { .attr(HitsTagAttr, BattlerTagType.FLYING) .attr(HitsTagAttr, BattlerTagType.FLOATING) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) + .attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS]) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.THOUSAND_WAVES, PokemonType.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 6) @@ -10562,93 +11239,95 @@ export function initMoves() { new AttackMove(MoveId.PRECIPICE_BLADES, PokemonType.GROUND, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 6) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.DRAGON_ASCENT, PokemonType.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), + new AttackMove(MoveId.DRAGON_ASCENT, PokemonType.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 6) // + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], -1, true), new AttackMove(MoveId.HYPERSPACE_FURY, PokemonType.DARK, MoveCategory.PHYSICAL, 100, -1, 5, -1, 0, 6) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true) + .attr(StatStageChangeAttr, [Stat.DEF], -1, true) .ignoresSubstitute() .makesContact(false) .ignoresProtect(), /* Unused */ - new AttackMove(MoveId.BREAKNECK_BLITZ__PHYSICAL, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.BREAKNECK_BLITZ__PHYSICAL, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.BREAKNECK_BLITZ__SPECIAL, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.BREAKNECK_BLITZ__SPECIAL, PokemonType.NORMAL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.ALL_OUT_PUMMELING__PHYSICAL, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.ALL_OUT_PUMMELING__PHYSICAL, PokemonType.FIGHTING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.ALL_OUT_PUMMELING__SPECIAL, PokemonType.FIGHTING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.ALL_OUT_PUMMELING__SPECIAL, PokemonType.FIGHTING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), + // biome-ignore format: slightly too long new AttackMove(MoveId.SUPERSONIC_SKYSTRIKE__PHYSICAL, PokemonType.FLYING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) .unimplemented(), - new AttackMove(MoveId.SUPERSONIC_SKYSTRIKE__SPECIAL, PokemonType.FLYING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SUPERSONIC_SKYSTRIKE__SPECIAL, PokemonType.FLYING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.ACID_DOWNPOUR__PHYSICAL, PokemonType.POISON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.ACID_DOWNPOUR__PHYSICAL, PokemonType.POISON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.ACID_DOWNPOUR__SPECIAL, PokemonType.POISON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.ACID_DOWNPOUR__SPECIAL, PokemonType.POISON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.TECTONIC_RAGE__PHYSICAL, PokemonType.GROUND, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.TECTONIC_RAGE__PHYSICAL, PokemonType.GROUND, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.TECTONIC_RAGE__SPECIAL, PokemonType.GROUND, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.TECTONIC_RAGE__SPECIAL, PokemonType.GROUND, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.CONTINENTAL_CRUSH__PHYSICAL, PokemonType.ROCK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.CONTINENTAL_CRUSH__PHYSICAL, PokemonType.ROCK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.CONTINENTAL_CRUSH__SPECIAL, PokemonType.ROCK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.CONTINENTAL_CRUSH__SPECIAL, PokemonType.ROCK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SAVAGE_SPIN_OUT__PHYSICAL, PokemonType.BUG, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SAVAGE_SPIN_OUT__PHYSICAL, PokemonType.BUG, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SAVAGE_SPIN_OUT__SPECIAL, PokemonType.BUG, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SAVAGE_SPIN_OUT__SPECIAL, PokemonType.BUG, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), + // biome-ignore format: slightly too long new AttackMove(MoveId.NEVER_ENDING_NIGHTMARE__PHYSICAL, PokemonType.GHOST, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) .unimplemented(), - new AttackMove(MoveId.NEVER_ENDING_NIGHTMARE__SPECIAL, PokemonType.GHOST, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.NEVER_ENDING_NIGHTMARE__SPECIAL, PokemonType.GHOST, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.CORKSCREW_CRASH__PHYSICAL, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.CORKSCREW_CRASH__PHYSICAL, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.CORKSCREW_CRASH__SPECIAL, PokemonType.STEEL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.CORKSCREW_CRASH__SPECIAL, PokemonType.STEEL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.INFERNO_OVERDRIVE__PHYSICAL, PokemonType.FIRE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.INFERNO_OVERDRIVE__PHYSICAL, PokemonType.FIRE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.INFERNO_OVERDRIVE__SPECIAL, PokemonType.FIRE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.INFERNO_OVERDRIVE__SPECIAL, PokemonType.FIRE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.HYDRO_VORTEX__PHYSICAL, PokemonType.WATER, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.HYDRO_VORTEX__PHYSICAL, PokemonType.WATER, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.HYDRO_VORTEX__SPECIAL, PokemonType.WATER, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.HYDRO_VORTEX__SPECIAL, PokemonType.WATER, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.BLOOM_DOOM__PHYSICAL, PokemonType.GRASS, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.BLOOM_DOOM__PHYSICAL, PokemonType.GRASS, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.BLOOM_DOOM__SPECIAL, PokemonType.GRASS, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.BLOOM_DOOM__SPECIAL, PokemonType.GRASS, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.GIGAVOLT_HAVOC__PHYSICAL, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.GIGAVOLT_HAVOC__PHYSICAL, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.GIGAVOLT_HAVOC__SPECIAL, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.GIGAVOLT_HAVOC__SPECIAL, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SHATTERED_PSYCHE__PHYSICAL, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SHATTERED_PSYCHE__PHYSICAL, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SHATTERED_PSYCHE__SPECIAL, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SHATTERED_PSYCHE__SPECIAL, PokemonType.PSYCHIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SUBZERO_SLAMMER__PHYSICAL, PokemonType.ICE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SUBZERO_SLAMMER__PHYSICAL, PokemonType.ICE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SUBZERO_SLAMMER__SPECIAL, PokemonType.ICE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SUBZERO_SLAMMER__SPECIAL, PokemonType.ICE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.DEVASTATING_DRAKE__PHYSICAL, PokemonType.DRAGON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.DEVASTATING_DRAKE__PHYSICAL, PokemonType.DRAGON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.DEVASTATING_DRAKE__SPECIAL, PokemonType.DRAGON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.DEVASTATING_DRAKE__SPECIAL, PokemonType.DRAGON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.BLACK_HOLE_ECLIPSE__PHYSICAL, PokemonType.DARK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.BLACK_HOLE_ECLIPSE__PHYSICAL, PokemonType.DARK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.BLACK_HOLE_ECLIPSE__SPECIAL, PokemonType.DARK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.BLACK_HOLE_ECLIPSE__SPECIAL, PokemonType.DARK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.TWINKLE_TACKLE__PHYSICAL, PokemonType.FAIRY, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.TWINKLE_TACKLE__PHYSICAL, PokemonType.FAIRY, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.TWINKLE_TACKLE__SPECIAL, PokemonType.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.TWINKLE_TACKLE__SPECIAL, PokemonType.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.CATASTROPIKA, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) + new AttackMove(MoveId.CATASTROPIKA, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) // .unimplemented(), /* End Unused */ new SelfStatusMove(MoveId.SHORE_UP, PokemonType.GROUND, -1, 5, -1, 0, 7) .attr(SandHealAttr) .triageMove(), - new AttackMove(MoveId.FIRST_IMPRESSION, PokemonType.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7) + new AttackMove(MoveId.FIRST_IMPRESSION, PokemonType.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7) // .condition(new FirstMoveCondition(), 3), new SelfStatusMove(MoveId.BANEFUL_BUNKER, PokemonType.POISON, -1, 10, -1, 4, 7) .attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER) @@ -10656,32 +11335,39 @@ export function initMoves() { new AttackMove(MoveId.SPIRIT_SHACKLE, PokemonType.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true) .makesContact(false), - new AttackMove(MoveId.DARKEST_LARIAT, PokemonType.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7) + new AttackMove(MoveId.DARKEST_LARIAT, PokemonType.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7) // .attr(IgnoreOpponentStatStagesAttr), new AttackMove(MoveId.SPARKLING_ARIA, PokemonType.WATER, MoveCategory.SPECIAL, 90, 100, 10, 100, 0, 7) .attr(HealStatusEffectAttr, false, StatusEffect.BURN) .soundBased() .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(MoveId.ICE_HAMMER, PokemonType.ICE, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1, true) + .attr(StatStageChangeAttr, [Stat.SPD], -1, true) .punchingMove(), new StatusMove(MoveId.FLORAL_HEALING, PokemonType.FAIRY, -1, 10, -1, 0, 7) - .attr(BoostHealAttr, 0.5, 2 / 3, true, false, (user, target, move) => globalScene.arena.terrain?.terrainType === TerrainType.GRASSY) + .attr( + BoostHealAttr, + 0.5, + 2 / 3, + true, + false, + (_user, _target, _move) => globalScene.arena.terrain?.terrainType === TerrainType.GRASSY, + ) .triageMove() .reflectable(), new AttackMove(MoveId.HIGH_HORSEPOWER, PokemonType.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7), new StatusMove(MoveId.STRENGTH_SAP, PokemonType.GRASS, 100, 10, -1, 0, 7) .attr(HitHealAttr, null, Stat.ATK) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1) - .condition((user, target, move) => target.getStatStage(Stat.ATK) > -6) + .attr(StatStageChangeAttr, [Stat.ATK], -1) + .condition((_user, target, _move) => target.getStatStage(Stat.ATK) > -6) .triageMove() .reflectable(), new ChargingAttackMove(MoveId.SOLAR_BLADE, PokemonType.GRASS, MoveCategory.PHYSICAL, 125, 100, 10, -1, 0, 7) .chargeText(i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) - .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]) + .chargeAttr(WeatherInstantChargeAttr, [WeatherType.SUNNY, WeatherType.HARSH_SUN]) .attr(AntiSunlightPowerDecreaseAttr) .slicingMove(), - new AttackMove(MoveId.LEAFAGE, PokemonType.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 7) + new AttackMove(MoveId.LEAFAGE, PokemonType.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 7) // .makesContact(false), new StatusMove(MoveId.SPOTLIGHT, PokemonType.NORMAL, -1, 15, -1, 3, 7) .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false) @@ -10689,46 +11375,58 @@ export function initMoves() { .reflectable(), new StatusMove(MoveId.TOXIC_THREAD, PokemonType.POISON, 100, 20, -1, 0, 7) .attr(StatusEffectAttr, StatusEffect.POISON) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .reflectable(), new SelfStatusMove(MoveId.LASER_FOCUS, PokemonType.NORMAL, -1, 30, -1, 0, 7) .attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false) - .attr(MessageAttr, (user) => + .attr(MessageAttr, user => i18next.t("battlerTags:laserFocusOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), }), ), new StatusMove(MoveId.GEAR_UP, PokemonType.STEEL, -1, 20, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => target.hasAbility(a, false)) }) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], 1, false, { + condition: (_user, target, _move) => !![AbilityId.PLUS, AbilityId.MINUS].find(a => target.hasAbility(a, false)), + }) .ignoresSubstitute() .target(MoveTarget.USER_AND_ALLIES) - .condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ AbilityId.PLUS, AbilityId.MINUS ].find(a => p?.hasAbility(a, false)))), - new AttackMove(MoveId.THROAT_CHOP, PokemonType.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) + .condition( + (user, _target, _move) => + !![user, user.getAlly()] + .filter(p => p?.isActive()) + .find(p => !![AbilityId.PLUS, AbilityId.MINUS].find(a => p?.hasAbility(a, false))), + ), + new AttackMove(MoveId.THROAT_CHOP, PokemonType.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) // .attr(AddBattlerTagAttr, BattlerTagType.THROAT_CHOPPED), new AttackMove(MoveId.POLLEN_PUFF, PokemonType.BUG, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7) .attr(StatusCategoryOnAllyAttr) .attr(HealOnAllyAttr, 0.5, true, false) .ballBombMove() // Fail if used against an ally that is affected by heal block, during the second failure check - .condition((user, target) => target == null || target.isOpponent(user) || !target.getTag(BattlerTagType.HEAL_BLOCK), 2), - new AttackMove(MoveId.ANCHOR_SHOT, PokemonType.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7) + .condition( + (user, target) => target == null || target.isOpponent(user) || !target.getTag(BattlerTagType.HEAL_BLOCK), + 2, + ), + new AttackMove(MoveId.ANCHOR_SHOT, PokemonType.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7) // .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true), new StatusMove(MoveId.PSYCHIC_TERRAIN, PokemonType.PSYCHIC, -1, 10, -1, 0, 7) .attr(TerrainChangeAttr, TerrainType.PSYCHIC) .target(MoveTarget.BOTH_SIDES), - new AttackMove(MoveId.LUNGE, PokemonType.BUG, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), - new AttackMove(MoveId.FIRE_LASH, PokemonType.FIRE, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1), - new AttackMove(MoveId.POWER_TRIP, PokemonType.DARK, MoveCategory.PHYSICAL, 20, 100, 10, -1, 0, 7) + new AttackMove(MoveId.LUNGE, PokemonType.BUG, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) // + .attr(StatStageChangeAttr, [Stat.ATK], -1), + new AttackMove(MoveId.FIRE_LASH, PokemonType.FIRE, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7) // + .attr(StatStageChangeAttr, [Stat.DEF], -1), + new AttackMove(MoveId.POWER_TRIP, PokemonType.DARK, MoveCategory.PHYSICAL, 20, 100, 10, -1, 0, 7) // .attr(PositiveStatStagePowerAttr), new AttackMove(MoveId.BURN_UP, PokemonType.FIRE, MoveCategory.SPECIAL, 130, 100, 5, -1, 0, 7) // Pass `true` to `ForDefend` as it should fail if the user is terastallized to a type that is not FIRE .condition(user => user.isOfType(PokemonType.FIRE, true, true), 2) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(AddBattlerTagAttr, BattlerTagType.BURNED_UP, true, false) - .attr(RemoveTypeAttr, PokemonType.FIRE, (user) => { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:burnedItselfOut", { pokemonName: getPokemonNameWithAffix(user) })); + .attr(RemoveTypeAttr, PokemonType.FIRE, user => { + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:burnedItselfOut", { pokemonName: getPokemonNameWithAffix(user) }), + ); }), new StatusMove(MoveId.SPEED_SWAP, PokemonType.PSYCHIC, -1, 10, -1, 0, 7) // Note: the 3 is NOT a typo; unlike power split / guard split which happen in the second failure sequence, speed @@ -10739,7 +11437,7 @@ export function initMoves() { .ignoresSubstitute(), new AttackMove(MoveId.SMART_STRIKE, PokemonType.STEEL, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 7), new StatusMove(MoveId.PURIFY, PokemonType.POISON, -1, 20, -1, 0, 7) - .condition((user, target, move) => { + .condition((_user, target, _move) => { if (!target.status) { return false; } @@ -10755,8 +11453,8 @@ export function initMoves() { new AttackMove(MoveId.CORE_ENFORCER, PokemonType.DRAGON, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 7) .target(MoveTarget.ALL_NEAR_ENEMIES) .attr(SuppressAbilitiesIfActedAttr), - new AttackMove(MoveId.TROP_KICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, 100, 15, 100, 0, 7) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), + new AttackMove(MoveId.TROP_KICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, 100, 15, 100, 0, 7) // + .attr(StatStageChangeAttr, [Stat.ATK], -1), new StatusMove(MoveId.INSTRUCT, PokemonType.PSYCHIC, -1, 15, -1, 0, 7) .ignoresSubstitute() .attr(RepeatMoveAttr) @@ -10772,23 +11470,20 @@ export function initMoves() { .ballBombMove() .makesContact(false), new AttackMove(MoveId.CLANGING_SCALES, PokemonType.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, { firstTargetOnly: true }) + .attr(StatStageChangeAttr, [Stat.DEF], -1, true, { firstTargetOnly: true }) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.DRAGON_HAMMER, PokemonType.DRAGON, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 7), - new AttackMove(MoveId.BRUTAL_SWING, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 7) + new AttackMove(MoveId.BRUTAL_SWING, PokemonType.DARK, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 7) // .target(MoveTarget.ALL_NEAR_OTHERS), new StatusMove(MoveId.AURORA_VEIL, PokemonType.ICE, -1, 20, -1, 0, 7) - .condition( - () => { - const weather = globalScene.arena.weather; - if (weather == null || weather.isEffectSuppressed()) { - return false; - } - return weather.weatherType === WeatherType.HAIL || weather.weatherType === WeatherType.SNOW; - }, - 3 - ) + .condition(() => { + const weather = globalScene.arena.weather; + if (weather == null || weather.isEffectSuppressed()) { + return false; + } + return weather.weatherType === WeatherType.HAIL || weather.weatherType === WeatherType.SNOW; + }, 3) .attr(AddArenaTagAttr, ArenaTagType.AURORA_VEIL, 5, true) .target(MoveTarget.USER_SIDE), /* Unused */ @@ -10804,9 +11499,9 @@ export function initMoves() { new AttackMove(MoveId.OCEANIC_OPERETTA, PokemonType.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) .unimplemented() .edgeCase(), // I assume it's because it needs sparkling aria and primarina - new AttackMove(MoveId.GUARDIAN_OF_ALOLA, PokemonType.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) + new AttackMove(MoveId.GUARDIAN_OF_ALOLA, PokemonType.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) // .unimplemented(), - new AttackMove(MoveId.SOUL_STEALING_7_STAR_STRIKE, PokemonType.GHOST, MoveCategory.PHYSICAL, 195, -1, 1, -1, 0, 7) + new AttackMove(MoveId.SOUL_STEALING_7_STAR_STRIKE, PokemonType.GHOST, MoveCategory.PHYSICAL, 195, -1, 1, -1, 0, 7) // .unimplemented(), new AttackMove(MoveId.STOKED_SPARKSURFER, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7) .unimplemented() @@ -10816,7 +11511,7 @@ export function initMoves() { .edgeCase(), // I assume it's because it needs giga impact and snorlax new SelfStatusMove(MoveId.EXTREME_EVOBOOST, PokemonType.NORMAL, -1, 1, -1, 0, 7) .unimplemented() - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true), + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 2, true), new AttackMove(MoveId.GENESIS_SUPERNOVA, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7) .unimplemented() .attr(TerrainChangeAttr, TerrainType.PSYCHIC), @@ -10826,44 +11521,48 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES) // Fails if the user was not hit by a physical attack during the turn .condition(user => user.getTag(ShellTrapTag)?.activated === true, 3), - new AttackMove(MoveId.FLEUR_CANNON, PokemonType.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), + new AttackMove(MoveId.FLEUR_CANNON, PokemonType.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7) // + .attr(StatStageChangeAttr, [Stat.SPATK], -2, true), new AttackMove(MoveId.PSYCHIC_FANGS, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7) .bitingMove() .attr(RemoveScreensAttr), new AttackMove(MoveId.STOMPING_TANTRUM, PokemonType.GROUND, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 7) - .attr(MovePowerMultiplierAttr, (user) => { + .attr(MovePowerMultiplierAttr, user => { // Stomping tantrum triggers on most failures (including sleep/freeze) const lastNonDancerMove = user.getLastXMoves(2)[1] as TurnMove | undefined; - return lastNonDancerMove && (lastNonDancerMove.result === MoveResult.MISS || lastNonDancerMove.result === MoveResult.FAIL) ? 2 : 1 + return lastNonDancerMove + && (lastNonDancerMove.result === MoveResult.MISS || lastNonDancerMove.result === MoveResult.FAIL) + ? 2 + : 1; }) // TODO: Review mainline accuracy and draft tests as needed .edgeCase(), new AttackMove(MoveId.SHADOW_BONE, PokemonType.GHOST, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) + .attr(StatStageChangeAttr, [Stat.DEF], -1) .makesContact(false), new AttackMove(MoveId.ACCELEROCK, PokemonType.ROCK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 1, 7), - new AttackMove(MoveId.LIQUIDATION, PokemonType.WATER, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1), - new AttackMove(MoveId.PRISMATIC_LASER, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 160, 100, 10, -1, 0, 7) + new AttackMove(MoveId.LIQUIDATION, PokemonType.WATER, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7) // + .attr(StatStageChangeAttr, [Stat.DEF], -1), + new AttackMove(MoveId.PRISMATIC_LASER, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 160, 100, 10, -1, 0, 7) // .attr(RechargeAttr), new AttackMove(MoveId.SPECTRAL_THIEF, PokemonType.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7) .attr(SpectralThiefAttr) .ignoresSubstitute(), - new AttackMove(MoveId.SUNSTEEL_STRIKE, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7) + new AttackMove(MoveId.SUNSTEEL_STRIKE, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7) // .ignoresAbilities(), - new AttackMove(MoveId.MOONGEIST_BEAM, PokemonType.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) + new AttackMove(MoveId.MOONGEIST_BEAM, PokemonType.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) // .ignoresAbilities(), new StatusMove(MoveId.TEARFUL_LOOK, PokemonType.NORMAL, -1, 20, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], -1) .reflectable(), - new AttackMove(MoveId.ZING_ZAP, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7) + new AttackMove(MoveId.ZING_ZAP, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7) // .attr(FlinchAttr), - new AttackMove(MoveId.NATURES_MADNESS, PokemonType.FAIRY, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 7) + new AttackMove(MoveId.NATURES_MADNESS, PokemonType.FAIRY, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 7) // .attr(TargetHalfHpDamageAttr), - new AttackMove(MoveId.MULTI_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 7) + new AttackMove(MoveId.MULTI_ATTACK, PokemonType.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 7) // .attr(FormChangeItemTypeAttr), /* Unused */ + // biome-ignore format: slightly too long new AttackMove(MoveId.TEN_MILLION_VOLT_THUNDERBOLT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) .unimplemented() .edgeCase(), // I assume it's because it needs thunderbolt and pikachu in a cap @@ -10898,7 +11597,9 @@ export function initMoves() { .makesContact(false), new AttackMove(MoveId.CLANGOROUS_SOULBLAZE, PokemonType.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7) .unimplemented() - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, { firstTargetOnly: true }) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, true, { + firstTargetOnly: true, + }) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES) .edgeCase(), // I assume it needs clanging scales and Kommo-O @@ -10911,27 +11612,27 @@ export function initMoves() { new AttackMove(MoveId.FLOATY_FALL, PokemonType.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, 30, 0, 7) .attr(FlinchAttr) .affectedByGravity(), - new AttackMove(MoveId.PIKA_PAPOW, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7) + new AttackMove(MoveId.PIKA_PAPOW, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7) // .attr(FriendshipPowerAttr), new AttackMove(MoveId.BOUNCY_BUBBLE, PokemonType.WATER, MoveCategory.SPECIAL, 60, 100, 20, -1, 0, 7) .attr(HitHealAttr, 1) .triageMove(), - new AttackMove(MoveId.BUZZY_BUZZ, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7) + new AttackMove(MoveId.BUZZY_BUZZ, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7) // .attr(StatusEffectAttr, StatusEffect.PARALYSIS), - new AttackMove(MoveId.SIZZLY_SLIDE, PokemonType.FIRE, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 7) + new AttackMove(MoveId.SIZZLY_SLIDE, PokemonType.FIRE, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 7) // .attr(StatusEffectAttr, StatusEffect.BURN), - new AttackMove(MoveId.GLITZY_GLOW, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7) + new AttackMove(MoveId.GLITZY_GLOW, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7) // .attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, false, true), - new AttackMove(MoveId.BADDY_BAD, PokemonType.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7) + new AttackMove(MoveId.BADDY_BAD, PokemonType.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7) // .attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, false, true), new AttackMove(MoveId.SAPPY_SEED, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7) .attr(LeechSeedAttr) .makesContact(false), - new AttackMove(MoveId.FREEZY_FROST, PokemonType.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7) + new AttackMove(MoveId.FREEZY_FROST, PokemonType.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7) // .attr(ResetStatsAttr, true), - new AttackMove(MoveId.SPARKLY_SWIRL, PokemonType.FAIRY, MoveCategory.SPECIAL, 120, 85, 5, -1, 0, 7) + new AttackMove(MoveId.SPARKLY_SWIRL, PokemonType.FAIRY, MoveCategory.SPECIAL, 120, 85, 5, -1, 0, 7) // .attr(PartyStatusCureAttr, null, AbilityId.NONE), - new AttackMove(MoveId.VEEVEE_VOLLEY, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 20, -1, 0, 7) + new AttackMove(MoveId.VEEVEE_VOLLEY, PokemonType.NORMAL, MoveCategory.PHYSICAL, -1, -1, 20, -1, 0, 7) // .attr(FriendshipPowerAttr), new AttackMove(MoveId.DOUBLE_IRON_BASH, PokemonType.STEEL, MoveCategory.PHYSICAL, 60, 100, 5, 30, 0, 7) .attr(MultiHitAttr, MultiHitType._2) @@ -10943,16 +11644,18 @@ export function initMoves() { .attr(ProtectAttr) .condition(failIfLastCondition), /* End Unused */ - new AttackMove(MoveId.DYNAMAX_CANNON, PokemonType.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (user, target, move) => { - // Move is only stronger against overleveled foes. + new AttackMove(MoveId.DYNAMAX_CANNON, PokemonType.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) // + .attr(MovePowerMultiplierAttr, (_user, target, _move) => { + // Move is only stronger against overleveled foes. if (target.level > globalScene.getMaxExpLevel()) { - const dynamaxCannonPercentMarginBeforeFullDamage = 0.05; // How much % above MaxExpLevel of wave will the target need to be to take full damage. + // How much % above MaxExpLevel of wave will the target need to be to take full damage. + const dynamaxCannonPercentMarginBeforeFullDamage = 0.05; + const overLevel = target.level - globalScene.getMaxExpLevel(); + const damageFactor = globalScene.getMaxExpLevel() * dynamaxCannonPercentMarginBeforeFullDamage; // The move's power scales as the margin is approached, reaching double power when it does or goes over it. - return 1 + Math.min(1, (target.level - globalScene.getMaxExpLevel()) / (globalScene.getMaxExpLevel() * dynamaxCannonPercentMarginBeforeFullDamage)); - } else { - return 1; + return 1 + Math.min(1, overLevel / damageFactor); } + return 1; }), new AttackMove(MoveId.SNIPE_SHOT, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8) @@ -10963,20 +11666,20 @@ export function initMoves() { .bitingMove(), new SelfStatusMove(MoveId.STUFF_CHEEKS, PokemonType.NORMAL, -1, 10, -1, 0, 8) .attr(EatBerryAttr, true) - .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) + .attr(StatStageChangeAttr, [Stat.DEF], 2, true) .restriction( user => globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()).length === 0, "battle:moveDisabledNoBerry", true, - 3 + 3, ), new SelfStatusMove(MoveId.NO_RETREAT, PokemonType.FIGHTING, -1, 5, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, true /* NOT ADDED if already trapped */) // fails if the user is currently trapped specifically from no retreat .condition(user => user.getTag(TrappedTag)?.tagType !== BattlerTagType.NO_RETREAT, 2), new StatusMove(MoveId.TAR_SHOT, PokemonType.ROCK, 100, 15, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .attr(AddBattlerTagAttr, BattlerTagType.TAR_SHOT, false) .reflectable(), new StatusMove(MoveId.MAGIC_POWDER, PokemonType.PSYCHIC, 100, 20, -1, 0, 8) @@ -10993,13 +11696,27 @@ export function initMoves() { new StatusMove(MoveId.OCTOLOCK, PokemonType.FIGHTING, 100, 15, -1, 0, 8) .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1), - new AttackMove(MoveId.BOLT_BEAK, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (_user, target) => target.turnData.acted ? 1 : 2), + new AttackMove(MoveId.BOLT_BEAK, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8) // + .attr(MovePowerMultiplierAttr, (_user, target) => (target.turnData.acted ? 1 : 2)), new AttackMove(MoveId.FISHIOUS_REND, PokemonType.WATER, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (_user, target) => target.turnData.acted ? 1 : 2) + .attr(MovePowerMultiplierAttr, (_user, target) => (target.turnData.acted ? 1 : 2)) .bitingMove(), - new StatusMove(MoveId.COURT_CHANGE, PokemonType.NORMAL, 100, 10, -1, 0, 8) - .attr(SwapArenaTagsAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.MIST, ArenaTagType.REFLECT, ArenaTagType.SPIKES, ArenaTagType.STEALTH_ROCK, ArenaTagType.STICKY_WEB, ArenaTagType.TAILWIND, ArenaTagType.TOXIC_SPIKES, ArenaTagType.SAFEGUARD, ArenaTagType.FIRE_GRASS_PLEDGE, ArenaTagType.WATER_FIRE_PLEDGE, ArenaTagType.GRASS_WATER_PLEDGE ]), + new StatusMove(MoveId.COURT_CHANGE, PokemonType.NORMAL, 100, 10, -1, 0, 8) // + .attr(SwapArenaTagsAttr, [ + ArenaTagType.AURORA_VEIL, + ArenaTagType.LIGHT_SCREEN, + ArenaTagType.MIST, + ArenaTagType.REFLECT, + ArenaTagType.SPIKES, + ArenaTagType.STEALTH_ROCK, + ArenaTagType.STICKY_WEB, + ArenaTagType.TAILWIND, + ArenaTagType.TOXIC_SPIKES, + ArenaTagType.SAFEGUARD, + ArenaTagType.FIRE_GRASS_PLEDGE, + ArenaTagType.WATER_FIRE_PLEDGE, + ArenaTagType.GRASS_WATER_PLEDGE, + ]), /* Unused */ new AttackMove(MoveId.MAX_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) @@ -11057,48 +11774,50 @@ export function initMoves() { .unimplemented(), /* End Unused */ new SelfStatusMove(MoveId.CLANGOROUS_SOUL, PokemonType.DRAGON, 100, 5, -1, 0, 8) - .attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, 3) + .attr(CutHpStatStageBoostAttr, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1, 3) .soundBased() .danceMove() .condition(new FailIfInsufficientHpCondition(3), 3), - new AttackMove(MoveId.BODY_PRESS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8) + new AttackMove(MoveId.BODY_PRESS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8) // .attr(DefAtkAttr), new StatusMove(MoveId.DECORATE, PokemonType.FAIRY, -1, 15, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 2) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], 2) .ignoresProtect(), new AttackMove(MoveId.DRUM_BEATING, PokemonType.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .makesContact(false), - new AttackMove(MoveId.SNAP_TRAP, PokemonType.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, -1, 0, 8) + new AttackMove(MoveId.SNAP_TRAP, PokemonType.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, -1, 0, 8) // .attr(TrapAttr, BattlerTagType.SNAP_TRAP), new AttackMove(MoveId.PYRO_BALL, PokemonType.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, 10, 0, 8) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(StatusEffectAttr, StatusEffect.BURN) .ballBombMove() .makesContact(false), - new AttackMove(MoveId.BEHEMOTH_BLADE, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 8) + new AttackMove(MoveId.BEHEMOTH_BLADE, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 8) // .slicingMove(), new AttackMove(MoveId.BEHEMOTH_BASH, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 8), new AttackMove(MoveId.AURA_WHEEL, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.SPD], 1, true) .makesContact(false) .attr(AuraWheelTypeAttr), new AttackMove(MoveId.BREAKING_SWIPE, PokemonType.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8) .target(MoveTarget.ALL_NEAR_ENEMIES) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), + .attr(StatStageChangeAttr, [Stat.ATK], -1), new AttackMove(MoveId.BRANCH_POKE, PokemonType.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 8), new AttackMove(MoveId.OVERDRIVE, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.APPLE_ACID, PokemonType.GRASS, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1), + new AttackMove(MoveId.APPLE_ACID, PokemonType.GRASS, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -1), new AttackMove(MoveId.GRAV_APPLE, PokemonType.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1) + .attr(StatStageChangeAttr, [Stat.DEF], -1) + .attr(MovePowerMultiplierAttr, (_user, _target, _move) => + globalScene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1, + ) .makesContact(false), - new AttackMove(MoveId.SPIRIT_BREAK, PokemonType.FAIRY, MoveCategory.PHYSICAL, 75, 100, 15, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1), - new AttackMove(MoveId.STRANGE_STEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 90, 95, 10, 20, 0, 8) + new AttackMove(MoveId.SPIRIT_BREAK, PokemonType.FAIRY, MoveCategory.PHYSICAL, 75, 100, 15, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.SPATK], -1), + new AttackMove(MoveId.STRANGE_STEAM, PokemonType.FAIRY, MoveCategory.SPECIAL, 90, 95, 10, 20, 0, 8) // .attr(ConfuseAttr), new StatusMove(MoveId.LIFE_DEW, PokemonType.WATER, -1, 10, -1, 0, 8) .attr(HealAttr, 0.25, true, false) @@ -11112,24 +11831,30 @@ export function initMoves() { new AttackMove(MoveId.METEOR_ASSAULT, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8) .attr(RechargeAttr) .makesContact(false), - new AttackMove(MoveId.ETERNABEAM, PokemonType.DRAGON, MoveCategory.SPECIAL, 160, 90, 5, -1, 0, 8) + new AttackMove(MoveId.ETERNABEAM, PokemonType.DRAGON, MoveCategory.SPECIAL, 160, 90, 5, -1, 0, 8) // .attr(RechargeAttr), - new AttackMove(MoveId.STEEL_BEAM, PokemonType.STEEL, MoveCategory.SPECIAL, 140, 95, 5, -1, 0, 8) + new AttackMove(MoveId.STEEL_BEAM, PokemonType.STEEL, MoveCategory.SPECIAL, 140, 95, 5, -1, 0, 8) // .attr(HalfSacrificialAttr), new AttackMove(MoveId.EXPANDING_FORCE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1) - .attr(VariableTargetAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER), + .attr(MovePowerMultiplierAttr, (user, _target, _move) => + globalScene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1, + ) + .attr(VariableTargetAttr, (user, _target, _move) => + globalScene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() + ? MoveTarget.ALL_NEAR_ENEMIES + : MoveTarget.NEAR_OTHER, + ), new AttackMove(MoveId.STEEL_ROLLER, PokemonType.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8) .attr(ClearTerrainAttr) .condition(() => !!globalScene.arena.terrain, 3), new AttackMove(MoveId.SCALE_SHOT, PokemonType.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true, { lastHitOnly: true }) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, { lastHitOnly: true }) + .attr(StatStageChangeAttr, [Stat.SPD], 1, true, { lastHitOnly: true }) + .attr(StatStageChangeAttr, [Stat.DEF], -1, true, { lastHitOnly: true }) .attr(MultiHitAttr) .makesContact(false), new ChargingAttackMove(MoveId.METEOR_BEAM, PokemonType.ROCK, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 8) .chargeText(i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true), + .chargeAttr(StatStageChangeAttr, [Stat.SPATK], 1, true), new AttackMove(MoveId.SHELL_SIDE_ARM, PokemonType.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8) .attr(ShellSideArmCategoryAttr) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -11137,24 +11862,33 @@ export function initMoves() { new AttackMove(MoveId.MISTY_EXPLOSION, PokemonType.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) .attr(SacrificialAttr) .target(MoveTarget.ALL_NEAR_OTHERS) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, _target, _move) => + globalScene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1, + ) .condition(failIfDampCondition, 3) .makesContact(false), - new AttackMove(MoveId.GRASSY_GLIDE, PokemonType.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, -1, 0, 8) - .attr(IncrementMovePriorityAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && user.isGrounded()), - new AttackMove(MoveId.RISING_VOLTAGE, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.ELECTRIC && target.isGrounded() ? 2 : 1), + new AttackMove(MoveId.GRASSY_GLIDE, PokemonType.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, -1, 0, 8) // + .attr( + IncrementMovePriorityAttr, + (user, _target, _move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && user.isGrounded(), + ), + new AttackMove(MoveId.RISING_VOLTAGE, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 8) // + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + globalScene.arena.getTerrainType() === TerrainType.ELECTRIC && target.isGrounded() ? 2 : 1, + ), new AttackMove(MoveId.TERRAIN_PULSE, PokemonType.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 8) .attr(TerrainPulseTypeAttr) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() !== TerrainType.NONE && user.isGrounded() ? 2 : 1) + .attr(MovePowerMultiplierAttr, (user, _target, _move) => + globalScene.arena.getTerrainType() !== TerrainType.NONE && user.isGrounded() ? 2 : 1, + ) .pulseMove(), - new AttackMove(MoveId.SKITTER_SMACK, PokemonType.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1), + new AttackMove(MoveId.SKITTER_SMACK, PokemonType.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.SPATK], -1), new AttackMove(MoveId.BURNING_JEALOUSY, PokemonType.FIRE, MoveCategory.SPECIAL, 70, 100, 5, 100, 0, 8) .attr(StatusIfBoostedAttr, StatusEffect.BURN) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.LASH_OUT, PokemonType.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (user, _target, _move) => user.turnData.statStagesDecreased ? 2 : 1), + new AttackMove(MoveId.LASH_OUT, PokemonType.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) // + .attr(MovePowerMultiplierAttr, (user, _target, _move) => (user.turnData.statStagesDecreased ? 2 : 1)), new AttackMove(MoveId.POLTERGEIST, PokemonType.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8) .condition(failIfNoTargetHeldItemsCondition, 3) .attr(PreMoveMessageAttr, attackedByItemMessageFunc) @@ -11164,16 +11898,16 @@ export function initMoves() { .reflectable() .unimplemented(), new StatusMove(MoveId.COACHING, PokemonType.FIGHTING, -1, 10, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF], 1) .target(MoveTarget.NEAR_ALLY) .condition(failIfSingleBattle), - new AttackMove(MoveId.FLIP_TURN, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8) + new AttackMove(MoveId.FLIP_TURN, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8) // .attr(ForceSwitchOutAttr, true), new AttackMove(MoveId.TRIPLE_AXEL, PokemonType.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8) .attr(MultiHitAttr, MultiHitType._3) .attr(MultiHitPowerIncrementAttr, 3) .checkAllHits(), - new AttackMove(MoveId.DUAL_WINGBEAT, PokemonType.FLYING, MoveCategory.PHYSICAL, 40, 90, 10, -1, 0, 8) + new AttackMove(MoveId.DUAL_WINGBEAT, PokemonType.FLYING, MoveCategory.PHYSICAL, 40, 90, 10, -1, 0, 8) // .attr(MultiHitAttr, MultiHitType._2), new AttackMove(MoveId.SCORCHING_SANDS, PokemonType.GROUND, MoveCategory.SPECIAL, 70, 100, 10, 30, 0, 8) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) @@ -11191,30 +11925,30 @@ export function initMoves() { .attr(MultiHitAttr, MultiHitType._3) .attr(CritOnlyAttr) .punchingMove(), - new AttackMove(MoveId.THUNDER_CAGE, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, -1, 0, 8) + new AttackMove(MoveId.THUNDER_CAGE, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, -1, 0, 8) // .attr(TrapAttr, BattlerTagType.THUNDER_CAGE), new AttackMove(MoveId.DRAGON_ENERGY, PokemonType.DRAGON, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 8) .attr(HpPowerAttr) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.FREEZING_GLARE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 8) + new AttackMove(MoveId.FREEZING_GLARE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 8) // .attr(StatusEffectAttr, StatusEffect.FREEZE), new AttackMove(MoveId.FIERY_WRATH, PokemonType.DARK, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8) .attr(FlinchAttr) .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.THUNDEROUS_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1), + new AttackMove(MoveId.THUNDEROUS_KICK, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.DEF], -1), new AttackMove(MoveId.GLACIAL_LANCE, PokemonType.ICE, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8) .target(MoveTarget.ALL_NEAR_ENEMIES) .makesContact(false), - new AttackMove(MoveId.ASTRAL_BARRAGE, PokemonType.GHOST, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 8) + new AttackMove(MoveId.ASTRAL_BARRAGE, PokemonType.GHOST, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 8) // .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.EERIE_SPELL, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 5, 100, 0, 8) .attr(AttackReducePpMoveAttr, 3) .soundBased(), - new AttackMove(MoveId.DIRE_CLAW, PokemonType.POISON, MoveCategory.PHYSICAL, 80, 100, 15, 50, 0, 8) - .attr(MultiStatusEffectAttr, [ StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP ]), - new AttackMove(MoveId.PSYSHIELD_BASH, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), + new AttackMove(MoveId.DIRE_CLAW, PokemonType.POISON, MoveCategory.PHYSICAL, 80, 100, 15, 50, 0, 8) // + .attr(MultiStatusEffectAttr, [StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP]), + new AttackMove(MoveId.PSYSHIELD_BASH, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.DEF], 1, true), new SelfStatusMove(MoveId.POWER_SHIFT, PokemonType.NORMAL, -1, 10, -1, 0, 8) .target(MoveTarget.USER) .attr(ShiftStatAttr, Stat.ATK, Stat.DEF), @@ -11222,11 +11956,11 @@ export function initMoves() { .attr(AddArenaTrapTagHitAttr, ArenaTagType.STEALTH_ROCK) .slicingMove(), new AttackMove(MoveId.SPRINGTIDE_STORM, PokemonType.FAIRY, MoveCategory.SPECIAL, 100, 80, 5, 30, 0, 8) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1) + .attr(StatStageChangeAttr, [Stat.ATK], -1) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), - new AttackMove(MoveId.MYSTICAL_POWER, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 70, 90, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true), + new AttackMove(MoveId.MYSTICAL_POWER, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 70, 90, 10, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.SPATK], 1, true), new AttackMove(MoveId.RAGING_FURY, PokemonType.FIRE, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 8) .makesContact(false) .attr(FrenzyAttr) @@ -11236,42 +11970,46 @@ export function initMoves() { new AttackMove(MoveId.WAVE_CRASH, PokemonType.WATER, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 8) .attr(RecoilAttr, false, 0.33) .recklessMove(), - new AttackMove(MoveId.CHLOROBLAST, PokemonType.GRASS, MoveCategory.SPECIAL, 150, 95, 5, -1, 0, 8) + new AttackMove(MoveId.CHLOROBLAST, PokemonType.GRASS, MoveCategory.SPECIAL, 150, 95, 5, -1, 0, 8) // .attr(RecoilAttr, true, 0.5), new AttackMove(MoveId.MOUNTAIN_GALE, PokemonType.ICE, MoveCategory.PHYSICAL, 100, 85, 10, 30, 0, 8) .makesContact(false) .attr(FlinchAttr), new SelfStatusMove(MoveId.VICTORY_DANCE, PokemonType.FIGHTING, -1, 10, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.DEF, Stat.SPD], 1, true) .danceMove(), new AttackMove(MoveId.HEADLONG_RUSH, PokemonType.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true) + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], -1, true) .punchingMove(), new AttackMove(MoveId.BARB_BARRAGE, PokemonType.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8) .makesContact(false) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1) + .attr(MovePowerMultiplierAttr, (_user, target, _move) => + target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) + ? 2 + : 1, + ) .attr(StatusEffectAttr, StatusEffect.POISON), new AttackMove(MoveId.ESPER_WING, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8) .attr(HighCritAttr) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true), - new AttackMove(MoveId.BITTER_MALICE, PokemonType.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), - new SelfStatusMove(MoveId.SHELTER, PokemonType.STEEL, -1, 10, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true), + .attr(StatStageChangeAttr, [Stat.SPD], 1, true), + new AttackMove(MoveId.BITTER_MALICE, PokemonType.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8) // + .attr(StatStageChangeAttr, [Stat.ATK], -1), + new SelfStatusMove(MoveId.SHELTER, PokemonType.STEEL, -1, 10, -1, 0, 8) // + .attr(StatStageChangeAttr, [Stat.DEF], 2, true), new AttackMove(MoveId.TRIPLE_ARROWS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8) .makesContact(false) .attr(HighCritAttr) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1, false, { effectChanceOverride: 50 }) + .attr(StatStageChangeAttr, [Stat.DEF], -1, false, { effectChanceOverride: 50 }) .attr(FlinchAttr), new AttackMove(MoveId.INFERNAL_PARADE, PokemonType.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8) .attr(StatusEffectAttr, StatusEffect.BURN) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.status ? 2 : 1), + .attr(MovePowerMultiplierAttr, (_user, target, _move) => (target.status ? 2 : 1)), new AttackMove(MoveId.CEASELESS_EDGE, PokemonType.DARK, MoveCategory.PHYSICAL, 65, 90, 15, 100, 0, 8) .attr(AddArenaTrapTagHitAttr, ArenaTagType.SPIKES) .slicingMove(), new AttackMove(MoveId.BLEAKWIND_STORM, PokemonType.FLYING, MoveCategory.SPECIAL, 100, 80, 10, 30, 0, 8) .attr(StormAccuracyAttr) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1) + .attr(StatStageChangeAttr, [Stat.SPD], -1) .windMove() .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.WILDBOLT_STORM, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 100, 80, 10, 20, 0, 8) @@ -11290,8 +12028,14 @@ export function initMoves() { .target(MoveTarget.USER_AND_ALLIES) .triageMove(), new SelfStatusMove(MoveId.TAKE_HEART, PokemonType.PSYCHIC, -1, 10, -1, 0, 8) - .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF ], 1, true) - .attr(HealStatusEffectAttr, true, [ StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP ]), + .attr(StatStageChangeAttr, [Stat.SPATK, Stat.SPDEF], 1, true) + .attr(HealStatusEffectAttr, true, [ + StatusEffect.PARALYSIS, + StatusEffect.POISON, + StatusEffect.TOXIC, + StatusEffect.BURN, + StatusEffect.SLEEP, + ]), /* Unused new AttackMove(MoveId.G_MAX_WILDFIRE, PokemonType.Fire, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.ALL_NEAR_ENEMIES) @@ -11397,7 +12141,9 @@ export function initMoves() { .attr(TeraMoveCategoryAttr) .attr(TeraBlastTypeAttr) .attr(TeraBlastPowerAttr) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, { condition: (user, target, move) => user.isTerastallized && user.isOfType(PokemonType.STELLAR) }), + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPATK], -1, true, { + condition: (user, _target, _move) => user.isTerastallized && user.isOfType(PokemonType.STELLAR), + }), new SelfStatusMove(MoveId.SILK_TRAP, PokemonType.BUG, -1, 10, -1, 4, 9) .attr(ProtectAttr, BattlerTagType.SILK_TRAP) .condition(failIfLastCondition, 3), @@ -11407,25 +12153,29 @@ export function initMoves() { .attr(ConfuseAttr) .recklessMove(), new AttackMove(MoveId.LAST_RESPECTS, PokemonType.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9) - .attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 100)) + .attr( + MovePowerMultiplierAttr, + (user, _target, _move) => + 1 + Math.min(user.isPlayer() ? globalScene.arena.playerFaints : globalScene.currentBattle.enemyFaints, 100), + ) .makesContact(false), - new AttackMove(MoveId.LUMINA_CRASH, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), + new AttackMove(MoveId.LUMINA_CRASH, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) // + .attr(StatStageChangeAttr, [Stat.SPDEF], -2), new AttackMove(MoveId.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 9) .attr(OrderUpStatBoostAttr) .makesContact(false), - new AttackMove(MoveId.JET_PUNCH, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9) + new AttackMove(MoveId.JET_PUNCH, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9) // .punchingMove(), new StatusMove(MoveId.SPICY_EXTRACT, PokemonType.GRASS, -1, 15, -1, 0, 9) - .attr(StatStageChangeAttr, [ Stat.ATK ], 2) - .attr(StatStageChangeAttr, [ Stat.DEF ], -2), - new AttackMove(MoveId.SPIN_OUT, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPD ], -2, true), + .attr(StatStageChangeAttr, [Stat.ATK], 2) + .attr(StatStageChangeAttr, [Stat.DEF], -2), + new AttackMove(MoveId.SPIN_OUT, PokemonType.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) // + .attr(StatStageChangeAttr, [Stat.SPD], -2, true), new AttackMove(MoveId.POPULATION_BOMB, PokemonType.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9) .attr(MultiHitAttr, MultiHitType._10) .slicingMove() .checkAllHits(), - new AttackMove(MoveId.ICE_SPINNER, PokemonType.ICE, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9) + new AttackMove(MoveId.ICE_SPINNER, PokemonType.ICE, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9) // .attr(ClearTerrainAttr), new AttackMove(MoveId.GLAIVE_RUSH, PokemonType.DRAGON, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 9) .attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_GET_HIT, true, false, 0, 0, true) @@ -11437,66 +12187,78 @@ export function initMoves() { new AttackMove(MoveId.SALT_CURE, PokemonType.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, 100, 0, 9) .attr(AddBattlerTagAttr, BattlerTagType.SALT_CURED) .makesContact(false), - new AttackMove(MoveId.TRIPLE_DIVE, PokemonType.WATER, MoveCategory.PHYSICAL, 30, 95, 10, -1, 0, 9) + new AttackMove(MoveId.TRIPLE_DIVE, PokemonType.WATER, MoveCategory.PHYSICAL, 30, 95, 10, -1, 0, 9) // .attr(MultiHitAttr, MultiHitType._3), new AttackMove(MoveId.MORTAL_SPIN, PokemonType.POISON, MoveCategory.PHYSICAL, 30, 100, 15, 100, 0, 9) - .attr(RemoveBattlerTagAttr, [ - BattlerTagType.BIND, - BattlerTagType.WRAP, - BattlerTagType.FIRE_SPIN, - BattlerTagType.WHIRLPOOL, - BattlerTagType.CLAMP, - BattlerTagType.SAND_TOMB, - BattlerTagType.MAGMA_STORM, - BattlerTagType.SNAP_TRAP, - BattlerTagType.THUNDER_CAGE, - BattlerTagType.SEEDED, - BattlerTagType.INFESTATION - ], true) + .attr( + RemoveBattlerTagAttr, + [ + BattlerTagType.BIND, + BattlerTagType.WRAP, + BattlerTagType.FIRE_SPIN, + BattlerTagType.WHIRLPOOL, + BattlerTagType.CLAMP, + BattlerTagType.SAND_TOMB, + BattlerTagType.MAGMA_STORM, + BattlerTagType.SNAP_TRAP, + BattlerTagType.THUNDER_CAGE, + BattlerTagType.SEEDED, + BattlerTagType.INFESTATION, + ], + true, + ) .attr(StatusEffectAttr, StatusEffect.POISON) .attr(RemoveArenaTrapAttr) .target(MoveTarget.ALL_NEAR_ENEMIES), - new StatusMove(MoveId.DOODLE, PokemonType.NORMAL, 100, 10, -1, 0, 9) + new StatusMove(MoveId.DOODLE, PokemonType.NORMAL, 100, 10, -1, 0, 9) // .attr(AbilityCopyAttr, true), new SelfStatusMove(MoveId.FILLET_AWAY, PokemonType.NORMAL, -1, 10, -1, 0, 9) - .attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, 2) + .attr(CutHpStatStageBoostAttr, [Stat.ATK, Stat.SPATK, Stat.SPD], 2, 2) .condition(new FailIfInsufficientHpCondition(2), 3), - new AttackMove(MoveId.KOWTOW_CLEAVE, PokemonType.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9) + new AttackMove(MoveId.KOWTOW_CLEAVE, PokemonType.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9) // .slicingMove(), new AttackMove(MoveId.FLOWER_TRICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 9) .attr(CritOnlyAttr) .makesContact(false), new AttackMove(MoveId.TORCH_SONG, PokemonType.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) + .attr(StatStageChangeAttr, [Stat.SPATK], 1, true) .soundBased(), new AttackMove(MoveId.AQUA_STEP, PokemonType.WATER, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.SPD], 1, true) .danceMove(), new AttackMove(MoveId.RAGING_BULL, PokemonType.NORMAL, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 9) .attr(RagingBullTypeAttr) .attr(RemoveScreensAttr), new AttackMove(MoveId.MAKE_IT_RAIN, PokemonType.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(MoneyAttr) - .attr(StatStageChangeAttr, [ Stat.SPATK ], -1, true, { firstTargetOnly: true }) + .attr(StatStageChangeAttr, [Stat.SPATK], -1, true, { firstTargetOnly: true }) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(MoveId.PSYBLADE, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9) - .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, _target, _move) => + globalScene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1, + ) .slicingMove(), new AttackMove(MoveId.HYDRO_STEAM, PokemonType.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 9) .attr(IgnoreWeatherTypeDebuffAttr, WeatherType.SUNNY) - .attr(MovePowerMultiplierAttr, (user, target, move) => { + .attr(MovePowerMultiplierAttr, (_user, _target, _move) => { const weather = globalScene.arena.weather; if (!weather) { return 1; } - return [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(weather.weatherType) && !weather.isEffectSuppressed() ? 1.5 : 1; + return [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(weather.weatherType) && !weather.isEffectSuppressed() + ? 1.5 + : 1; }), - new AttackMove(MoveId.RUINATION, PokemonType.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9) + new AttackMove(MoveId.RUINATION, PokemonType.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9) // .attr(TargetHalfHpDamageAttr), - new AttackMove(MoveId.COLLISION_COURSE, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 4 / 3 : 1), + new AttackMove(MoveId.COLLISION_COURSE, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) // + .attr(MovePowerMultiplierAttr, (user, target, move) => + target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 4 / 3 : 1, + ), new AttackMove(MoveId.ELECTRO_DRIFT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 9) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 4 / 3 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => + target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 4 / 3 : 1, + ) .makesContact(), new SelfStatusMove(MoveId.SHED_TAIL, PokemonType.NORMAL, -1, 10, -1, 0, 9) .attr(AddSubstituteAttr, 0.5, true) @@ -11507,30 +12269,31 @@ export function initMoves() { // Don't display text if current move phase is follow up (ie move called indirectly) isVirtual((globalScene.phaseManager.getCurrentPhase() as MovePhase).useMode) ? "" - : i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) })) + : i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }), + ) .attr(ChillyReceptionAttr, true), new SelfStatusMove(MoveId.TIDY_UP, PokemonType.NORMAL, -1, 10, -1, 0, 9) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true) + .attr(StatStageChangeAttr, [Stat.ATK, Stat.SPD], 1, true) .attr(RemoveArenaTrapAttr, true) .attr(RemoveAllSubstitutesAttr), new StatusMove(MoveId.SNOWSCAPE, PokemonType.ICE, -1, 10, -1, 0, 9) .attr(WeatherChangeAttr, WeatherType.SNOW) .target(MoveTarget.BOTH_SIDES), - new AttackMove(MoveId.POUNCE, PokemonType.BUG, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPD ], -1), - new AttackMove(MoveId.TRAILBLAZE, PokemonType.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9) - .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true), - new AttackMove(MoveId.CHILLING_WATER, PokemonType.WATER, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 9) - .attr(StatStageChangeAttr, [ Stat.ATK ], -1), - new AttackMove(MoveId.HYPER_DRILL, PokemonType.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) + new AttackMove(MoveId.POUNCE, PokemonType.BUG, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9) // + .attr(StatStageChangeAttr, [Stat.SPD], -1), + new AttackMove(MoveId.TRAILBLAZE, PokemonType.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9) // + .attr(StatStageChangeAttr, [Stat.SPD], 1, true), + new AttackMove(MoveId.CHILLING_WATER, PokemonType.WATER, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 9) // + .attr(StatStageChangeAttr, [Stat.ATK], -1), + new AttackMove(MoveId.HYPER_DRILL, PokemonType.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) // .ignoresProtect(), - new AttackMove(MoveId.TWIN_BEAM, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9) + new AttackMove(MoveId.TWIN_BEAM, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9) // .attr(MultiHitAttr, MultiHitType._2), new AttackMove(MoveId.RAGE_FIST, PokemonType.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9) .attr(RageFistPowerAttr) .punchingMove(), - new AttackMove(MoveId.ARMOR_CANNON, PokemonType.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) - .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), + new AttackMove(MoveId.ARMOR_CANNON, PokemonType.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) // + .attr(StatStageChangeAttr, [Stat.DEF, Stat.SPDEF], -1, true), new AttackMove(MoveId.BITTER_BLADE, PokemonType.FIRE, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 9) .attr(HitHealAttr) .slicingMove() @@ -11539,8 +12302,10 @@ export function initMoves() { // Pass `true` to `isOfType` to fail if the user is terastallized to a type other than ELECTRIC .condition(user => user.isOfType(PokemonType.ELECTRIC, true, true), 2) .attr(AddBattlerTagAttr, BattlerTagType.DOUBLE_SHOCKED, true, false) - .attr(RemoveTypeAttr, PokemonType.ELECTRIC, (user) => { - globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) })); + .attr(RemoveTypeAttr, PokemonType.ELECTRIC, user => { + globalScene.phaseManager.queueMessage( + i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) }), + ); }), new AttackMove(MoveId.GIGATON_HAMMER, PokemonType.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, -1, 0, 9) .makesContact(false) @@ -11569,7 +12334,7 @@ export function initMoves() { new AttackMove(MoveId.MAGICAL_TORQUE, PokemonType.FAIRY, MoveCategory.PHYSICAL, 100, 100, 10, 30, 0, 9) .attr(ConfuseAttr) .makesContact(false), - new AttackMove(MoveId.BLOOD_MOON, PokemonType.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 9) + new AttackMove(MoveId.BLOOD_MOON, PokemonType.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 9) // .restriction(consecutiveUseRestriction), new AttackMove(MoveId.MATCHA_GOTCHA, PokemonType.GRASS, MoveCategory.SPECIAL, 80, 90, 15, 20, 0, 9) .attr(HitHealAttr) @@ -11587,20 +12352,26 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(MoveId.ELECTRO_SHOT, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, -1, 0, 9) .chargeText(i18next.t("moveTriggers:absorbedElectricity", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) - .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]), + .chargeAttr(StatStageChangeAttr, [Stat.SPATK], 1, true) + .chargeAttr(WeatherInstantChargeAttr, [WeatherType.RAIN, WeatherType.HEAVY_RAIN]), new AttackMove(MoveId.TERA_STARSTORM, PokemonType.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(TeraMoveCategoryAttr) .attr(TeraStarstormTypeAttr) - .attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(SpeciesId.TERAPAGOS) && (user.isTerastallized || globalScene.currentBattle.preTurnCommands[user.getFieldIndex()]?.command === Command.TERA) ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER) - .partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */ + .attr(VariableTargetAttr, (user, _target, _move) => + user.hasSpecies(SpeciesId.TERAPAGOS) + && (user.isTerastallized + || globalScene.currentBattle.preTurnCommands[user.getFieldIndex()]?.command === Command.TERA) + ? MoveTarget.ALL_NEAR_ENEMIES + : MoveTarget.NEAR_OTHER, + ) + .partial(), // Does not ignore abilities that affect stats, relevant in determining the move's category (cf `TeraMoveCategoryAttr`) new AttackMove(MoveId.FICKLE_BEAM, PokemonType.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, -1, 0, 9) .attr(PreMoveMessageAttr, doublePowerChanceMessageFunc(30)) .attr(DoublePowerChanceAttr, 30), new SelfStatusMove(MoveId.BURNING_BULWARK, PokemonType.FIRE, -1, 10, -1, 4, 9) .attr(ProtectAttr, BattlerTagType.BURNING_BULWARK) .condition(failIfLastCondition, 3), - new AttackMove(MoveId.THUNDERCLAP, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9) + new AttackMove(MoveId.THUNDERCLAP, PokemonType.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9) // .condition(failIfTargetNotAttackingCondition, 3), new AttackMove(MoveId.MIGHTY_CLEAVE, PokemonType.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9) .slicingMove() @@ -11608,7 +12379,7 @@ export function initMoves() { new AttackMove(MoveId.TACHYON_CUTTER, PokemonType.STEEL, MoveCategory.SPECIAL, 50, -1, 10, -1, 0, 9) .attr(MultiHitAttr, MultiHitType._2) .slicingMove(), - new AttackMove(MoveId.HARD_PRESS, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9) + new AttackMove(MoveId.HARD_PRESS, PokemonType.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9) // .attr(OpponentHighHpPowerAttr, 100), new StatusMove(MoveId.DRAGON_CHEER, PokemonType.DRAGON, -1, 15, -1, 0, 9) .attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true) @@ -11618,8 +12389,12 @@ export function initMoves() { new AttackMove(MoveId.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) .attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED) .soundBased(), - new AttackMove(MoveId.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1), + new AttackMove(MoveId.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9) // + .attr(MovePowerMultiplierAttr, (user, _target, _move) => + user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL + ? 2 + : 1, + ), new AttackMove(MoveId.SUPERCELL_SLAM, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9) .attr(AlwaysHitMinimizeAttr) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED) @@ -11632,7 +12407,7 @@ export function initMoves() { new AttackMove(MoveId.UPPER_HAND, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9) .attr(FlinchAttr) .condition(upperHandCondition, 3), - new AttackMove(MoveId.MALIGNANT_CHAIN, PokemonType.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) - .attr(StatusEffectAttr, StatusEffect.TOXIC) + new AttackMove(MoveId.MALIGNANT_CHAIN, PokemonType.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) // + .attr(StatusEffectAttr, StatusEffect.TOXIC), ); } From 4f52627146858fdb8ca7a11c5be85b99ac557e1b Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:58:36 -0800 Subject: [PATCH 023/101] [Misc] Fix param typing for `OverridesHelper#startingLevel` --- test/test-utils/helpers/overrides-helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-utils/helpers/overrides-helper.ts b/test/test-utils/helpers/overrides-helper.ts index 331d1c94acf..32e7cc3f6b9 100644 --- a/test/test-utils/helpers/overrides-helper.ts +++ b/test/test-utils/helpers/overrides-helper.ts @@ -78,7 +78,7 @@ export class OverridesHelper extends GameManagerHelper { * @param level - The level to set * @returns `this` */ - public startingLevel(level: SpeciesId | number): this { + public startingLevel(level: number): this { vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(level); this.log(`Player Pokemon starting level set to ${level}!`); return this; From c945a1b3f9856bb84ef56a6761be099d8c5ae10a Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 27 Nov 2025 23:40:43 -0800 Subject: [PATCH 024/101] Update submodules --- assets | 2 +- locales | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index a718a52c205..9587d57f2d8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a718a52c20568802c2f03a355a2b784789849960 +Subproject commit 9587d57f2d8fbd48e7fc023e9c02c494eea3cf75 diff --git a/locales b/locales index f341227e4ca..967f2eb12a8 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit f341227e4ca8488bc1b199215d3d5effa9f2e41f +Subproject commit 967f2eb12a86c6fcc9292f6b1d2fadb66a8f197b From 4522e9e59379b1bd256ebc910f876ffdd5edf55f Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:43:06 +0100 Subject: [PATCH 025/101] [i18n] Replace hardcoded messages for Data import (#6795) * localize confirm import message * localize error messages --- src/system/game-data.ts | 107 ++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 0135c198680..e2ccbc8abd0 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1446,71 +1446,58 @@ export class GameData { globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), fixedInt(1500)); if (!valid) { - return globalScene.ui.showText( - `Your ${dataName} data could not be loaded. It may be corrupted.`, - null, - () => globalScene.ui.showText("", 0), - fixedInt(1500), - ); + return displayError(i18next.t("menuUiHandler:importCorrupt", { dataName })); } - globalScene.ui.showText( - `Your ${dataName} data will be overridden and the page will reload. Proceed?`, - null, - () => { - globalScene.ui.setOverlayMode( - UiMode.CONFIRM, - () => { - localStorage.setItem(dataKey, encrypt(dataStr, bypassLogin)); + globalScene.ui.showText(i18next.t("menuUiHandler:confirmImport", { dataName }), null, () => { + globalScene.ui.setOverlayMode( + UiMode.CONFIRM, + () => { + localStorage.setItem(dataKey, encrypt(dataStr, bypassLogin)); - if (!bypassLogin && dataType < GameDataType.SETTINGS) { - updateUserInfo().then(success => { - if (!success[0]) { - return displayError( - `Could not contact the server. Your ${dataName} data could not be imported.`, - ); + if (!bypassLogin && dataType < GameDataType.SETTINGS) { + updateUserInfo().then(success => { + if (!success[0]) { + return displayError(i18next.t("menuUiHandler:importNoServer", { dataName })); + } + const { trainerId, secretId } = this; + let updatePromise: Promise; + if (dataType === GameDataType.SESSION) { + updatePromise = pokerogueApi.savedata.session.update( + { + slot: slotId, + trainerId, + secretId, + clientSessionId, + }, + dataStr, + ); + } else { + updatePromise = pokerogueApi.savedata.system.update( + { trainerId, secretId, clientSessionId }, + dataStr, + ); + } + updatePromise.then(error => { + if (error) { + console.error(error); + return displayError(i18next.t("menuUiHandler:importError", { dataName })); } - const { trainerId, secretId } = this; - let updatePromise: Promise; - if (dataType === GameDataType.SESSION) { - updatePromise = pokerogueApi.savedata.session.update( - { - slot: slotId, - trainerId, - secretId, - clientSessionId, - }, - dataStr, - ); - } else { - updatePromise = pokerogueApi.savedata.system.update( - { trainerId, secretId, clientSessionId }, - dataStr, - ); - } - updatePromise.then(error => { - if (error) { - console.error(error); - return displayError( - `An error occurred while updating ${dataName} data. Please contact the administrator.`, - ); - } - window.location.reload(); - }); + window.location.reload(); }); - } else { - window.location.reload(); - } - }, - () => { - globalScene.ui.revertMode(); - globalScene.ui.showText("", 0); - }, - false, - -98, - ); - }, - ); + }); + } else { + window.location.reload(); + } + }, + () => { + globalScene.ui.revertMode(); + globalScene.ui.showText("", 0); + }, + false, + -98, + ); + }); }; })((e.target as any).files[0]); From 3478e0992394a186be3ed12e05917d30f6aaf573 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:26:49 -0500 Subject: [PATCH 026/101] [Dev] Break up `test:create` script; add help message, file name CLI argument support (#6793) * [Dev] Broke up `test:create` script, added CLI args file name suppoert * Moved `HELP_FLAGS` constant; fixed help msg indentation * ran biome * Fix floting promise err * Added REUSE info * Typo fix * comment out reward boilerplate * Removed redundant comments --------- Co-authored-by: fabske0 <192151969+fabske0@users.noreply.github.com> --- biome.jsonc | 10 ++ scripts/create-test/cli.js | 80 +++++++++ scripts/create-test/constants.js | 47 ++++++ scripts/create-test/create-test.js | 235 ++++++--------------------- scripts/create-test/dirs.js | 56 +++++++ scripts/create-test/help-message.js | 32 ++++ scripts/create-test/interactive.js | 66 ++++++++ scripts/helpers/file.js | 36 ++++ scripts/parse-egg-moves/main.js | 2 +- scripts/scrape-trainer-names/main.js | 2 +- 10 files changed, 378 insertions(+), 188 deletions(-) create mode 100644 scripts/create-test/cli.js create mode 100644 scripts/create-test/constants.js create mode 100644 scripts/create-test/dirs.js create mode 100644 scripts/create-test/help-message.js create mode 100644 scripts/create-test/interactive.js create mode 100644 scripts/helpers/file.js diff --git a/biome.jsonc b/biome.jsonc index 27ce10b8629..3a2c39e1541 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -251,6 +251,16 @@ } }, "overrides": [ + { + "includes": ["**/scripts/**/*.js"], + "linter": { + "rules": { + "nursery": { + "noFloatingPromises": "error" + } + } + } + }, { "includes": ["**/test/**/*.test.ts"], "linter": { diff --git a/scripts/create-test/cli.js b/scripts/create-test/cli.js new file mode 100644 index 00000000000..2bf24d78470 --- /dev/null +++ b/scripts/create-test/cli.js @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import chalk from "chalk"; +import { cliAliases, validTestTypes } from "./constants.js"; +import { promptFileName, promptTestType } from "./interactive.js"; + +/** + * @import {testType} from "./constants.js" + */ + +/** + * Parse `process.argv` to retrieve the test type if it exists, otherwise prompting input from the user. + * @param {string | undefined} arg - The argument from `process.argv` + * @returns {Promise} + * A Promise that resolves with the type of test to be created, or `undefined` if the user interactively selects "Exit". + * Will set `process.exitCode` to a non-zero integer if args are invalid. + */ +export async function getTestType(arg) { + if (arg == null) { + return await promptTestType(); + } + + const testType = getCliTestType(arg); + if (testType) { + console.log(chalk.blue(`Using ${testType} as test type from CLI...`)); + return testType; + } + console.error( + chalk.red.bold( + `✗ Invalid type of test file specified: ${arg}!\nValid types: ${chalk.blue(validTestTypes.join(", "))}`, + ), + ); + process.exitCode = 1; + return; +} + +/** + * Parse a test type from command-line args. + * @param {string} arg + * @returns {testType | undefined} The resulting test type. + * Will return `undefined` if no valid match was found. + */ +function getCliTestType(arg) { + // Check for a direct match, falling back to alias checking if none work + const testTypeName = validTestTypes.find(c => c.toLowerCase() === arg.toLowerCase()); + if (testTypeName) { + return testTypeName; + } + + const alias = /** @type {(keyof typeof cliAliases)[]} */ (Object.keys(cliAliases)).find(aliasKey => + cliAliases[aliasKey].some(alias => alias.toLowerCase() === arg.toLowerCase()), + ); + return alias; +} + +/** + * Obtain the file name for a given file + * @param {testType} testType - The chosen test type + * @param {string | undefined} arg - The contents of `process.argv[3]`, if it exists + * @returns {Promise} A promise that resolves with the name of the file to create. + */ +export async function getFileName(testType, arg) { + if (arg == null) { + return await promptFileName(testType); + } + + const nameTrimmed = arg.trim().replace(".test.ts", ""); + if (nameTrimmed.length === 0) { + console.error(chalk.red.bold("✗ Cannot use an empty string as a file name!")); + process.exitCode = 1; + return; + } + + console.log(chalk.blue(`Using ${nameTrimmed} as file name from CLI...`)); + return nameTrimmed; +} diff --git a/scripts/create-test/constants.js b/scripts/create-test/constants.js new file mode 100644 index 00000000000..e3ddfaa7a24 --- /dev/null +++ b/scripts/create-test/constants.js @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * Array containing all valid options for the type of test file to create. + * @package + */ +export const validTestTypes = /** @type {const} */ ([ + "Move", + "Ability", + "Item", + "Reward", + "Mystery Encounter", + "Utils", + "UI", +]); + +/** + * @typedef {typeof validTestTypes[number]} + * testType + * Union type representing a single valid choice of test type. + */ + +/** + * Const object mapping each test type to any additional names they can be used with from CLI. + * @satisfies {Partial>} + */ +export const cliAliases = /** @type {const} */ ({ + "Mystery Encounter": ["ME"], +}); + +/** + * Const object matching all test types to the directories in which their tests reside. + * @satisfies {Record} + */ +export const testTypesToDirs = /** @type {const} */ ({ + Move: "moves", + Ability: "abilities", + Item: "items", + Reward: "rewards", + "Mystery Encounter": "mystery-encounter/encounters", + Utils: "utils", + UI: "ui", +}); diff --git a/scripts/create-test/create-test.js b/scripts/create-test/create-test.js index ad1f999177c..3c49ceb3621 100644 --- a/scripts/create-test/create-test.js +++ b/scripts/create-test/create-test.js @@ -11,146 +11,25 @@ */ import fs from "node:fs"; -import path, { join } from "node:path"; +import { join } from "node:path"; import chalk from "chalk"; -import inquirer from "inquirer"; +import { writeFileSafe } from "../helpers/file.js"; +import { toKebabCase, toTitleCase } from "../helpers/strings.js"; +import { getFileName, getTestType } from "./cli.js"; +import { getBoilerplatePath, getTestFileFullPath } from "./dirs.js"; +import { HELP_FLAGS, showHelpText } from "./help-message.js"; + +/** + * @import {testType} from "./constants.js" + */ //#region Constants - -const version = "2.0.2"; -// Get the directory name of the current module file +const version = "2.1.0"; const __dirname = import.meta.dirname; -const projectRoot = path.join(__dirname, "..", ".."); - -const choices = /** @type {const} */ (["Move", "Ability", "Item", "Reward", "Mystery Encounter", "Utils", "UI"]); -/** @typedef {typeof choices[number]} choiceType */ -/** - * Object mapping choice types to extra names they can be used with from CLI. - * @satisfies {Partial>} - */ -const cliAliases = { - "Mystery Encounter": ["ME"], -}; - -/** @satisfies {{[k in choiceType]: string}} */ -const choicesToDirs = /** @type {const} */ ({ - Move: "moves", - Ability: "abilities", - Item: "items", - Reward: "rewards", - "Mystery Encounter": "mystery-encounter/encounters", - Utils: "utils", - UI: "ui", -}); - +const projectRoot = join(__dirname, "..", ".."); //#endregion -//#region Functions -/** - * Get the path to a given folder in the test directory - * @param {...string} folders the subfolders to append to the base path - * @returns {string} the path to the requested folder - */ -function getTestFolderPath(...folders) { - return path.join(projectRoot, "test", ...folders); -} - -/** - * Prompts the user to select a type via list. - * @returns {Promise} the selected type - */ -async function promptTestType() { - /** @type {choiceType | "EXIT"} */ - const choice = ( - await inquirer.prompt([ - { - type: "list", - name: "selectedOption", - message: "What type of test would you like to create?", - choices: [...choices, "EXIT"], - }, - ]) - ).selectedOption; - - if (choice === "EXIT") { - console.log("Exiting..."); - return process.exit(0); - } - - return choice; -} - -/** - * Prompts the user to provide a file name. - * @param {choiceType} selectedType The chosen string (used to display console logs) - * @returns {Promise} the selected file name - */ -async function promptFileName(selectedType) { - /** @type {string} */ - const fileNameAnswer = ( - await inquirer.prompt([ - { - type: "input", - name: "userInput", - message: `Please provide the name of the ${selectedType}.`, - }, - ]) - ).userInput; - - if (fileNameAnswer.trim().length === 0) { - console.error("Please provide a valid file name!"); - return await promptFileName(selectedType); - } - - return fileNameAnswer; -} - -/** - * Obtain the path to the boilerplate file based on the current option. - * @param {choiceType} choiceType The choice selected - * @returns {string} The path to the boilerplate file - */ -function getBoilerplatePath(choiceType) { - switch (choiceType) { - // case "Reward": - // return path.join(__dirname, "boilerplates/reward.boilerplate.ts"); - default: - return path.join(__dirname, "boilerplates/default.boilerplate.ts"); - } -} - -/** - * Parse `process.argv` and get the test type if it exists. - * @returns {choiceType | undefined} - * The type of choice the CLI args corresponds to, or `undefined` if none were specified. - * Will set `process.exitCode` to a non-zero integer if args are invalid. - */ -function convertArgsToTestType() { - // If the first argument is a test name, use that as the test name - const args = process.argv.slice(2); - if (args[0] == null) { - return; - } - - // Check for a direct match, falling back to alias checking. - const choiceName = choices.find(c => c.toLowerCase() === args[0].toLowerCase()); - if (choiceName) { - return choiceName; - } - - const alias = /** @type {(keyof cliAliases)[]} */ (Object.keys(cliAliases)).find(k => - cliAliases[k].some(a => a.toLowerCase() === args[0].toLowerCase()), - ); - if (alias) { - return alias; - } - console.error( - chalk.red.bold(`✗ Invalid type of test file specified: ${args[0]}! -Valid types: ${chalk.blue(choices.join(", "))}`), - ); - process.exitCode = 1; - return; -} +//#region Main /** * Run the interactive `test:create` CLI. @@ -159,65 +38,49 @@ Valid types: ${chalk.blue(choices.join(", "))}`), async function runInteractive() { console.group(chalk.grey(`🧪 Create Test - v${version}\n`)); - const cliTestType = convertArgsToTestType(); - if (process.exitCode) { + const args = process.argv.slice(2); + + if (HELP_FLAGS.some(h => args.includes(h))) { + return showHelpText(); + } + + const testType = await getTestType(args[0]); + if (process.exitCode || !testType) { return; } - // TODO: Add a help command + + const fileNameAnswer = await getFileName(testType, args[1]); + if (process.exitCode || !fileNameAnswer) { + return; + } + try { - let choice; - if (cliTestType) { - console.log(chalk.blue(`Using ${cliTestType} as test type from CLI...`)); - choice = cliTestType; - } else { - choice = await promptTestType(); - } - const fileNameAnswer = await promptFileName(choice); - - // Convert fileName from snake_case or camelCase to kebab-case - const fileName = fileNameAnswer - .replace(/_+/g, "-") // Convert snake_case (underscore) to kebab-case (dashes) - .replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case - .replace(/\s+/g, "-") // Replace spaces with dashes - .toLowerCase(); // Ensure all lowercase - - // Format the description for the test case in Title Case - const formattedName = fileName.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase()); - const description = `${choice} - ${formattedName}`; - - // Determine the directory based on the type - const localDir = choicesToDirs[choice]; - const absoluteDir = getTestFolderPath(localDir); - - // Define the content template - const content = fs.readFileSync(getBoilerplatePath(choice), "utf8").replace("{{description}}", description); - - // Ensure the directory exists - if (!fs.existsSync(absoluteDir)) { - fs.mkdirSync(absoluteDir, { recursive: true }); - } - - // Create the file with the given name - const filePath = path.join(absoluteDir, `${fileName}.test.ts`); - - if (fs.existsSync(filePath)) { - console.error(chalk.red.bold(`✗ File "${fileName}.test.ts" already exists!\n`)); - process.exit(1); - } - - // Write the template content to the file - fs.writeFileSync(filePath, content, "utf8"); - - console.log(chalk.green.bold(`✔ File created at: ${join("test", localDir, fileName)}.test.ts\n`)); - console.groupEnd(); + doCreateFile(testType, fileNameAnswer); } catch (err) { console.error(chalk.red("✗ Error: ", err)); } + console.groupEnd(); +} + +/** + * Helper function to create the test file. + * @param {testType} testType - The type of test to create + * @param {string} fileNameAnswer - The name of the file to create + * @returns {void} + */ +function doCreateFile(testType, fileNameAnswer) { + // Convert file name to kebab-case, formatting the description in Title Case + const fileName = toKebabCase(fileNameAnswer); + const formattedName = toTitleCase(fileNameAnswer); + const description = `${testType} - ${formattedName}`; + + const content = fs.readFileSync(getBoilerplatePath(testType), "utf8").replace("{{description}}", description); + const filePath = getTestFileFullPath(testType, fileName); + writeFileSafe(filePath, content, "utf8"); + + console.log(chalk.green.bold(`✔ File created at: ${filePath.replace(`${projectRoot}/`, "")}\n`)); } //#endregion -//#region Run -runInteractive(); - -//#endregion +await runInteractive(); diff --git a/scripts/create-test/dirs.js b/scripts/create-test/dirs.js new file mode 100644 index 00000000000..1254e2b94d5 --- /dev/null +++ b/scripts/create-test/dirs.js @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { join } from "path"; +import { testTypesToDirs } from "./constants.js"; + +/** + * @import { testType } from "./constants.js"; + */ + +// Get the directory name of the current module file +const __dirname = import.meta.dirname; +const projectRoot = join(__dirname, "..", ".."); + +/** + * Const object matching all {@linkcode testType}s to any custom boilerplate files + * they may be associated with. + * @type {Readonly>>} + */ +const customBoilerplates = { + // Reward: "boilerplates/reward.boilerplate.ts", // Todo: Boilerplate is added in the modifier rework +}; + +const DEFAULT_BOILERPLATE_PATH = "boilerplates/default.boilerplate.ts"; + +/** + * Retrieve the path to the boilerplate file used for the given test type. + * @param {testType} testType - The type of test file to create + * @returns {string} The path to the boilerplate file. + */ +export function getBoilerplatePath(testType) { + return join(import.meta.dirname, customBoilerplates[testType] ?? DEFAULT_BOILERPLATE_PATH); +} + +/** + * Get the path to a given folder in the test directory + * @param {...string} folders the subfolders to append to the base path + * @returns {string} the path to the requested folder + */ +function getTestFolderPath(...folders) { + return join(projectRoot, "test", ...folders); +} + +/** + * Helper function to convert the test file name into an absolute path. + * @param {testType} testType - The type of test being created (used to look up folder) + * @param {string} fileName - The name of the test file (without suffix) + * @returns {string} + */ +export function getTestFileFullPath(testType, fileName) { + const absoluteDir = getTestFolderPath(testTypesToDirs[testType]); + return join(absoluteDir, `${fileName}.test.ts`); +} diff --git a/scripts/create-test/help-message.js b/scripts/create-test/help-message.js new file mode 100644 index 00000000000..e6589b40d89 --- /dev/null +++ b/scripts/create-test/help-message.js @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2025 Pagefault Games + * SPDX-FileContributor: Bertie690 + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import chalk from "chalk"; + +/** + * Array containing all valid ways of showing the help message. + */ +export const HELP_FLAGS = /** @type {const} */ (["-h", "-help", "--help"]); + +/** + * Show help/usage text for the `test:create` CLI. + * @package + */ +export function showHelpText() { + console.log(` +Usage: ${chalk.cyan("pnpm test:create [options] [testType] [fileName]")} +If either ${chalk.hex("#7fff00")("testType")} or ${chalk.hex("#7fff00")("fileName")} are omitted, +they will be selected interactively. + +${chalk.hex("#8a2be2")("Arguments:")} + ${chalk.hex("#7fff00")("testType")} The type/category of test file to create. + ${chalk.hex("#7fff00")("fileName")} The name of the test file to create. + +${chalk.hex("#ffa500")("Options:")} + ${chalk.blue("-h, -help, --help")} Show this help message. +`); +} diff --git a/scripts/create-test/interactive.js b/scripts/create-test/interactive.js new file mode 100644 index 00000000000..622e823e9cd --- /dev/null +++ b/scripts/create-test/interactive.js @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import chalk from "chalk"; +import inquirer from "inquirer"; +import { validTestTypes } from "./constants.js"; + +/** + * @import {testType} from "./constants.js" + */ +/** + * Prompt the user to select a test type via list. + * @returns {Promise} The selected type, or `undefined` if "Exit" was pressed. + */ +export async function promptTestType() { + /** @type {testType | "EXIT"} */ + const choice = ( + await inquirer.prompt([ + { + type: "list", + name: "selectedOption", + message: "What type of test would you like to create?", + choices: [...validTestTypes, "EXIT"], + }, + ]) + ).selectedOption; + + if (choice === "EXIT") { + console.log("Exiting..."); + process.exitCode = 0; + return; + } + + return choice; +} + +/** + * Prompt the user to provide a file name. + * @param {testType} selectedType - The chosen string (used for logging & validation) + * @returns {Promise} The selected file name + */ +export async function promptFileName(selectedType) { + /** @type {string} */ + const fileNameAnswer = ( + await inquirer.prompt([ + { + type: "input", + name: "userInput", + message: `Please provide the name of the ${selectedType}.`, + validate: name => { + const nameProcessed = name.trim().replace(".test.ts", ""); + if (nameProcessed.length === 0) { + return chalk.red.bold("✗ Cannot use an empty string as a file name!"); + } + return true; + }, + }, + ]) + ).userInput; + + // Trim whitespace and any extension suffixes + return fileNameAnswer.trim().replace(".test.ts", ""); +} diff --git a/scripts/helpers/file.js b/scripts/helpers/file.js new file mode 100644 index 00000000000..bd4ad5b9dd7 --- /dev/null +++ b/scripts/helpers/file.js @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2025 Pagefault Games + * SPDX-FileContributor: Bertie690 + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; + +/** + * @import {PathOrFileDescriptor, WriteFileOptions} from "node:fs" + */ + +/** + * "Safely" write to a file, creating any parent directories as required. + * @param {PathOrFileDescriptor} file - The filename or file descriptor to open + * @param {string | NodeJS.ArrayBufferView} content - The content which will be written + * @param {WriteFileOptions} [options] + * @returns {void} + * @remarks + * If `file` is a file descriptor, this method will simply return the result of + * {@linkcode writeFileSync} verbatim. + */ +export function writeFileSafe(file, content, options) { + if (typeof file === "number") { + return writeFileSync(file, content, options); + } + + const parentDir = dirname(file.toString("utf-8")); + if (!existsSync(parentDir)) { + mkdirSync(parentDir, { recursive: true }); + } + + writeFileSync(file, content, options); +} diff --git a/scripts/parse-egg-moves/main.js b/scripts/parse-egg-moves/main.js index e7b3af53fde..dd4fafb45cb 100644 --- a/scripts/parse-egg-moves/main.js +++ b/scripts/parse-egg-moves/main.js @@ -170,4 +170,4 @@ function badArgs() { process.exitCode = 1; } -start(); +await start(); diff --git a/scripts/scrape-trainer-names/main.js b/scripts/scrape-trainer-names/main.js index f0424ae5392..bb5f48c62c3 100644 --- a/scripts/scrape-trainer-names/main.js +++ b/scripts/scrape-trainer-names/main.js @@ -297,4 +297,4 @@ async function promptExisting(outFile) { ).continue; } -main(); +await main(); From c6986d9cf57c312ae11290452ab5c5bd8c5e9f14 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:58:56 -0500 Subject: [PATCH 027/101] [Dev] `eggMoves:parse` CLI script no longer errors without args/on exit (#6788) * [Dev] `eggMoves:parse` script no longer crashes without args, errors on exit no more 1 exit code or undefined errors * Version bump * comment fixes --------- Co-authored-by: Wlowscha <54003515+Wlowscha@users.noreply.github.com> --- .../parse-egg-moves/egg-move-template.boilerplate.ts | 6 ++++++ .../egg-move-template.boilerplate.ts.license | 4 ---- scripts/parse-egg-moves/interactive.js | 2 +- scripts/parse-egg-moves/main.js | 11 ++++++----- src/data/balance/egg-moves.ts | 6 ++++++ 5 files changed, 19 insertions(+), 10 deletions(-) delete mode 100644 scripts/parse-egg-moves/egg-move-template.boilerplate.ts.license diff --git a/scripts/parse-egg-moves/egg-move-template.boilerplate.ts b/scripts/parse-egg-moves/egg-move-template.boilerplate.ts index bfac05f4bde..8b7a64f3ebd 100644 --- a/scripts/parse-egg-moves/egg-move-template.boilerplate.ts +++ b/scripts/parse-egg-moves/egg-move-template.boilerplate.ts @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + //! DO NOT EDIT THIS FILE - CREATED BY THE `eggMoves:parse` script automatically import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/scripts/parse-egg-moves/egg-move-template.boilerplate.ts.license b/scripts/parse-egg-moves/egg-move-template.boilerplate.ts.license deleted file mode 100644 index ce549b94a45..00000000000 --- a/scripts/parse-egg-moves/egg-move-template.boilerplate.ts.license +++ /dev/null @@ -1,4 +0,0 @@ -SPDX-FileCopyrightText: 2025 Pagefault Games -SPDX-FileContributor: Bertie690 - -SPDX-License-Identifier: AGPL-3.0-only diff --git a/scripts/parse-egg-moves/interactive.js b/scripts/parse-egg-moves/interactive.js index f915b69f662..8e0f5e68d24 100644 --- a/scripts/parse-egg-moves/interactive.js +++ b/scripts/parse-egg-moves/interactive.js @@ -32,7 +32,7 @@ export async function runInteractive() { if (answer === "Exit") { console.log("Exiting..."); - process.exitCode = 1; + process.exitCode = 0; return { type: "Exit" }; } diff --git a/scripts/parse-egg-moves/main.js b/scripts/parse-egg-moves/main.js index dd4fafb45cb..ed7028a4ff3 100644 --- a/scripts/parse-egg-moves/main.js +++ b/scripts/parse-egg-moves/main.js @@ -18,7 +18,7 @@ import { showHelpText } from "./help-message.js"; import { runInteractive } from "./interactive.js"; import { parseEggMoves } from "./parse.js"; -const version = "1.0.0"; +const version = "1.0.1"; // Get the directory name of the current module file const __filename = fileURLToPath(import.meta.url); @@ -54,8 +54,8 @@ async function start() { let csv = ""; const inputType = await parseArguments(); - if (process.exitCode) { - // If exit code is non-zero, return to allow it to propagate up the chain. + // If exit code was set, return to allow it to propagate it up the chain. + if (process.exitCode != null) { return; } switch (inputType.type) { @@ -66,7 +66,6 @@ async function start() { csv = await fs.promises.readFile(inputType.value, "utf-8"); break; case "Exit": - // Help screen triggered; break out return; } @@ -80,8 +79,10 @@ async function start() { async function parseArguments() { const args = process.argv.slice(2); // first 2 args are node and script name (irrelevant) + // Yoink everything up to the first "=" to get the raw command, using nullish coaclescing to convert + // "no args" into "undefined" /** @type {string | undefined} */ - const arg = args[0].split("=")[0]; // Yoink everything up to the first "=" to get the raw command + const arg = args[0]?.split("=")[0]; switch (arg) { case "-f": case "--file": diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index f43ee573207..bb656174879 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + //! DO NOT EDIT THIS FILE - CREATED BY THE `eggMoves:parse` script automatically import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; From 32dec62406679248b5751a7abe7917d433daa0f3 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:51:58 -0500 Subject: [PATCH 028/101] [Dev] Add `.pnpm-store` to `.gitignore` (#6801) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 243d75097ef..99fdf873e33 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ dist dist-ssr *.local build +.pnpm-store # Editor directories and files (excluding `extensions.json` for devcontainer) *.code-workspace From 450113c29694c857ce413adc142d772faebde1bf Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 28 Nov 2025 22:42:23 -0800 Subject: [PATCH 029/101] [Dev] Update `pnpm` version in `package.json` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9550c0a9fc7..b256d863f83 100644 --- a/package.json +++ b/package.json @@ -81,5 +81,5 @@ "engines": { "node": ">=24.9.0" }, - "packageManager": "pnpm@10.19.0" + "packageManager": "pnpm@10.24.0" } From 9bfe5f02d8ec528ab502bca2ff609c393b8e69fe Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:49:20 -0600 Subject: [PATCH 030/101] [Deps] Update packages and add 1d min age for package updates (#6804) * [Deps] Update packages and add 1d min age for package updates * Fix tests --- package.json | 42 +- pnpm-lock.yaml | 1998 +++++++++++++--------------- pnpm-workspace.yaml | 2 + test/moves/flame-burst.test.ts | 62 +- test/ui/item-manage-button.test.ts | 3 +- 5 files changed, 990 insertions(+), 1117 deletions(-) diff --git a/package.json b/package.json index b256d863f83..2f5348b5c5b 100644 --- a/package.json +++ b/package.json @@ -41,42 +41,42 @@ "devDependencies": { "@biomejs/biome": "2.3.2", "@ls-lint/ls-lint": "2.3.1", - "@types/crypto-js": "^4.2.0", + "@types/crypto-js": "^4.2.2", "@types/jsdom": "^27.0.0", - "@types/node": "^24", - "@vitest/coverage-istanbul": "^3.2.4", - "@vitest/expect": "^3.2.4", - "@vitest/utils": "^3.2.4", - "chalk": "^5.4.1", - "dependency-cruiser": "^17.0.2", - "inquirer": "^12.8.2", - "jsdom": "^27.0.0", - "lefthook": "^1.12.2", - "msw": "^2.10.4", + "@types/node": "^24.10.1", + "@vitest/coverage-istanbul": "^4.0.14", + "@vitest/expect": "^4.0.14", + "@vitest/utils": "^4.0.14", + "chalk": "^5.6.2", + "dependency-cruiser": "^17.3.1", + "inquirer": "^13.0.1", + "jsdom": "^27.2.0", + "lefthook": "^2.0.4", + "msw": "^2.12.3", "phaser3spectorjs": "^0.0.8", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-github-theme": "^0.3.1", - "typedoc-plugin-coverage": "^4.0.1", - "typedoc-plugin-mdn-links": "^5.0.9", - "typescript": "^5.9.2", - "vite": "^7.0.7", + "typedoc-plugin-coverage": "^4.0.2", + "typedoc-plugin-mdn-links": "^5.0.10", + "typescript": "^5.9.3", + "vite": "^7.2.4", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.2.4", - "vitest-canvas-mock": "^0.3.3" + "vitest": "^4.0.14", + "vitest-canvas-mock": "^1.1.2" }, "dependencies": { "@material/material-color-utilities": "^0.3.0", "compare-versions": "^6.1.1", - "core-js": "^3.46.0", + "core-js": "^3.47.0", "crypto-js": "^4.2.0", - "i18next": "^25.5.3", + "i18next": "^25.6.3", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.3.0", "jszip": "^3.10.1", "phaser": "^3.90.0", - "phaser3-rex-plugins": "^1.80.16" + "phaser3-rex-plugins": "^1.80.17" }, "engines": { "node": ">=24.9.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c93f37b5f0b..09cbdd9ae23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,14 +15,14 @@ importers: specifier: ^6.1.1 version: 6.1.1 core-js: - specifier: ^3.46.0 - version: 3.46.0 + specifier: ^3.47.0 + version: 3.47.0 crypto-js: specifier: ^4.2.0 version: 4.2.0 i18next: - specifier: ^25.5.3 - version: 25.5.3(typescript@5.9.3) + specifier: ^25.6.3 + version: 25.6.3(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.2.0 version: 8.2.0 @@ -31,7 +31,7 @@ importers: version: 3.0.2 i18next-korean-postposition-processor: specifier: ^1.0.0 - version: 1.0.0(i18next@25.5.3(typescript@5.9.3)) + version: 1.0.0(i18next@25.6.3(typescript@5.9.3)) json-stable-stringify: specifier: ^1.3.0 version: 1.3.0 @@ -42,8 +42,8 @@ importers: specifier: ^3.90.0 version: 3.90.0 phaser3-rex-plugins: - specifier: ^1.80.16 - version: 1.80.16(graphology-types@0.24.8) + specifier: ^1.80.17 + version: 1.80.17(graphology-types@0.24.8) devDependencies: '@biomejs/biome': specifier: 2.3.2 @@ -52,79 +52,82 @@ importers: specifier: 2.3.1 version: 2.3.1 '@types/crypto-js': - specifier: ^4.2.0 + specifier: ^4.2.2 version: 4.2.2 '@types/jsdom': specifier: ^27.0.0 version: 27.0.0 '@types/node': - specifier: ^24 - version: 24.6.2 + specifier: ^24.10.1 + version: 24.10.1 '@vitest/coverage-istanbul': - specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1)) + specifier: ^4.0.14 + version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) '@vitest/expect': - specifier: ^3.2.4 - version: 3.2.4 + specifier: ^4.0.14 + version: 4.0.14 '@vitest/utils': - specifier: ^3.2.4 - version: 3.2.4 + specifier: ^4.0.14 + version: 4.0.14 chalk: - specifier: ^5.4.1 + specifier: ^5.6.2 version: 5.6.2 dependency-cruiser: - specifier: ^17.0.2 - version: 17.0.2 + specifier: ^17.3.1 + version: 17.3.1 inquirer: - specifier: ^12.8.2 - version: 12.9.6(@types/node@24.6.2) + specifier: ^13.0.1 + version: 13.0.1(@types/node@24.10.1) jsdom: - specifier: ^27.0.0 - version: 27.0.0(postcss@8.5.6) + specifier: ^27.2.0 + version: 27.2.0 lefthook: - specifier: ^1.12.2 - version: 1.13.6 + specifier: ^2.0.4 + version: 2.0.4 msw: - specifier: ^2.10.4 - version: 2.11.3(@types/node@24.6.2)(typescript@5.9.3) + specifier: ^2.12.3 + version: 2.12.3(@types/node@24.10.1)(typescript@5.9.3) phaser3spectorjs: specifier: ^0.0.8 version: 0.0.8 typedoc: - specifier: ^0.28.13 - version: 0.28.13(typescript@5.9.3) + specifier: ^0.28.14 + version: 0.28.14(typescript@5.9.3) typedoc-github-theme: specifier: ^0.3.1 - version: 0.3.1(typedoc@0.28.13(typescript@5.9.3)) + version: 0.3.1(typedoc@0.28.14(typescript@5.9.3)) typedoc-plugin-coverage: - specifier: ^4.0.1 - version: 4.0.1(typedoc@0.28.13(typescript@5.9.3)) + specifier: ^4.0.2 + version: 4.0.2(typedoc@0.28.14(typescript@5.9.3)) typedoc-plugin-mdn-links: - specifier: ^5.0.9 - version: 5.0.9(typedoc@0.28.13(typescript@5.9.3)) + specifier: ^5.0.10 + version: 5.0.10(typedoc@0.28.14(typescript@5.9.3)) typescript: - specifier: ^5.9.2 + specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.0.7 - version: 7.1.9(@types/node@24.6.2)(yaml@2.8.1) + specifier: ^7.2.4 + version: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.1.9(@types/node@24.6.2)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1) + specifier: ^4.0.14 + version: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) vitest-canvas-mock: - specifier: ^0.3.3 - version: 0.3.3(vitest@3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1)) + specifier: ^1.1.2 + version: 1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) packages: - '@asamuzakjp/css-color@4.0.5': - resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} + '@acemir/cssom@0.9.24': + resolution: {integrity: sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==} - '@asamuzakjp/dom-selector@6.5.7': - resolution: {integrity: sha512-cvdTPsi2qC1c22UppvuVmx/PDwuc6+QQkwt9OnwQD6Uotbh//tb2XDF0OoK2V0F4b8d02LIwNp3BieaDMAhIhA==} + '@asamuzakjp/css-color@4.1.0': + resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==} + + '@asamuzakjp/dom-selector@6.7.4': + resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -133,16 +136,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -167,8 +170,8 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': @@ -179,8 +182,8 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -192,12 +195,12 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@biomejs/biome@2.3.2': @@ -222,24 +225,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.3.2': resolution: {integrity: sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.3.2': resolution: {integrity: sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.3.2': resolution: {integrity: sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.3.2': resolution: {integrity: sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==} @@ -253,12 +260,6 @@ packages: cpu: [x64] os: [win32] - '@bundled-es-modules/cookie@2.0.1': - resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} - - '@bundled-es-modules/statuses@1.0.1': - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -283,181 +284,192 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-syntax-patches-for-csstree@1.0.14': - resolution: {integrity: sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==} + '@csstools/css-syntax-patches-for-csstree@1.0.19': + resolution: {integrity: sha512-QW5/SM2ARltEhoKcmRI1LoLf3/C7dHGswwCnfLcoMgqurBT4f8GvwXMgAbK/FwcxthmJRK5MGTtddj0yQn0J9g==} engines: {node: '>=18'} - peerDependencies: - postcss: ^8.4 '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@gerrit0/mini-shiki@3.13.0': - resolution: {integrity: sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==} + '@gerrit0/mini-shiki@3.17.0': + resolution: {integrity: sha512-Bpf6WuFar20ZXL6qU6VpVl4bVQfyyYiX+6O4xrns4nkU3Mr8paeupDbS1HENpcLOYj7pN4Rkd/yCaPA0vQwKww==} - '@inquirer/ansi@1.0.0': - resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} - '@inquirer/checkbox@4.2.4': - resolution: {integrity: sha512-2n9Vgf4HSciFq8ttKXk+qy+GsyTXPV1An6QAwe/8bkbbqvG4VW1I/ZY1pNu2rf+h9bdzMLPbRSfcNxkHBy/Ydw==} + '@inquirer/ansi@2.0.1': + resolution: {integrity: sha512-QAZUk6BBncv/XmSEZTscd8qazzjV3E0leUMrEPjxCd51QBgCKmprUGLex5DTsNtURm7LMzv+CLcd6S86xvBfYg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/checkbox@5.0.1': + resolution: {integrity: sha512-5VPFBK8jKdsjMK3DTFOlbR0+Kkd4q0AWB7VhWQn6ppv44dr3b7PU8wSJQTC5oA0f/aGW7v/ZozQJAY9zx6PKig==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -465,8 +477,17 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.18': - resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==} + '@inquirer/confirm@6.0.1': + resolution: {integrity: sha512-wD+pM7IxLn1TdcQN12Q6wcFe5VpyCuh/I2sSmqO5KjWH2R4v+GkUToHb+PsDGobOe1MtAlXMwGNkZUPc2+L6NA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -474,8 +495,115 @@ packages: '@types/node': optional: true - '@inquirer/core@10.2.2': - resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==} + '@inquirer/core@11.0.1': + resolution: {integrity: sha512-Tpf49h50e4KYffVUCXzkx4gWMafUi3aDQDwfVAAGBNnVcXiwJIj4m2bKlZ7Kgyf6wjt1eyXH1wDGXcAokm4Ssw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.0.1': + resolution: {integrity: sha512-zDKobHI7Ry++4noiV9Z5VfYgSVpPZoMApviIuGwLOMciQaP+dGzCO+1fcwI441riklRiZg4yURWyEoX0Zy2zZw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.0.1': + resolution: {integrity: sha512-TBrTpAB6uZNnGQHtSEkbvJZIQ3dXZOrwqQSO9uUbwct3G2LitwBCE5YZj98MbQ5nzihzs5pRjY1K9RRLH4WgoA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@2.0.1': + resolution: {integrity: sha512-BPYWJXCAK9w6R+pb2s3WyxUz9ts9SP/LDOUwA9fu7LeuyYgojz83i0DSRwezu736BgMwz14G63Xwj70hSzHohQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/figures@2.0.1': + resolution: {integrity: sha512-KtMxyjLCuDFqAWHmCY9qMtsZ09HnjMsm8H3OvpSIpfhHdfw3/AiGWHNrfRwbyvHPtOJpumm8wGn5fkhtvkWRsg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/input@5.0.1': + resolution: {integrity: sha512-cEhEUohCpE2BCuLKtFFZGp4Ief05SEcqeAOq9NxzN5ThOQP8Rl5N/Nt9VEDORK1bRb2Sk/zoOyQYfysPQwyQtA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.0.1': + resolution: {integrity: sha512-4//zgBGHe8Q/FfCoUXZUrUHyK/q5dyqiwsePz3oSSPSmw1Ijo35ZkjaftnxroygcUlLYfXqm+0q08lnB5hd49A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.0.1': + resolution: {integrity: sha512-UJudHpd7Ia30Q+x+ctYqI9Nh6SyEkaBscpa7J6Ts38oc1CNSws0I1hJEdxbQBlxQd65z5GEJPM4EtNf6tzfWaQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.0.1': + resolution: {integrity: sha512-MURRu/cyvLm9vchDDaVZ9u4p+ADnY0Mz3LQr0KTgihrrvuKZlqcWwlBC4lkOMvd0KKX4Wz7Ww9+uA7qEpQaqjg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.0.1': + resolution: {integrity: sha512-vVfVHKUgH6rZmMlyd0jOuGZo0Fw1jfcOqZF96lMwlgavx7g0x7MICe316bV01EEoI+c68vMdbkTTawuw3O+Fgw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.0.1': + resolution: {integrity: sha512-XwiaK5xBvr31STX6Ji8iS3HCRysBXfL/jUbTzufdWTS6LTGtvDQA50oVETt1BJgjKyQBp9vt0VU6AmU/AnOaGA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.0.1': + resolution: {integrity: sha512-gPByrgYoezGyKMq5KjV7Tuy1JU2ArIy6/sI8sprw0OpXope3VGQwP5FK1KD4eFFqEhKu470Dwe6/AyDPmGRA0Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -483,113 +611,15 @@ packages: '@types/node': optional: true - '@inquirer/editor@4.2.20': - resolution: {integrity: sha512-7omh5y5bK672Q+Brk4HBbnHNowOZwrb/78IFXdrEB9PfdxL3GudQyDk8O9vQ188wj3xrEebS2M9n18BjJoI83g==} - engines: {node: '>=18'} + '@inquirer/type@4.0.1': + resolution: {integrity: sha512-odO8YwoQAw/eVu/PSPsDDVPmqO77r/Mq7zcoF5VduVqIu2wSRWUgmYb5K9WH1no0SjLnOe8MDKtDL++z6mfo2g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/expand@4.0.20': - resolution: {integrity: sha512-Dt9S+6qUg94fEvgn54F2Syf0Z3U8xmnBI9ATq2f5h9xt09fs2IJXSCIXyyVHwvggKWFXEY/7jATRo2K6Dkn6Ow==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/external-editor@1.0.2': - resolution: {integrity: sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@1.0.13': - resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} - engines: {node: '>=18'} - - '@inquirer/input@4.2.4': - resolution: {integrity: sha512-cwSGpLBMwpwcZZsc6s1gThm0J+it/KIJ+1qFL2euLmSKUMGumJ5TcbMgxEjMjNHRGadouIYbiIgruKoDZk7klw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/number@3.0.20': - resolution: {integrity: sha512-bbooay64VD1Z6uMfNehED2A2YOPHSJnQLs9/4WNiV/EK+vXczf/R988itL2XLDGTgmhMF2KkiWZo+iEZmc4jqg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@4.0.20': - resolution: {integrity: sha512-nxSaPV2cPvvoOmRygQR+h0B+Av73B01cqYLcr7NXcGXhbmsYfUb8fDdw2Us1bI2YsX+VvY7I7upgFYsyf8+Nug==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/prompts@7.8.6': - resolution: {integrity: sha512-68JhkiojicX9SBUD8FE/pSKbOKtwoyaVj1kwqLfvjlVXZvOy3iaSWX4dCLsZyYx/5Ur07Fq+yuDNOen+5ce6ig==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/rawlist@4.1.8': - resolution: {integrity: sha512-CQ2VkIASbgI2PxdzlkeeieLRmniaUU1Aoi5ggEdm6BIyqopE9GuDXdDOj9XiwOqK5qm72oI2i6J+Gnjaa26ejg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/search@3.1.3': - resolution: {integrity: sha512-D5T6ioybJJH0IiSUK/JXcoRrrm8sXwzrVMjibuPs+AgxmogKslaafy1oxFiorNI4s3ElSkeQZbhYQgLqiL8h6Q==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/select@4.3.4': - resolution: {integrity: sha512-Qp20nySRmfbuJBBsgPU7E/cL62Hf250vMZRzYDcBHty2zdD1kKCnoDFWRr0WO2ZzaXp3R7a4esaVGJUx0E6zvA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/type@3.0.8': - resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} @@ -619,8 +649,8 @@ packages: '@material/material-color-utilities@0.3.0': resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} - '@mswjs/interceptors@0.39.7': - resolution: {integrity: sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==} + '@mswjs/interceptors@0.40.0': + resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} engines: {node: '>=18'} '@open-draft/deferred-promise@2.2.0': @@ -632,140 +662,147 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@rollup/rollup-android-arm-eabi@4.52.4': - resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.4': - resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.4': - resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.4': - resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.4': - resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.4': - resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': - resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.52.4': - resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.52.4': - resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.52.4': - resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.52.4': - resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.52.4': - resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.52.4': - resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.52.4': - resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.52.4': - resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.52.4': - resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.52.4': - resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-openharmony-arm64@4.52.4': - resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.4': - resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.4': - resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.4': - resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.4': - resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} cpu: [x64] os: [win32] - '@shikijs/engine-oniguruma@3.13.0': - resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==} + '@shikijs/engine-oniguruma@3.17.0': + resolution: {integrity: sha512-flSbHZAiOZDNTrEbULY8DLWavu/TyVu/E7RChpLB4WvKX4iHMfj80C6Hi3TjIWaQtHOW0KC6kzMcuB5TO1hZ8Q==} - '@shikijs/langs@3.13.0': - resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==} + '@shikijs/langs@3.17.0': + resolution: {integrity: sha512-icmur2n5Ojb+HAiQu6NEcIIJ8oWDFGGEpiqSCe43539Sabpx7Y829WR3QuUW2zjTM4l6V8Sazgb3rrHO2orEAw==} - '@shikijs/themes@3.13.0': - resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==} + '@shikijs/themes@3.17.0': + resolution: {integrity: sha512-/xEizMHLBmMHwtx4JuOkRf3zwhWD2bmG5BRr0IPjpcWpaq4C3mYEuTk/USAEglN0qPrTwEHwKVpSu/y2jhferA==} - '@shikijs/types@3.13.0': - resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==} + '@shikijs/types@3.17.0': + resolution: {integrity: sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/crypto-js@4.2.2': resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} @@ -782,8 +819,8 @@ packages: '@types/jsdom@27.0.0': resolution: {integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==} - '@types/node@24.6.2': - resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -794,39 +831,39 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@vitest/coverage-istanbul@3.2.4': - resolution: {integrity: sha512-IDlpuFJiWU9rhcKLkpzj8mFu/lpe64gVgnV15ZOrYx1iFzxxrxCzbExiUEKtwwXRvEiEMUS6iZeYgnMxgbqbxQ==} + '@vitest/coverage-istanbul@4.0.14': + resolution: {integrity: sha512-weQA5DR6/GaHL61WK0mnq9fzEeWxkpEawM4mp2WodMewLHKc1mCITJcmofNSMON2x0O951RjdnFsXsKi8WzSWg==} peerDependencies: - vitest: 3.2.4 + vitest: 4.0.14 - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.0.14': + resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + '@vitest/mocker@4.0.14': + resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.0.14': + resolution: {integrity: sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==} - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.0.14': + resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.0.14': + resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.0.14': + resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.0.14': + resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} acorn-jsx-walk@2.0.0: resolution: {integrity: sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==} @@ -882,8 +919,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.12: - resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} + baseline-browser-mapping@2.8.31: + resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} hasBin: true bidi-js@1.0.3: @@ -892,15 +929,11 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -913,11 +946,11 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - caniuse-lite@1.0.30001747: - resolution: {integrity: sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==} + caniuse-lite@1.0.30001757: + resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} chalk@4.1.2: @@ -928,12 +961,8 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chardet@2.1.0: - resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} @@ -950,8 +979,8 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@14.0.1: - resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==} + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} compare-versions@6.1.1: @@ -960,12 +989,12 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} - core-js@3.46.0: - resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -973,10 +1002,6 @@ packages: cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} @@ -987,8 +1012,8 @@ packages: cssfontparser@1.2.1: resolution: {integrity: sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==} - cssstyle@5.3.1: - resolution: {integrity: sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==} + cssstyle@5.3.3: + resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} engines: {node: '>=20'} dagre@0.8.5: @@ -1010,16 +1035,12 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - dependency-cruiser@17.0.2: - resolution: {integrity: sha512-Aryg/E8ostay8B7OBPqrxcxeGSgtPRKosP6do3L3TiPg4dAvIJFl2EFuG/mO8JAZv70pBTveKvKxhABPyNduvg==} + dependency-cruiser@17.3.1: + resolution: {integrity: sha512-yWwszB4GKIBKK/xiHSQ6TVIV6k8byd+gMGT2RMQ+03wb1jGH48cSsLH29iUUZgGtKyLSH51NurbnjXV0+niUjA==} engines: {node: ^20.12||^22||>=24} hasBin: true @@ -1027,18 +1048,15 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.262: + resolution: {integrity: sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==} - electron-to-chromium@1.5.230: - resolution: {integrity: sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -1066,8 +1084,8 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true @@ -1107,10 +1125,6 @@ packages: picomatch: optional: true - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1127,6 +1141,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1135,10 +1153,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -1164,8 +1178,8 @@ packages: peerDependencies: graphology-types: '>=0.24.0' - graphql@16.11.0: - resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + graphql@16.12.0: + resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} has-flag@4.0.0: @@ -1218,8 +1232,8 @@ packages: i18next@22.5.1: resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==} - i18next@25.5.3: - resolution: {integrity: sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==} + i18next@25.6.3: + resolution: {integrity: sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -1248,9 +1262,9 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inquirer@12.9.6: - resolution: {integrity: sha512-603xXOgyfxhuis4nfnWaZrMaotNT0Km9XwwBNWUKbIDqeCY89jGr2F9YPEMiNhU6XjIP4VoWISMBFfcc5NgrTw==} - engines: {node: '>=18'} + inquirer@13.0.1: + resolution: {integrity: sha512-+Qob/OSCmHIgyFKa4S+bDk36Nudwt+zpUBGZaSttGMnvsrzbIqtNFS9RutEPc2QAzpQxBP0cV3wmY/c5Vy73qg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -1289,9 +1303,6 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -1312,25 +1323,16 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jest-canvas-mock@2.5.2: - resolution: {integrity: sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - jsdom@27.0.0: - resolution: {integrity: sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==} - engines: {node: '>=20'} + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: @@ -1364,58 +1366,58 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lefthook-darwin-arm64@1.13.6: - resolution: {integrity: sha512-m6Lb77VGc84/Qo21Lhq576pEvcgFCnvloEiP02HbAHcIXD0RTLy9u2yAInrixqZeaz13HYtdDaI7OBYAAdVt8A==} + lefthook-darwin-arm64@2.0.4: + resolution: {integrity: sha512-AR63/O5UkM7Sc6x5PhP4vTuztTYRBeBroXApeWGM/8e5uZyoQug/7KTh7xhbCMDf8WJv6vdFeXAQCPSmDyPU3Q==} cpu: [arm64] os: [darwin] - lefthook-darwin-x64@1.13.6: - resolution: {integrity: sha512-CoRpdzanu9RK3oXR1vbEJA5LN7iB+c7hP+sONeQJzoOXuq4PNKVtEaN84Gl1BrVtCNLHWFAvCQaZPPiiXSy8qg==} + lefthook-darwin-x64@2.0.4: + resolution: {integrity: sha512-618DVUttSzV9egQiqTQoxGfnR240JoPWYmqRVHhiegnQKZ2lp5XJ+7NMxeRk/ih93VVOLzFO5ky3PbpxTmJgjQ==} cpu: [x64] os: [darwin] - lefthook-freebsd-arm64@1.13.6: - resolution: {integrity: sha512-X4A7yfvAJ68CoHTqP+XvQzdKbyd935sYy0bQT6Ajz7FL1g7hFiro8dqHSdPdkwei9hs8hXeV7feyTXbYmfjKQQ==} + lefthook-freebsd-arm64@2.0.4: + resolution: {integrity: sha512-mTAQym1BK38fKglHBQ/0GXPznVC4LoStHO5lAI3ZxaEC0FQetqGHYFzhWbIH5sde9JhztE2rL/aBzMHDoAtzSw==} cpu: [arm64] os: [freebsd] - lefthook-freebsd-x64@1.13.6: - resolution: {integrity: sha512-ai2m+Sj2kGdY46USfBrCqLKe9GYhzeq01nuyDYCrdGISePeZ6udOlD1k3lQKJGQCHb0bRz4St0r5nKDSh1x/2A==} + lefthook-freebsd-x64@2.0.4: + resolution: {integrity: sha512-sy02aSxd8UMd6XmiPFVl/Em0b78jdZcDSsLwg+bweJQQk0l+vJhOfqFiG11mbnpo+EBIZmRe6OH5LkxeSU36+w==} cpu: [x64] os: [freebsd] - lefthook-linux-arm64@1.13.6: - resolution: {integrity: sha512-cbo4Wtdq81GTABvikLORJsAWPKAJXE8Q5RXsICFUVznh5PHigS9dFW/4NXywo0+jfFPCT6SYds2zz4tCx6DA0Q==} + lefthook-linux-arm64@2.0.4: + resolution: {integrity: sha512-W0Nlr/Cz2QTH9n4k5zNrk3LSsg1C4wHiJi8hrAiQVTaAV/N1XrKqd0DevqQuouuapG6pw/6B1xCgiNPebv9oyw==} cpu: [arm64] os: [linux] - lefthook-linux-x64@1.13.6: - resolution: {integrity: sha512-uJl9vjCIIBTBvMZkemxCE+3zrZHlRO7Oc+nZJ+o9Oea3fu+W82jwX7a7clw8jqNfaeBS+8+ZEQgiMHWCloTsGw==} + lefthook-linux-x64@2.0.4: + resolution: {integrity: sha512-N6ySVCtB/DrOZ1ZgPL8WBZTgtoVHvcPKI+LV5wbcGrvA/dzDZFvniadrbDWZg7Tm705efiQzyENjwhhqNkwiww==} cpu: [x64] os: [linux] - lefthook-openbsd-arm64@1.13.6: - resolution: {integrity: sha512-7r153dxrNRQ9ytRs2PmGKKkYdvZYFPre7My7XToSTiRu5jNCq++++eAKVkoyWPduk97dGIA+YWiEr5Noe0TK2A==} + lefthook-openbsd-arm64@2.0.4: + resolution: {integrity: sha512-VmOhJO3pYzZ/1C2WFXtL/n5pq4/eYOroqJJpwTJfmCHyw4ceLACu8MDyU5AMJhGMkbL8mPxGInJKxg5xhYgGRw==} cpu: [arm64] os: [openbsd] - lefthook-openbsd-x64@1.13.6: - resolution: {integrity: sha512-Z+UhLlcg1xrXOidK3aLLpgH7KrwNyWYE3yb7ITYnzJSEV8qXnePtVu8lvMBHs/myzemjBzeIr/U/+ipjclR06g==} + lefthook-openbsd-x64@2.0.4: + resolution: {integrity: sha512-U8MZz1xlHUdflkQQ2hkMQsei6fSZbs8tuE4EjCIHWnNdnAF4V8sZ6n1KbxsJcoZXPyBZqxZSMu1o/Ye8IAMVKg==} cpu: [x64] os: [openbsd] - lefthook-windows-arm64@1.13.6: - resolution: {integrity: sha512-Uxef6qoDxCmUNQwk8eBvddYJKSBFglfwAY9Y9+NnnmiHpWTjjYiObE9gT2mvGVpEgZRJVAatBXc+Ha5oDD/OgQ==} + lefthook-windows-arm64@2.0.4: + resolution: {integrity: sha512-543H3y2JAwNdvwUQ6nlNBG7rdKgoOUgzAa6pYcl6EoqicCRrjRmGhkJu7vUudkkrD2Wjm7tr9hU9poP2g5fRFQ==} cpu: [arm64] os: [win32] - lefthook-windows-x64@1.13.6: - resolution: {integrity: sha512-mOZoM3FQh3o08M8PQ/b3IYuL5oo36D9ehczIw1dAgp1Ly+Tr4fJ96A+4SEJrQuYeRD4mex9bR7Ps56I73sBSZA==} + lefthook-windows-x64@2.0.4: + resolution: {integrity: sha512-UDEPK9RWKm60xsNOdS/DQOdFba0SFa4w3tpFMXK1AJzmRHhosoKrorXGhtTr6kcM0MGKOtYi8GHsm++ArZ9wvQ==} cpu: [x64] os: [win32] - lefthook@1.13.6: - resolution: {integrity: sha512-ojj4/4IJ29Xn4drd5emqVgilegAPN3Kf0FQM2p/9+lwSTpU+SZ1v4Ig++NF+9MOa99UKY8bElmVrLhnUUNFh5g==} + lefthook@2.0.4: + resolution: {integrity: sha512-GNCU2vQWM/UWjiEF23601aILi1aMbPke6viortH7wIO/oVGOCW0H6FdLez4XZDyqnHL9XkTnd0BBVrBbYVMLpA==} hasBin: true lie@3.3.0: @@ -1427,12 +1429,6 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.2: resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} @@ -1443,11 +1439,11 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -1467,8 +1463,8 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - memoize@10.1.0: - resolution: {integrity: sha512-MMbFhJzh4Jlg/poq1si90XRlTZRDHVqdlz2mPyGJ6kqMpyHUyVpDd5gpFAvVehW64+RA1eKE9Yt8aSLY7w2Kgg==} + memoize@10.2.0: + resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==} engines: {node: '>=18'} mimic-function@5.0.1: @@ -1482,18 +1478,14 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - moo-color@1.0.3: resolution: {integrity: sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.11.3: - resolution: {integrity: sha512-878imp8jxIpfzuzxYfX0qqTq1IFQz/1/RBHs/PyirSjzi+xKM/RRfIpIqHSCWjH0GxidrjhgiiXC+DWXNDvT9w==} + msw@2.12.3: + resolution: {integrity: sha512-/5rpGC0eK8LlFqsHaBmL19/PVKxu/CCt8pO1vzp9X6SDLsRDh/Ccudkf3Ur5lyaKxJz9ndAx+LaThdv0ySqB6A==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -1510,6 +1502,10 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1524,8 +1520,8 @@ packages: encoding: optional: true - node-releases@2.0.23: - resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -1534,12 +1530,12 @@ packages: obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -1549,29 +1545,20 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - phaser3-rex-plugins@1.80.16: - resolution: {integrity: sha512-c34SfEjdK7rz8EbM7bDmJwB/xCb/YR77IaAaowzqkCLb7pLeI3E8PfLivm882iirN11XOHjYT2uYlvtdbNAtMA==} + phaser3-rex-plugins@1.80.17: + resolution: {integrity: sha512-0xNgjA2rbh9323hP1dFwasYgKO1CyYJO6PwznnNTaWnn8ksbg9VQhprpAp4ASxSPkfn1ISgzxw8LqWjNJK8Rew==} phaser3spectorjs@0.0.8: resolution: {integrity: sha512-0dSO7/aMjEUPrp5EcjRvRRsEf+jXDbmzalPeJ6VtTB2Pn1PeaKc+qlL/DmO3l1Dvc5lkzc+Sil1Ta+Hkyi5cbA==} @@ -1624,22 +1611,19 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true rettime@0.7.0: resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} - rollup@4.52.4: - resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - run-async@4.0.6: resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} engines: {node: '>=0.12.0'} @@ -1664,8 +1648,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -1676,14 +1660,6 @@ packages: setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1705,8 +1681,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -1715,9 +1691,9 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -1734,9 +1710,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-literal@3.1.0: - resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1748,14 +1721,14 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tapable@2.3.0: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1766,23 +1739,15 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} - engines: {node: '>=14.0.0'} + tldts-core@7.0.19: + resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} - tldts-core@7.0.16: - resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==} - - tldts@7.0.16: - resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==} + tldts@7.0.19: + resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} hasBin: true tough-cookie@6.0.0: @@ -1817,9 +1782,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} + type-fest@5.2.0: + resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} + engines: {node: '>=20'} typedoc-github-theme@0.3.1: resolution: {integrity: sha512-j6PmkAGmf/MGCzYjQcUH6jS9djPsNl/IoTXooxC+MoeMkBhbmPyKJlpR6Lw12BLoe2OYpYA2J1KMktUJXp/8Sw==} @@ -1827,19 +1792,19 @@ packages: peerDependencies: typedoc: ~0.28.0 - typedoc-plugin-coverage@4.0.1: - resolution: {integrity: sha512-P1QBR5GJSfW3fDrpz4Vkd8z8lzWaBYjaHebRLk0u2Uga0oSFlPaqrCyiHzItBXxZX28aMlNlZwrUnsLgUgqA7g==} + typedoc-plugin-coverage@4.0.2: + resolution: {integrity: sha512-mfn0e7NCqB8x2PfvhXrtmd7KWlsNf1+B2N9y8gR/jexXBLrXl/0e+b2HdG5HaTXGi7i0t2pyQY2VRmq7gtdEHQ==} engines: {node: '>= 18'} peerDependencies: typedoc: 0.28.x - typedoc-plugin-mdn-links@5.0.9: - resolution: {integrity: sha512-kXssRKBhUd0JeHzFmxWVsGWVFR9WXafe70Y8Ed+MYH2Nu2647cqfGQN1OBKgvXpmAT8MTpACmUIQ7GnQnh1/iw==} + typedoc-plugin-mdn-links@5.0.10: + resolution: {integrity: sha512-TOMj1+fyhqhdJaMwfkw7ANz+0KHjRVUnE/SorPW83wghElmsMxxCZhDSBgF2hRB9+qsf/qIjDw65RDay94E2Wg==} peerDependencies: typedoc: 0.27.x || 0.28.x - typedoc@0.28.13: - resolution: {integrity: sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==} + typedoc@0.28.14: + resolution: {integrity: sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: @@ -1853,14 +1818,14 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - undici-types@7.13.0: - resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -1868,11 +1833,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -1881,8 +1841,8 @@ packages: vite: optional: true - vite@7.1.9: - resolution: {integrity: sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==} + vite@7.2.4: + resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1921,31 +1881,37 @@ packages: yaml: optional: true - vitest-canvas-mock@0.3.3: - resolution: {integrity: sha512-3P968tYBpqYyzzOaVtqnmYjqbe13576/fkjbDEJSfQAkHtC5/UjuRHOhFEN/ZV5HVZIkaROBUWgazDKJ+Ibw+Q==} + vitest-canvas-mock@1.1.2: + resolution: {integrity: sha512-cFPhB0CZDrpdp0ZnlvnFCuq8iKRct/RswWbhqNa2HchlKfwN6FAurLwfKWIdBrYAxIzcwIgME0Ytp/VRkV2APg==} peerDependencies: - vitest: '*' + vitest: ^3.0.0 || ^4.0.0 - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@4.0.14: + resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.14 + '@vitest/browser-preview': 4.0.14 + '@vitest/browser-webdriverio': 4.0.14 + '@vitest/ui': 4.0.14 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': optional: true '@vitest/ui': optional: true @@ -1958,9 +1924,9 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - watskeburt@4.2.3: - resolution: {integrity: sha512-uG9qtQYoHqAsnT711nG5iZc/8M5inSmkGCOp7pFaytKG2aTfIca7p//CjiVzAE4P7hzaYuCozMjNNaLgmhbK5g==} - engines: {node: ^18||>=20} + watskeburt@5.0.0: + resolution: {integrity: sha512-fEMhfIzu9WOuAJdDcTT+aPjn0JHI2+UeJ+zWSEs/tgMvc+MFDZVmhlZ8C1uJWXax1ETYc4trUnHFHyx2DrG0jQ==} + engines: {node: ^20.12||^22.13||>=24.0} hasBin: true webfontloader@1.6.28: @@ -1988,11 +1954,6 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -2006,9 +1967,9 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} @@ -2055,7 +2016,9 @@ packages: snapshots: - '@asamuzakjp/css-color@4.0.5': + '@acemir/cssom@0.9.24': {} + + '@asamuzakjp/css-color@4.1.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -2063,7 +2026,7 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 11.2.2 - '@asamuzakjp/dom-selector@6.5.7': + '@asamuzakjp/dom-selector@6.7.4': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 @@ -2075,23 +2038,23 @@ snapshots: '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.4': + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -2101,19 +2064,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -2121,59 +2084,59 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/parser@7.28.4': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@biomejs/biome@2.3.2': optionalDependencies: @@ -2210,14 +2173,6 @@ snapshots: '@biomejs/cli-win32-x64@2.3.2': optional: true - '@bundled-es-modules/cookie@2.0.1': - dependencies: - cookie: 0.7.2 - - '@bundled-es-modules/statuses@1.0.1': - dependencies: - statuses: 2.0.2 - '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -2236,231 +2191,242 @@ snapshots: dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.6)': - dependencies: - postcss: 8.5.6 + '@csstools/css-syntax-patches-for-csstree@1.0.19': {} '@csstools/css-tokenizer@3.0.4': {} - '@esbuild/aix-ppc64@0.25.10': + '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.25.10': + '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.25.10': + '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.25.10': + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.25.10': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.25.10': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.25.10': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.25.10': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.25.10': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.25.10': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.25.10': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.25.10': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.25.10': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.25.10': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.25.10': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.25.10': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.25.10': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.25.10': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.25.10': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.25.10': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.25.10': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.25.10': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.25.10': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.25.10': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.25.10': + '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.25.10': + '@esbuild/win32-x64@0.25.12': optional: true - '@gerrit0/mini-shiki@3.13.0': + '@gerrit0/mini-shiki@3.17.0': dependencies: - '@shikijs/engine-oniguruma': 3.13.0 - '@shikijs/langs': 3.13.0 - '@shikijs/themes': 3.13.0 - '@shikijs/types': 3.13.0 + '@shikijs/engine-oniguruma': 3.17.0 + '@shikijs/langs': 3.17.0 + '@shikijs/themes': 3.17.0 + '@shikijs/types': 3.17.0 '@shikijs/vscode-textmate': 10.0.2 - '@inquirer/ansi@1.0.0': {} + '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.2.4(@types/node@24.6.2)': + '@inquirer/ansi@2.0.1': {} + + '@inquirer/checkbox@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.6.2) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/confirm@5.1.18(@types/node@24.6.2)': + '@inquirer/confirm@5.1.21(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) + '@inquirer/core': 10.3.2(@types/node@24.10.1) + '@inquirer/type': 3.0.10(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/core@10.2.2(@types/node@24.6.2)': + '@inquirer/confirm@6.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.6.2) + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) + optionalDependencies: + '@types/node': 24.10.1 + + '@inquirer/core@10.3.2(@types/node@24.10.1)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.10.1) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/editor@4.2.20(@types/node@24.6.2)': + '@inquirer/core@11.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/external-editor': 1.0.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) + '@inquirer/ansi': 2.0.1 + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) + cli-width: 4.1.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + wrap-ansi: 9.0.2 optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/expand@4.0.20(@types/node@24.6.2)': + '@inquirer/editor@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/external-editor': 2.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/external-editor@1.0.2(@types/node@24.6.2)': + '@inquirer/expand@5.0.1(@types/node@24.10.1)': dependencies: - chardet: 2.1.0 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) + optionalDependencies: + '@types/node': 24.10.1 + + '@inquirer/external-editor@2.0.1(@types/node@24.10.1)': + dependencies: + chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/figures@1.0.13': {} + '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.2.4(@types/node@24.6.2)': + '@inquirer/figures@2.0.1': {} + + '@inquirer/input@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/number@3.0.20(@types/node@24.6.2)': + '@inquirer/number@4.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/password@4.0.20(@types/node@24.6.2)': + '@inquirer/password@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/prompts@7.8.6(@types/node@24.6.2)': + '@inquirer/prompts@8.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/checkbox': 4.2.4(@types/node@24.6.2) - '@inquirer/confirm': 5.1.18(@types/node@24.6.2) - '@inquirer/editor': 4.2.20(@types/node@24.6.2) - '@inquirer/expand': 4.0.20(@types/node@24.6.2) - '@inquirer/input': 4.2.4(@types/node@24.6.2) - '@inquirer/number': 3.0.20(@types/node@24.6.2) - '@inquirer/password': 4.0.20(@types/node@24.6.2) - '@inquirer/rawlist': 4.1.8(@types/node@24.6.2) - '@inquirer/search': 3.1.3(@types/node@24.6.2) - '@inquirer/select': 4.3.4(@types/node@24.6.2) + '@inquirer/checkbox': 5.0.1(@types/node@24.10.1) + '@inquirer/confirm': 6.0.1(@types/node@24.10.1) + '@inquirer/editor': 5.0.1(@types/node@24.10.1) + '@inquirer/expand': 5.0.1(@types/node@24.10.1) + '@inquirer/input': 5.0.1(@types/node@24.10.1) + '@inquirer/number': 4.0.1(@types/node@24.10.1) + '@inquirer/password': 5.0.1(@types/node@24.10.1) + '@inquirer/rawlist': 5.0.1(@types/node@24.10.1) + '@inquirer/search': 4.0.1(@types/node@24.10.1) + '@inquirer/select': 5.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/rawlist@4.1.8(@types/node@24.6.2)': + '@inquirer/rawlist@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/search@3.1.3(@types/node@24.6.2)': + '@inquirer/search@4.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.6.2) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/select@4.3.4(@types/node@24.6.2)': + '@inquirer/select@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.6.2) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@inquirer/type@3.0.8(@types/node@24.6.2)': + '@inquirer/type@3.0.10(@types/node@24.10.1)': optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@inquirer/type@4.0.1(@types/node@24.10.1)': + optionalDependencies: + '@types/node': 24.10.1 '@istanbuljs/schema@0.1.3': {} @@ -2487,7 +2453,7 @@ snapshots: '@material/material-color-utilities@0.3.0': {} - '@mswjs/interceptors@0.39.7': + '@mswjs/interceptors@0.40.0': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -2505,100 +2471,98 @@ snapshots: '@open-draft/until@2.1.0': {} - '@pkgjs/parseargs@0.11.0': + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true - '@rollup/rollup-android-arm-eabi@4.52.4': + '@rollup/rollup-android-arm64@4.53.3': optional: true - '@rollup/rollup-android-arm64@4.52.4': + '@rollup/rollup-darwin-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-arm64@4.52.4': + '@rollup/rollup-darwin-x64@4.53.3': optional: true - '@rollup/rollup-darwin-x64@4.52.4': + '@rollup/rollup-freebsd-arm64@4.53.3': optional: true - '@rollup/rollup-freebsd-arm64@4.52.4': + '@rollup/rollup-freebsd-x64@4.53.3': optional: true - '@rollup/rollup-freebsd-x64@4.52.4': + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + '@rollup/rollup-linux-arm-musleabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.4': + '@rollup/rollup-linux-arm64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.4': + '@rollup/rollup-linux-arm64-musl@4.53.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.4': + '@rollup/rollup-linux-loong64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.4': + '@rollup/rollup-linux-ppc64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.4': + '@rollup/rollup-linux-riscv64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.4': + '@rollup/rollup-linux-riscv64-musl@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.4': + '@rollup/rollup-linux-s390x-gnu@4.53.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.4': + '@rollup/rollup-linux-x64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.4': + '@rollup/rollup-linux-x64-musl@4.53.3': optional: true - '@rollup/rollup-linux-x64-musl@4.52.4': + '@rollup/rollup-openharmony-arm64@4.53.3': optional: true - '@rollup/rollup-openharmony-arm64@4.52.4': + '@rollup/rollup-win32-arm64-msvc@4.53.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.4': + '@rollup/rollup-win32-ia32-msvc@4.53.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.4': + '@rollup/rollup-win32-x64-gnu@4.53.3': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.4': + '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.4': - optional: true - - '@shikijs/engine-oniguruma@3.13.0': + '@shikijs/engine-oniguruma@3.17.0': dependencies: - '@shikijs/types': 3.13.0 + '@shikijs/types': 3.17.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.13.0': + '@shikijs/langs@3.17.0': dependencies: - '@shikijs/types': 3.13.0 + '@shikijs/types': 3.17.0 - '@shikijs/themes@3.13.0': + '@shikijs/themes@3.17.0': dependencies: - '@shikijs/types': 3.13.0 + '@shikijs/types': 3.17.0 - '@shikijs/types@3.13.0': + '@shikijs/types@3.17.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 '@shikijs/vscode-textmate@10.0.2': {} - '@types/chai@5.2.2': + '@standard-schema/spec@1.0.0': {} + + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 - - '@types/cookie@0.6.0': {} + assertion-error: 2.0.1 '@types/crypto-js@4.2.2': {} @@ -2612,13 +2576,13 @@ snapshots: '@types/jsdom@27.0.0': dependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 - '@types/node@24.6.2': + '@types/node@24.10.1': dependencies: - undici-types: 7.13.0 + undici-types: 7.16.0 '@types/statuses@2.0.6': {} @@ -2626,64 +2590,60 @@ snapshots: '@types/unist@3.0.3': {} - '@vitest/coverage-istanbul@3.2.4(vitest@3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1))': + '@vitest/coverage-istanbul@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1))': dependencies: '@istanbuljs/schema': 0.1.3 - debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - magicast: 0.3.5 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1) + magicast: 0.5.1 + obug: 2.1.1 + tinyrainbow: 3.0.3 + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/expect@3.2.4': + '@vitest/expect@4.0.14': dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - tinyrainbow: 2.0.0 + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + chai: 6.2.1 + tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(vite@7.1.9(@types/node@24.6.2)(yaml@2.8.1))': + '@vitest/mocker@4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 4.0.14 estree-walker: 3.0.3 - magic-string: 0.30.19 + magic-string: 0.30.21 optionalDependencies: - msw: 2.11.3(@types/node@24.6.2)(typescript@5.9.3) - vite: 7.1.9(@types/node@24.6.2)(yaml@2.8.1) + msw: 2.12.3(@types/node@24.10.1)(typescript@5.9.3) + vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) - '@vitest/pretty-format@3.2.4': + '@vitest/pretty-format@4.0.14': dependencies: - tinyrainbow: 2.0.0 + tinyrainbow: 3.0.3 - '@vitest/runner@3.2.4': + '@vitest/runner@4.0.14': dependencies: - '@vitest/utils': 3.2.4 - pathe: 2.0.3 - strip-literal: 3.1.0 - - '@vitest/snapshot@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.19 + '@vitest/utils': 4.0.14 pathe: 2.0.3 - '@vitest/spy@3.2.4': + '@vitest/snapshot@4.0.14': dependencies: - tinyspy: 4.0.4 + '@vitest/pretty-format': 4.0.14 + magic-string: 0.30.21 + pathe: 2.0.3 - '@vitest/utils@3.2.4': + '@vitest/spy@4.0.14': {} + + '@vitest/utils@4.0.14': dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 + '@vitest/pretty-format': 4.0.14 + tinyrainbow: 3.0.3 acorn-jsx-walk@2.0.0: {} @@ -2726,7 +2686,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.12: {} + baseline-browser-mapping@2.8.31: {} bidi-js@1.0.3: dependencies: @@ -2736,15 +2696,13 @@ snapshots: dependencies: balanced-match: 1.0.2 - browserslist@4.26.3: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.12 - caniuse-lite: 1.0.30001747 - electron-to-chromium: 1.5.230 - node-releases: 2.0.23 - update-browserslist-db: 1.1.3(browserslist@4.26.3) - - cac@6.7.14: {} + baseline-browser-mapping: 2.8.31 + caniuse-lite: 1.0.30001757 + electron-to-chromium: 1.5.262 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) call-bind-apply-helpers@1.0.2: dependencies: @@ -2763,15 +2721,9 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - caniuse-lite@1.0.30001747: {} + caniuse-lite@1.0.30001757: {} - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.1: {} chalk@4.1.2: dependencies: @@ -2780,9 +2732,7 @@ snapshots: chalk@5.6.2: {} - chardet@2.1.0: {} - - check-error@2.1.1: {} + chardet@2.1.1: {} cli-width@4.1.0: {} @@ -2798,15 +2748,15 @@ snapshots: color-name@1.1.4: {} - commander@14.0.1: {} + commander@14.0.2: {} compare-versions@6.1.1: {} convert-source-map@2.0.0: {} - cookie@0.7.2: {} + cookie@1.1.1: {} - core-js@3.46.0: {} + core-js@3.47.0: {} core-util-is@1.0.3: {} @@ -2816,12 +2766,6 @@ snapshots: transitivePeerDependencies: - encoding - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - crypto-js@4.2.0: {} css-tree@3.1.0: @@ -2831,13 +2775,11 @@ snapshots: cssfontparser@1.2.1: {} - cssstyle@5.3.1(postcss@8.5.6): + cssstyle@5.3.3: dependencies: - '@asamuzakjp/css-color': 4.0.5 - '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) + '@asamuzakjp/css-color': 4.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.0.19 css-tree: 3.1.0 - transitivePeerDependencies: - - postcss dagre@0.8.5: dependencies: @@ -2855,15 +2797,13 @@ snapshots: decimal.js@10.6.0: {} - deep-eql@5.0.2: {} - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - dependency-cruiser@17.0.2: + dependency-cruiser@17.3.1: dependencies: acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) @@ -2871,20 +2811,20 @@ snapshots: acorn-loose: 8.5.2 acorn-walk: 8.3.4 ajv: 8.17.1 - commander: 14.0.1 + commander: 14.0.2 enhanced-resolve: 5.18.3 ignore: 7.0.5 interpret: 3.1.1 is-installed-globally: 1.0.0 json5: 2.2.3 - memoize: 10.1.0 + memoize: 10.2.0 picomatch: 4.0.3 prompts: 2.4.2 rechoir: 0.8.0 safe-regex: 2.1.1 - semver: 7.7.2 + semver: 7.7.3 tsconfig-paths-webpack-plugin: 4.2.0 - watskeburt: 4.2.3 + watskeburt: 5.0.0 dunder-proto@1.0.1: dependencies: @@ -2892,14 +2832,12 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.262: {} - electron-to-chromium@1.5.230: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -2919,34 +2857,34 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.25.10: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -2970,11 +2908,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fsevents@2.3.3: optional: true @@ -2984,6 +2917,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3002,15 +2937,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -3033,7 +2959,7 @@ snapshots: graphology-types: 0.24.8 obliterator: 2.0.5 - graphql@16.11.0: {} + graphql@16.12.0: {} has-flag@4.0.0: {} @@ -3085,15 +3011,15 @@ snapshots: transitivePeerDependencies: - encoding - i18next-korean-postposition-processor@1.0.0(i18next@25.5.3(typescript@5.9.3)): + i18next-korean-postposition-processor@1.0.0(i18next@25.6.3(typescript@5.9.3)): dependencies: - i18next: 25.5.3(typescript@5.9.3) + i18next: 25.6.3(typescript@5.9.3) i18next@22.5.1: dependencies: '@babel/runtime': 7.28.4 - i18next@25.5.3(typescript@5.9.3): + i18next@25.6.3(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 optionalDependencies: @@ -3115,17 +3041,17 @@ snapshots: ini@4.1.1: {} - inquirer@12.9.6(@types/node@24.6.2): + inquirer@13.0.1(@types/node@24.10.1): dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@24.6.2) - '@inquirer/prompts': 7.8.6(@types/node@24.6.2) - '@inquirer/type': 3.0.8(@types/node@24.6.2) - mute-stream: 2.0.0 + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/prompts': 8.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) + mute-stream: 3.0.0 run-async: 4.0.6 rxjs: 7.8.2 optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 interpret@3.1.1: {} @@ -3150,17 +3076,15 @@ snapshots: isarray@2.0.5: {} - isexe@2.0.0: {} - istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -3183,37 +3107,24 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jest-canvas-mock@2.5.2: - dependencies: - cssfontparser: 1.2.1 - moo-color: 1.0.3 - js-tokens@4.0.0: {} - js-tokens@9.0.1: {} - - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 - jsdom@27.0.0(postcss@8.5.6): + jsdom@27.2.0: dependencies: - '@asamuzakjp/dom-selector': 6.5.7 - cssstyle: 5.3.1(postcss@8.5.6) + '@acemir/cssom': 0.9.24 + '@asamuzakjp/dom-selector': 6.7.4 + cssstyle: 5.3.3 data-urls: 6.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - parse5: 7.3.0 - rrweb-cssom: 0.8.0 + parse5: 8.0.0 saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.0 @@ -3226,7 +3137,6 @@ snapshots: xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil - - postcss - supports-color - utf-8-validate @@ -3255,48 +3165,48 @@ snapshots: kleur@3.0.3: {} - lefthook-darwin-arm64@1.13.6: + lefthook-darwin-arm64@2.0.4: optional: true - lefthook-darwin-x64@1.13.6: + lefthook-darwin-x64@2.0.4: optional: true - lefthook-freebsd-arm64@1.13.6: + lefthook-freebsd-arm64@2.0.4: optional: true - lefthook-freebsd-x64@1.13.6: + lefthook-freebsd-x64@2.0.4: optional: true - lefthook-linux-arm64@1.13.6: + lefthook-linux-arm64@2.0.4: optional: true - lefthook-linux-x64@1.13.6: + lefthook-linux-x64@2.0.4: optional: true - lefthook-openbsd-arm64@1.13.6: + lefthook-openbsd-arm64@2.0.4: optional: true - lefthook-openbsd-x64@1.13.6: + lefthook-openbsd-x64@2.0.4: optional: true - lefthook-windows-arm64@1.13.6: + lefthook-windows-arm64@2.0.4: optional: true - lefthook-windows-x64@1.13.6: + lefthook-windows-x64@2.0.4: optional: true - lefthook@1.13.6: + lefthook@2.0.4: optionalDependencies: - lefthook-darwin-arm64: 1.13.6 - lefthook-darwin-x64: 1.13.6 - lefthook-freebsd-arm64: 1.13.6 - lefthook-freebsd-x64: 1.13.6 - lefthook-linux-arm64: 1.13.6 - lefthook-linux-x64: 1.13.6 - lefthook-openbsd-arm64: 1.13.6 - lefthook-openbsd-x64: 1.13.6 - lefthook-windows-arm64: 1.13.6 - lefthook-windows-x64: 1.13.6 + lefthook-darwin-arm64: 2.0.4 + lefthook-darwin-x64: 2.0.4 + lefthook-freebsd-arm64: 2.0.4 + lefthook-freebsd-x64: 2.0.4 + lefthook-linux-arm64: 2.0.4 + lefthook-linux-x64: 2.0.4 + lefthook-openbsd-arm64: 2.0.4 + lefthook-openbsd-x64: 2.0.4 + lefthook-windows-arm64: 2.0.4 + lefthook-windows-x64: 2.0.4 lie@3.3.0: dependencies: @@ -3308,10 +3218,6 @@ snapshots: lodash@4.17.21: {} - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - lru-cache@11.2.2: {} lru-cache@5.1.1: @@ -3320,19 +3226,19 @@ snapshots: lunr@2.3.9: {} - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: + magicast@0.5.1: dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 source-map-js: 1.2.1 make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 markdown-it@14.1.0: dependencies: @@ -3349,7 +3255,7 @@ snapshots: mdurl@2.0.0: {} - memoize@10.1.0: + memoize@10.2.0: dependencies: mimic-function: 5.0.1 @@ -3361,33 +3267,30 @@ snapshots: minimist@1.2.8: {} - minipass@7.1.2: {} - moo-color@1.0.3: dependencies: color-name: 1.1.4 ms@2.1.3: {} - msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3): + msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3): dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@inquirer/confirm': 5.1.18(@types/node@24.6.2) - '@mswjs/interceptors': 0.39.7 + '@inquirer/confirm': 5.1.21(@types/node@24.10.1) + '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 - '@types/cookie': 0.6.0 '@types/statuses': 2.0.6 - graphql: 16.11.0 + cookie: 1.1.1 + graphql: 16.12.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 path-to-regexp: 6.3.0 picocolors: 1.1.1 rettime: 0.7.0 + statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 4.41.0 + type-fest: 5.2.0 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -3399,21 +3302,23 @@ snapshots: mute-stream@2.0.0: {} + mute-stream@3.0.0: {} + nanoid@3.3.11: {} node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.23: {} + node-releases@2.0.27: {} object-keys@1.1.1: {} obliterator@2.0.5: {} - outvariant@1.4.3: {} + obug@2.1.1: {} - package-json-from-dist@1.0.1: {} + outvariant@1.4.3: {} pako@1.0.11: {} @@ -3423,29 +3328,24 @@ snapshots: dependencies: entities: 6.0.1 - path-key@3.1.1: {} + parse5@8.0.0: + dependencies: + entities: 6.0.1 path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-to-regexp@6.3.0: {} pathe@2.0.3: {} - pathval@2.0.1: {} - - phaser3-rex-plugins@1.80.16(graphology-types@0.24.8): + phaser3-rex-plugins@1.80.17(graphology-types@0.24.8): dependencies: dagre: 0.8.5 eventemitter3: 3.1.2 graphology: 0.25.4(graphology-types@0.24.8) i18next: 22.5.1 i18next-http-backend: 2.7.3 - js-yaml: 4.1.0 + js-yaml: 4.1.1 mustache: 4.2.0 papaparse: 5.5.3 webfontloader: 1.6.28 @@ -3492,7 +3392,7 @@ snapshots: rechoir@0.8.0: dependencies: - resolve: 1.22.10 + resolve: 1.22.11 regexp-tree@0.1.27: {} @@ -3500,7 +3400,7 @@ snapshots: require-from-string@2.0.2: {} - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 @@ -3508,36 +3408,34 @@ snapshots: rettime@0.7.0: {} - rollup@4.52.4: + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.4 - '@rollup/rollup-android-arm64': 4.52.4 - '@rollup/rollup-darwin-arm64': 4.52.4 - '@rollup/rollup-darwin-x64': 4.52.4 - '@rollup/rollup-freebsd-arm64': 4.52.4 - '@rollup/rollup-freebsd-x64': 4.52.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 - '@rollup/rollup-linux-arm-musleabihf': 4.52.4 - '@rollup/rollup-linux-arm64-gnu': 4.52.4 - '@rollup/rollup-linux-arm64-musl': 4.52.4 - '@rollup/rollup-linux-loong64-gnu': 4.52.4 - '@rollup/rollup-linux-ppc64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-musl': 4.52.4 - '@rollup/rollup-linux-s390x-gnu': 4.52.4 - '@rollup/rollup-linux-x64-gnu': 4.52.4 - '@rollup/rollup-linux-x64-musl': 4.52.4 - '@rollup/rollup-openharmony-arm64': 4.52.4 - '@rollup/rollup-win32-arm64-msvc': 4.52.4 - '@rollup/rollup-win32-ia32-msvc': 4.52.4 - '@rollup/rollup-win32-x64-gnu': 4.52.4 - '@rollup/rollup-win32-x64-msvc': 4.52.4 + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 - rrweb-cssom@0.8.0: {} - run-async@4.0.6: {} rxjs@7.8.2: @@ -3558,7 +3456,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.3: {} set-function-length@1.2.2: dependencies: @@ -3571,12 +3469,6 @@ snapshots: setimmediate@1.0.5: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -3589,7 +3481,7 @@ snapshots: statuses@2.0.2: {} - std-env@3.9.0: {} + std-env@3.10.0: {} strict-event-emitter@0.5.1: {} @@ -3599,10 +3491,10 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: + string-width@7.2.0: dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 string_decoder@1.1.1: @@ -3619,10 +3511,6 @@ snapshots: strip-bom@3.0.0: {} - strip-literal@3.1.0: - dependencies: - js-tokens: 9.0.1 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3631,13 +3519,9 @@ snapshots: symbol-tree@3.2.4: {} - tapable@2.3.0: {} + tagged-tag@1.0.0: {} - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.4.5 - minimatch: 9.0.5 + tapable@2.3.0: {} tinybench@2.9.0: {} @@ -3648,21 +3532,17 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.1.1: {} + tinyrainbow@3.0.3: {} - tinyrainbow@2.0.0: {} + tldts-core@7.0.19: {} - tinyspy@4.0.4: {} - - tldts-core@7.0.16: {} - - tldts@7.0.16: + tldts@7.0.19: dependencies: - tldts-core: 7.0.16 + tldts-core: 7.0.19 tough-cookie@6.0.0: dependencies: - tldts: 7.0.16 + tldts: 7.0.19 tr46@0.0.3: {} @@ -3689,23 +3569,25 @@ snapshots: tslib@2.8.1: {} - type-fest@4.41.0: {} - - typedoc-github-theme@0.3.1(typedoc@0.28.13(typescript@5.9.3)): + type-fest@5.2.0: dependencies: - typedoc: 0.28.13(typescript@5.9.3) + tagged-tag: 1.0.0 - typedoc-plugin-coverage@4.0.1(typedoc@0.28.13(typescript@5.9.3)): + typedoc-github-theme@0.3.1(typedoc@0.28.14(typescript@5.9.3)): dependencies: - typedoc: 0.28.13(typescript@5.9.3) + typedoc: 0.28.14(typescript@5.9.3) - typedoc-plugin-mdn-links@5.0.9(typedoc@0.28.13(typescript@5.9.3)): + typedoc-plugin-coverage@4.0.2(typedoc@0.28.14(typescript@5.9.3)): dependencies: - typedoc: 0.28.13(typescript@5.9.3) + typedoc: 0.28.14(typescript@5.9.3) - typedoc@0.28.13(typescript@5.9.3): + typedoc-plugin-mdn-links@5.0.10(typedoc@0.28.14(typescript@5.9.3)): dependencies: - '@gerrit0/mini-shiki': 3.13.0 + typedoc: 0.28.14(typescript@5.9.3) + + typedoc@0.28.14(typescript@5.9.3): + dependencies: + '@gerrit0/mini-shiki': 3.17.0 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 @@ -3716,96 +3598,73 @@ snapshots: uc.micro@2.1.0: {} - undici-types@7.13.0: {} + undici-types@7.16.0: {} until-async@3.0.2: {} - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.26.3 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 util-deprecate@1.0.2: {} - vite-node@3.2.4(@types/node@24.6.2)(yaml@2.8.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.1.9(@types/node@24.6.2)(yaml@2.8.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.9(@types/node@24.6.2)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.1.9(@types/node@24.6.2)(yaml@2.8.1) + vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript - vite@7.1.9(@types/node@24.6.2)(yaml@2.8.1): + vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1): dependencies: - esbuild: 0.25.10 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.4 + rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 24.10.1 fsevents: 2.3.3 yaml: 2.8.1 - vitest-canvas-mock@0.3.3(vitest@3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1)): + vitest-canvas-mock@1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)): dependencies: - jest-canvas-mock: 2.5.2 - vitest: 3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1) + cssfontparser: 1.2.1 + moo-color: 1.0.3 + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) - vitest@3.2.4(@types/node@24.6.2)(jsdom@27.0.0(postcss@8.5.6))(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(yaml@2.8.1): + vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1): dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@24.6.2)(typescript@5.9.3))(vite@7.1.9(@types/node@24.6.2)(yaml@2.8.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3 + '@vitest/expect': 4.0.14 + '@vitest/mocker': 4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.14 + '@vitest/runner': 4.0.14 + '@vitest/snapshot': 4.0.14 + '@vitest/spy': 4.0.14 + '@vitest/utils': 4.0.14 + es-module-lexer: 1.7.0 expect-type: 1.2.2 - magic-string: 0.30.19 + magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.9.0 + std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.1.9(@types/node@24.6.2)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.6.2)(yaml@2.8.1) + tinyrainbow: 3.0.3 + vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.6.2 - jsdom: 27.0.0(postcss@8.5.6) + '@types/node': 24.10.1 + jsdom: 27.2.0 transitivePeerDependencies: - jiti - less @@ -3815,7 +3674,6 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml @@ -3824,7 +3682,7 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - watskeburt@4.2.3: {} + watskeburt@5.0.0: {} webfontloader@1.6.28: {} @@ -3848,10 +3706,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which@2.0.2: - dependencies: - isexe: 2.0.0 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -3869,10 +3723,10 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 - string-width: 5.1.2 + string-width: 7.2.0 strip-ansi: 7.1.2 ws@8.18.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e457e38a05c..8eba78ab1b7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,5 @@ onlyBuiltDependencies: - msw shellEmulator: true + +minimumReleaseAge: 1440 diff --git a/test/moves/flame-burst.test.ts b/test/moves/flame-burst.test.ts index e340936f94c..cc0852b95d2 100644 --- a/test/moves/flame-burst.test.ts +++ b/test/moves/flame-burst.test.ts @@ -1,10 +1,11 @@ import { allAbilities } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import type { Pokemon } from "#field/pokemon"; -import { TurnEndPhase } from "#phases/turn-end-phase"; import { GameManager } from "#test/test-utils/game-manager"; +import { toDmgValue } from "#utils/common"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -20,7 +21,7 @@ describe("Moves - Flame Burst", () => { * @returns Effect damage of Flame Burst */ const getEffectDamage = (pokemon: Pokemon): number => { - return Math.max(1, Math.floor((pokemon.getMaxHp() * 1) / 16)); + return toDmgValue(pokemon.getMaxHp() / 16); }; beforeAll(() => { @@ -37,10 +38,8 @@ describe("Moves - Flame Burst", () => { game = new GameManager(phaserGame); game.override .battleStyle("double") - .moveset([MoveId.FLAME_BURST, MoveId.SPLASH]) .criticalHits(false) .ability(AbilityId.UNNERVE) - .startingWave(4) .enemySpecies(SpeciesId.SHUCKLE) .enemyAbility(AbilityId.BALL_FETCH) .enemyMoveset(MoveId.SPLASH); @@ -50,9 +49,9 @@ describe("Moves - Flame Burst", () => { await game.classicMode.startBattle([SpeciesId.PIKACHU, SpeciesId.PIKACHU]); const [leftEnemy, rightEnemy] = game.scene.getEnemyField(); - game.move.select(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); - game.move.select(MoveId.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + game.move.use(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); + game.move.use(MoveId.SPLASH, 1); + await game.toEndOfTurn(); expect(leftEnemy.hp).toBeLessThan(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp() - getEffectDamage(rightEnemy)); @@ -64,9 +63,9 @@ describe("Moves - Flame Burst", () => { await game.classicMode.startBattle([SpeciesId.PIKACHU, SpeciesId.PIKACHU]); const [leftEnemy, rightEnemy] = game.scene.getEnemyField(); - game.move.select(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); - game.move.select(MoveId.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + game.move.use(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); + game.move.use(MoveId.SPLASH, 1); + await game.toEndOfTurn(); expect(leftEnemy.hp).toBe(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp()); @@ -78,9 +77,9 @@ describe("Moves - Flame Burst", () => { vi.spyOn(rightEnemy, "getAbility").mockReturnValue(allAbilities[AbilityId.FLASH_FIRE]); - game.move.select(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); - game.move.select(MoveId.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + game.move.use(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); + game.move.use(MoveId.SPLASH, 1); + await game.toEndOfTurn(); expect(leftEnemy.hp).toBeLessThan(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp() - getEffectDamage(rightEnemy)); @@ -92,19 +91,36 @@ describe("Moves - Flame Burst", () => { vi.spyOn(rightEnemy, "getAbility").mockReturnValue(allAbilities[AbilityId.MAGIC_GUARD]); - game.move.select(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); - game.move.select(MoveId.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + game.move.use(MoveId.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); + game.move.use(MoveId.SPLASH, 1); + await game.toEndOfTurn(); expect(leftEnemy.hp).toBeLessThan(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp()); }); - it( - "is not affected by protection moves and Endure", - async () => { - // TODO: update this test when it's possible to select move for each enemy - }, - { skip: true }, - ); + it("ignores protection moves and Endure from the non-target pokemon", async () => { + await game.classicMode.startBattle([SpeciesId.MILOTIC]); + + const [enemy1, enemy2] = game.scene.getEnemyField(); + + game.move.use(MoveId.FLAME_BURST, 0, BattlerIndex.ENEMY); + await game.move.forceEnemyMove(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.PROTECT); + await game.toNextTurn(); + + expect(enemy1).not.toHaveFullHp(); + expect(enemy2).toHaveTakenDamage(getEffectDamage(enemy2)); + + enemy1.hp = enemy1.getMaxHp(); + enemy2.hp = 1; + + game.move.use(MoveId.FLAME_BURST, 0, BattlerIndex.ENEMY); + await game.move.forceEnemyMove(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.ENDURE); + await game.toEndOfTurn(); + + expect(enemy1).not.toHaveFullHp(); + expect(enemy2).toHaveFainted(); + }); }); diff --git a/test/ui/item-manage-button.test.ts b/test/ui/item-manage-button.test.ts index 30c398f21a9..3d87cf4d1d9 100644 --- a/test/ui/item-manage-button.test.ts +++ b/test/ui/item-manage-button.test.ts @@ -107,7 +107,8 @@ describe("UI - Transfer Items", () => { }); // Test that the manage button actually discards items, needs proofreading - it("should discard items when button is selected", async () => { + // TODO: test is buggy, fix later + it.todo("should discard items when button is selected", async () => { let pokemon: Pokemon | undefined; await new Promise(resolve => { From b6bd9566e24729e4677f1afb60da159425a9f60d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:39:58 -0600 Subject: [PATCH 031/101] [Dev] Update Biome from `2.3.2` to `2.3.8` (#6799) * Update Biome to `2.3.8` and run Biome over the repo * Update comment in `biome.jsonc` --- biome.jsonc | 29 +-- package.json | 2 +- pnpm-lock.yaml | 74 ++++---- test/abilities/magic-guard.test.ts | 1 + test/abilities/moxie.test.ts | 26 ++- test/abilities/protean-libero.test.ts | 2 + test/abilities/shields-down.test.ts | 40 ++--- .../status-immunity-ab-attrs.test.ts | 19 +- test/abilities/wimp-out.test.ts | 66 ++++--- test/arena/arena-tags.test.ts | 1 + test/field/pokemon.test.ts | 20 +-- test/items/multi-lens.test.ts | 1 + test/moves/magic-coat.test.ts | 37 ++-- test/moves/order-up.test.ts | 1 + test/moves/parting-shot.test.ts | 166 ++++++++---------- test/ui/battle-info.test.ts | 25 +-- test/ui/pokedex.test.ts | 153 ++++++---------- 17 files changed, 303 insertions(+), 360 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 3a2c39e1541..d2678fef289 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -5,7 +5,7 @@ */ { - "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -16,7 +16,7 @@ "enabled": true, "useEditorconfig": true, "indentStyle": "space", - "includes": ["**", "!**/src/data/balance/**"], + "includes": ["**", "!src/data/balance/**"], // TODO: enable formatting of balance folder "lineWidth": 120 }, "files": { @@ -25,18 +25,23 @@ // and having to verify whether each individual file is ignored "includes": [ "**", - "!**/dist", - "!**/coverage", - "!**/assets", - "!**/locales", - "!**/.github", - "!**/node_modules", - "!**/typedoc", + "!!.github", + "!!assets", + "!!coverage", + "!!dist", + "!!docs", + "!!locales", + "!!typedoc", // TODO: lint css and html? - "!**/*.css", - "!**/*.html", + "!!*.css", + "!!*.html", // this file is too big - "!**/src/data/balance/tm-species-map.ts" + "!src/data/balance/tm-species-map.ts", + // there's some sort of bug when Biome parses this file + // relating to recursive variable assignment + // cf https://github.com/biomejs/biome/issues/8204 + // TODO: remove this exclusion when the bug is fixed + "!!test/test-utils/setup/test-end-log.ts" ] }, "assist": { diff --git a/package.json b/package.json index 2f5348b5c5b..c53bfbe0c86 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "update-submodules:remote": "pnpm update-locales:remote && pnpm update-assets:remote" }, "devDependencies": { - "@biomejs/biome": "2.3.2", + "@biomejs/biome": "2.3.8", "@ls-lint/ls-lint": "2.3.1", "@types/crypto-js": "^4.2.2", "@types/jsdom": "^27.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09cbdd9ae23..a45d078838e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,8 +46,8 @@ importers: version: 1.80.17(graphology-types@0.24.8) devDependencies: '@biomejs/biome': - specifier: 2.3.2 - version: 2.3.2 + specifier: 2.3.8 + version: 2.3.8 '@ls-lint/ls-lint': specifier: 2.3.1 version: 2.3.1 @@ -203,59 +203,59 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.3.2': - resolution: {integrity: sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==} + '@biomejs/biome@2.3.8': + resolution: {integrity: sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.3.2': - resolution: {integrity: sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==} + '@biomejs/cli-darwin-arm64@2.3.8': + resolution: {integrity: sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.3.2': - resolution: {integrity: sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==} + '@biomejs/cli-darwin-x64@2.3.8': + resolution: {integrity: sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.3.2': - resolution: {integrity: sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==} + '@biomejs/cli-linux-arm64-musl@2.3.8': + resolution: {integrity: sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.3.2': - resolution: {integrity: sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==} + '@biomejs/cli-linux-arm64@2.3.8': + resolution: {integrity: sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.3.2': - resolution: {integrity: sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==} + '@biomejs/cli-linux-x64-musl@2.3.8': + resolution: {integrity: sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.3.2': - resolution: {integrity: sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==} + '@biomejs/cli-linux-x64@2.3.8': + resolution: {integrity: sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.3.2': - resolution: {integrity: sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==} + '@biomejs/cli-win32-arm64@2.3.8': + resolution: {integrity: sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.3.2': - resolution: {integrity: sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==} + '@biomejs/cli-win32-x64@2.3.8': + resolution: {integrity: sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -2138,39 +2138,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/biome@2.3.2': + '@biomejs/biome@2.3.8': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.2 - '@biomejs/cli-darwin-x64': 2.3.2 - '@biomejs/cli-linux-arm64': 2.3.2 - '@biomejs/cli-linux-arm64-musl': 2.3.2 - '@biomejs/cli-linux-x64': 2.3.2 - '@biomejs/cli-linux-x64-musl': 2.3.2 - '@biomejs/cli-win32-arm64': 2.3.2 - '@biomejs/cli-win32-x64': 2.3.2 + '@biomejs/cli-darwin-arm64': 2.3.8 + '@biomejs/cli-darwin-x64': 2.3.8 + '@biomejs/cli-linux-arm64': 2.3.8 + '@biomejs/cli-linux-arm64-musl': 2.3.8 + '@biomejs/cli-linux-x64': 2.3.8 + '@biomejs/cli-linux-x64-musl': 2.3.8 + '@biomejs/cli-win32-arm64': 2.3.8 + '@biomejs/cli-win32-x64': 2.3.8 - '@biomejs/cli-darwin-arm64@2.3.2': + '@biomejs/cli-darwin-arm64@2.3.8': optional: true - '@biomejs/cli-darwin-x64@2.3.2': + '@biomejs/cli-darwin-x64@2.3.8': optional: true - '@biomejs/cli-linux-arm64-musl@2.3.2': + '@biomejs/cli-linux-arm64-musl@2.3.8': optional: true - '@biomejs/cli-linux-arm64@2.3.2': + '@biomejs/cli-linux-arm64@2.3.8': optional: true - '@biomejs/cli-linux-x64-musl@2.3.2': + '@biomejs/cli-linux-x64-musl@2.3.8': optional: true - '@biomejs/cli-linux-x64@2.3.2': + '@biomejs/cli-linux-x64@2.3.8': optional: true - '@biomejs/cli-win32-arm64@2.3.2': + '@biomejs/cli-win32-arm64@2.3.8': optional: true - '@biomejs/cli-win32-x64@2.3.2': + '@biomejs/cli-win32-x64@2.3.8': optional: true '@csstools/color-helpers@5.1.0': {} diff --git a/test/abilities/magic-guard.test.ts b/test/abilities/magic-guard.test.ts index e2977420edf..cab541b6a4e 100644 --- a/test/abilities/magic-guard.test.ts +++ b/test/abilities/magic-guard.test.ts @@ -55,6 +55,7 @@ describe("AbilityId - Magic Guard", () => { expect(magikarp.hp).toBe(magikarp.getMaxHp()); }); + // biome-ignore format: prefer pre-2.3.6 formatting it.each<{ abName: string; move?: MoveId; enemyMove?: MoveId; passive?: AbilityId; enemyAbility?: AbilityId }>([ { abName: "Bad Dreams", enemyMove: MoveId.SPORE, enemyAbility: AbilityId.BAD_DREAMS }, { abName: "Aftermath", move: MoveId.PSYCHIC_FANGS, enemyAbility: AbilityId.AFTERMATH }, diff --git a/test/abilities/moxie.test.ts b/test/abilities/moxie.test.ts index d762187baba..d6b429d72e5 100644 --- a/test/abilities/moxie.test.ts +++ b/test/abilities/moxie.test.ts @@ -49,25 +49,21 @@ describe("Abilities - Moxie", () => { }); // TODO: Activate this test when MOXIE is corrected to work on faint and not on battle victory - it.todo( - "should raise ATK stat stage by 1 when defeating an ally Pokemon", - async () => { - game.override.battleStyle("double"); - const moveToUse = MoveId.AERIAL_ACE; - await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.MIGHTYENA]); + it.todo("should raise ATK stat stage by 1 when defeating an ally Pokemon", async () => { + game.override.battleStyle("double"); + const moveToUse = MoveId.AERIAL_ACE; + await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.MIGHTYENA]); - const [firstPokemon, secondPokemon] = game.scene.getPlayerField(); + const [firstPokemon, secondPokemon] = game.scene.getPlayerField(); - expect(firstPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(firstPokemon.getStatStage(Stat.ATK)).toBe(0); - secondPokemon.hp = 1; + secondPokemon.hp = 1; - game.move.select(moveToUse, BattlerIndex.PLAYER_2); + game.move.select(moveToUse, BattlerIndex.PLAYER_2); - await game.phaseInterceptor.to("TurnEndPhase"); + await game.phaseInterceptor.to("TurnEndPhase"); - expect(firstPokemon.getStatStage(Stat.ATK)).toBe(1); - }, - 20000, - ); + expect(firstPokemon.getStatStage(Stat.ATK)).toBe(1); + }, 20000); }); diff --git a/test/abilities/protean-libero.test.ts b/test/abilities/protean-libero.test.ts index 305955f5221..deb5cddb8ee 100644 --- a/test/abilities/protean-libero.test.ts +++ b/test/abilities/protean-libero.test.ts @@ -121,6 +121,7 @@ describe("Abilities - Protean/Libero", () => { expectTypeChange(bulbasaur); }); + // biome-ignore format: prefer pre-2.3.6 formatting it.each<{ category: string; move?: MoveId; passive?: AbilityId; enemyMove?: MoveId }>([ { category: "Variable type Moves'", move: MoveId.WEATHER_BALL, passive: AbilityId.DROUGHT }, { category: "Type Change Abilities'", passive: AbilityId.REFRIGERATE }, @@ -146,6 +147,7 @@ describe("Abilities - Protean/Libero", () => { }, ); + // biome-ignore format: prefer pre-2.3.6 formatting it.each<{ cause: string; move?: MoveId; passive?: AbilityId; enemyMove?: MoveId }>([ { cause: "misses", move: MoveId.FOCUS_BLAST }, { cause: "is protected against", enemyMove: MoveId.PROTECT }, diff --git a/test/abilities/shields-down.test.ts b/test/abilities/shields-down.test.ts index ff07f2c9560..71a7cca5061 100644 --- a/test/abilities/shields-down.test.ts +++ b/test/abilities/shields-down.test.ts @@ -35,33 +35,31 @@ describe("Abilities - Shields Down", () => { .enemyMoveset(MoveId.SPLASH); }); - it.each([0, 1, 2, 3, 4, 5, 6])( - "should change from Meteor Form to Core Form on entry/turn end based on HP - form index %i", - async meteorIndex => { - game.override.starterForms({ - // Start in meteor form - [SpeciesId.MINIOR]: meteorIndex, - }); + it.each([0, 1, 2, 3, 4, 5, 6])(// + "should change from Meteor Form to Core Form on entry/turn end based on HP - form index %i", async meteorIndex => { + game.override.starterForms({ + // Start in meteor form + [SpeciesId.MINIOR]: meteorIndex, + }); - await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MINIOR]); + await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MINIOR]); - const minior = game.scene.getPlayerParty()[1]; - expect(minior.formIndex).toBe(meteorIndex); - minior.hp *= 0.49; + const minior = game.scene.getPlayerParty()[1]; + expect(minior.formIndex).toBe(meteorIndex); + minior.hp *= 0.49; - // Switch to minior - should change to Core due to being <50% HP - game.doSwitchPokemon(1); - await game.toNextTurn(); + // Switch to minior - should change to Core due to being <50% HP + game.doSwitchPokemon(1); + await game.toNextTurn(); - expect(minior.formIndex).toBe(meteorIndex + 7); + expect(minior.formIndex).toBe(meteorIndex + 7); - // Use roost to regain 50% HP; should transform back into Meteor Form at turn end - game.move.use(MoveId.ROOST); - await game.toNextTurn(); + // Use roost to regain 50% HP; should transform back into Meteor Form at turn end + game.move.use(MoveId.ROOST); + await game.toNextTurn(); - expect(minior.formIndex).toBe(meteorIndex); - }, - ); + expect(minior.formIndex).toBe(meteorIndex); + }); it("should revert to base form on arena reset, even when fainted", async () => { game.override.startingWave(4).starterForms({ diff --git a/test/abilities/status-immunity-ab-attrs.test.ts b/test/abilities/status-immunity-ab-attrs.test.ts index 7df621d5577..513a3f4432d 100644 --- a/test/abilities/status-immunity-ab-attrs.test.ts +++ b/test/abilities/status-immunity-ab-attrs.test.ts @@ -79,17 +79,14 @@ describe.each<{ name: string; ability: AbilityId; status: StatusEffect }>([ }); // TODO: This does not propagate failures currently - it.todo( - `should cause status moves inflicting ${statusStr} to count as failed if no other effects can be applied`, - async () => { - await game.classicMode.startBattle([SpeciesId.FEEBAS]); + it.todo(`should cause status moves inflicting ${statusStr} to count as failed if no other effects can be applied`, async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - game.move.use(MoveId.SPORE); - await game.toEndOfTurn(); + game.move.use(MoveId.SPORE); + await game.toEndOfTurn(); - const karp = game.field.getEnemyPokemon(); - expect(karp.status?.effect).toBeUndefined(); - expect(game.field.getPlayerPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); - }, - ); + const karp = game.field.getEnemyPokemon(); + expect(karp.status?.effect).toBeUndefined(); + expect(game.field.getPlayerPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); }); diff --git a/test/abilities/wimp-out.test.ts b/test/abilities/wimp-out.test.ts index 46fd5094255..1caf9bf7701 100644 --- a/test/abilities/wimp-out.test.ts +++ b/test/abilities/wimp-out.test.ts @@ -199,29 +199,26 @@ describe("Abilities - Wimp Out", () => { // TODO: Enable when this behavior is fixed (currently Shell Bell won't activate if Wimp Out activates because // the pokemon is removed from the field before the Shell Bell modifier is applied, so it can't see the // damage dealt and doesn't heal the pokemon) - it.todo( - "If it falls below half and recovers back above half from a Shell Bell, Wimp Out will activate even after the Shell Bell recovery", - async () => { - game.override - .moveset([MoveId.DOUBLE_EDGE]) - .enemyMoveset([MoveId.SPLASH]) - .startingHeldItems([{ name: "SHELL_BELL", count: 4 }]); - await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); + it.todo("If it falls below half and recovers back above half from a Shell Bell, Wimp Out will activate even after the Shell Bell recovery", async () => { + game.override + .moveset([MoveId.DOUBLE_EDGE]) + .enemyMoveset([MoveId.SPLASH]) + .startingHeldItems([{ name: "SHELL_BELL", count: 4 }]); + await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); - const wimpod = game.field.getPlayerPokemon(); + const wimpod = game.field.getPlayerPokemon(); - wimpod.damageAndUpdate(toDmgValue(wimpod.getMaxHp() * 0.4)); + wimpod.damageAndUpdate(toDmgValue(wimpod.getMaxHp() * 0.4)); - game.move.select(MoveId.DOUBLE_EDGE); - game.doSelectPartyPokemon(1); - await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(MoveId.DOUBLE_EDGE); + game.doSelectPartyPokemon(1); + await game.phaseInterceptor.to("TurnEndPhase"); - expect(game.scene.getPlayerParty()[1]).toBe(wimpod); - expect(wimpod.hp).toBeGreaterThan(toDmgValue(wimpod.getMaxHp() / 2)); - expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); - expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.TYRUNT); - }, - ); + expect(game.scene.getPlayerParty()[1]).toBe(wimpod); + expect(wimpod.hp).toBeGreaterThan(toDmgValue(wimpod.getMaxHp() / 2)); + expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.TYRUNT); + }); it("Wimp Out will activate due to weather damage", async () => { game.override.weather(WeatherType.HAIL).enemyMoveset([MoveId.SPLASH]); @@ -460,26 +457,23 @@ describe("Abilities - Wimp Out", () => { }); // TODO: This interaction is not implemented yet - it.todo( - "Wimp Out will not activate if the Pokémon's HP falls below half due to hurting itself in confusion", - async () => { - game.override.moveset([MoveId.SWORDS_DANCE]).enemyMoveset([MoveId.SWAGGER]); - await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); - const playerPokemon = game.field.getPlayerPokemon(); - playerPokemon.hp *= 0.51; - playerPokemon.setStatStage(Stat.ATK, 6); - playerPokemon.addTag(BattlerTagType.CONFUSED); + it.todo("Wimp Out will not activate if the Pokémon's HP falls below half due to hurting itself in confusion", async () => { + game.override.moveset([MoveId.SWORDS_DANCE]).enemyMoveset([MoveId.SWAGGER]); + await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); + const playerPokemon = game.field.getPlayerPokemon(); + playerPokemon.hp *= 0.51; + playerPokemon.setStatStage(Stat.ATK, 6); + playerPokemon.addTag(BattlerTagType.CONFUSED); - // TODO: add helper function to force confusion self-hits + // TODO: add helper function to force confusion self-hits - while (playerPokemon.getHpRatio() > 0.49) { - game.move.select(MoveId.SWORDS_DANCE); - await game.phaseInterceptor.to("TurnEndPhase"); - } + while (playerPokemon.getHpRatio() > 0.49) { + game.move.select(MoveId.SWORDS_DANCE); + await game.phaseInterceptor.to("TurnEndPhase"); + } - confirmNoSwitch(); - }, - ); + confirmNoSwitch(); + }); it("should not activate on wave X0 bosses", async () => { game.override.enemyAbility(AbilityId.WIMP_OUT).startingLevel(5850).startingWave(10); diff --git a/test/arena/arena-tags.test.ts b/test/arena/arena-tags.test.ts index 1d0c0cf1fd8..4be8729ab5e 100644 --- a/test/arena/arena-tags.test.ts +++ b/test/arena/arena-tags.test.ts @@ -65,6 +65,7 @@ describe("Arena Tags", () => { })), ); + // biome-ignore format: prefer pre-2.3.6 formatting it.each(arenaTags)( "$name should display a message on addition, and a separate one on removal - $sideName", ({ tagType, side }) => { diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index 2bf2e0b23b2..94b3a2316ea 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -199,19 +199,17 @@ describe("Spec - Pokemon", () => { }); }); - it.each([5, 25, 55, 95, 145, 195])( - "should set minimum IVs for enemy trainer pokemon based on wave (%i)", - async wave => { - game.override.startingWave(wave); - await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + it.each([5, 25, 55, 95, 145, 195])(// + "should set minimum IVs for enemy trainer pokemon based on wave (%i)", async wave => { + game.override.startingWave(wave); + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); - for (const pokemon of game.field.getEnemyParty()) { - for (const iv of pokemon.ivs) { - expect(iv).toBeGreaterThanOrEqual(Math.floor(wave / 10)); - } + for (const pokemon of game.field.getEnemyParty()) { + for (const iv of pokemon.ivs) { + expect(iv).toBeGreaterThanOrEqual(Math.floor(wave / 10)); } - }, - ); + } + }); it.each([ { wave: 5, friendship: 6 }, diff --git a/test/items/multi-lens.test.ts b/test/items/multi-lens.test.ts index bdf93a4ae12..aff9cc148f6 100644 --- a/test/items/multi-lens.test.ts +++ b/test/items/multi-lens.test.ts @@ -37,6 +37,7 @@ describe("Items - Multi Lens", () => { .enemyLevel(99); }); + // biome-ignore format: prefer pre-2.3.6 formatting it.each([ { stackCount: 1, firstHitDamage: 0.75 }, { stackCount: 2, firstHitDamage: 0.5 }, diff --git a/test/moves/magic-coat.test.ts b/test/moves/magic-coat.test.ts index 7c1c703119d..73ffbf2c2fb 100644 --- a/test/moves/magic-coat.test.ts +++ b/test/moves/magic-coat.test.ts @@ -219,30 +219,27 @@ describe("Moves - Magic Coat", () => { }); // TODO: stomping tantrum should consider moves that were bounced. - it.todo( - "should properly cause the enemy's stomping tantrum to be doubled in power after bouncing and failing", - async () => { - game.override.enemyMoveset([MoveId.STOMPING_TANTRUM, MoveId.SPLASH, MoveId.CHARM]); - await game.classicMode.startBattle([SpeciesId.BULBASAUR]); + it.todo("should properly cause the enemy's stomping tantrum to be doubled in power after bouncing and failing", async () => { + game.override.enemyMoveset([MoveId.STOMPING_TANTRUM, MoveId.SPLASH, MoveId.CHARM]); + await game.classicMode.startBattle([SpeciesId.BULBASAUR]); - const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; - const enemy = game.field.getEnemyPokemon(); - vi.spyOn(stomping_tantrum, "calculateBattlePower"); + const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; + const enemy = game.field.getEnemyPokemon(); + vi.spyOn(stomping_tantrum, "calculateBattlePower"); - game.move.select(MoveId.SPORE); - await game.move.selectEnemyMove(MoveId.CHARM); - await game.phaseInterceptor.to("TurnEndPhase"); - expect(enemy.getLastXMoves(1)[0].result).toBe("success"); + game.move.select(MoveId.SPORE); + await game.move.selectEnemyMove(MoveId.CHARM); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemy.getLastXMoves(1)[0].result).toBe("success"); - await game.phaseInterceptor.to("BerryPhase"); - expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); + await game.phaseInterceptor.to("BerryPhase"); + expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); - await game.toNextTurn(); - game.move.select(MoveId.GROWL); - await game.phaseInterceptor.to("BerryPhase"); - expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); - }, - ); + await game.toNextTurn(); + game.move.select(MoveId.GROWL); + await game.phaseInterceptor.to("BerryPhase"); + expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); + }); it("should respect immunities when bouncing a move", async () => { vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockReturnValue(100); diff --git a/test/moves/order-up.test.ts b/test/moves/order-up.test.ts index 2da7cc5daf8..5f19f8f9b97 100644 --- a/test/moves/order-up.test.ts +++ b/test/moves/order-up.test.ts @@ -40,6 +40,7 @@ describe("Moves - Order Up", () => { vi.spyOn(game.scene, "triggerPokemonBattleAnim").mockReturnValue(true); }); + // biome-ignore format: prefer pre-2.3.6 formatting it.each([ { formIndex: 0, formName: "Curly", stat: Stat.ATK, statName: "Attack" }, { formIndex: 1, formName: "Droopy", stat: Stat.DEF, statName: "Defense" }, diff --git a/test/moves/parting-shot.test.ts b/test/moves/parting-shot.test.ts index e9400aef29b..2d452f3adfc 100644 --- a/test/moves/parting-shot.test.ts +++ b/test/moves/parting-shot.test.ts @@ -60,113 +60,101 @@ describe("Moves - Parting Shot", () => { expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); }); - it.todo( - // TODO: fix this bug to pass the test! - "Parting shot should fail if target is -6/-6 de-buffed", - async () => { - game.override.moveset([MoveId.PARTING_SHOT, MoveId.MEMENTO, MoveId.SPLASH]); - await game.classicMode.startBattle([ - SpeciesId.MEOWTH, - SpeciesId.MEOWTH, - SpeciesId.MEOWTH, - SpeciesId.MURKROW, - SpeciesId.ABRA, - ]); + // TODO: fix this bug to pass the test! + it.todo("Parting shot should fail if target is -6/-6 de-buffed", async () => { + game.override.moveset([MoveId.PARTING_SHOT, MoveId.MEMENTO, MoveId.SPLASH]); + await game.classicMode.startBattle([ + SpeciesId.MEOWTH, + SpeciesId.MEOWTH, + SpeciesId.MEOWTH, + SpeciesId.MURKROW, + SpeciesId.ABRA, + ]); - // use Memento 3 times to debuff enemy - game.move.select(MoveId.MEMENTO); - await game.phaseInterceptor.to("FaintPhase"); - expect(game.field.getPlayerPokemon().isFainted()).toBe(true); - game.doSelectPartyPokemon(1); + // use Memento 3 times to debuff enemy + game.move.select(MoveId.MEMENTO); + await game.phaseInterceptor.to("FaintPhase"); + expect(game.field.getPlayerPokemon().isFainted()).toBe(true); + game.doSelectPartyPokemon(1); - await game.phaseInterceptor.to("TurnInitPhase", false); - game.move.select(MoveId.MEMENTO); - await game.phaseInterceptor.to("FaintPhase"); - expect(game.field.getPlayerPokemon().isFainted()).toBe(true); - game.doSelectPartyPokemon(2); + await game.phaseInterceptor.to("TurnInitPhase", false); + game.move.select(MoveId.MEMENTO); + await game.phaseInterceptor.to("FaintPhase"); + expect(game.field.getPlayerPokemon().isFainted()).toBe(true); + game.doSelectPartyPokemon(2); - await game.phaseInterceptor.to("TurnInitPhase", false); - game.move.select(MoveId.MEMENTO); - await game.phaseInterceptor.to("FaintPhase"); - expect(game.field.getPlayerPokemon().isFainted()).toBe(true); - game.doSelectPartyPokemon(3); + await game.phaseInterceptor.to("TurnInitPhase", false); + game.move.select(MoveId.MEMENTO); + await game.phaseInterceptor.to("FaintPhase"); + expect(game.field.getPlayerPokemon().isFainted()).toBe(true); + game.doSelectPartyPokemon(3); - // set up done - await game.phaseInterceptor.to("TurnInitPhase", false); - const enemyPokemon = game.field.getEnemyPokemon(); - expect(enemyPokemon).toBeDefined(); + // set up done + await game.phaseInterceptor.to("TurnInitPhase", false); + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6); - expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6); - // now parting shot should fail - game.move.select(MoveId.PARTING_SHOT); + // now parting shot should fail + game.move.select(MoveId.PARTING_SHOT); - await game.phaseInterceptor.to("BerryPhase", false); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6); - expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); - }, - ); + await game.phaseInterceptor.to("BerryPhase", false); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6); + expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + }); - it.todo( - // TODO: fix this bug to pass the test! - "Parting shot shouldn't allow switch out when mist is active", - async () => { - game.override.enemySpecies(SpeciesId.ALTARIA).enemyAbility(AbilityId.NONE).enemyMoveset([MoveId.MIST]); - await game.classicMode.startBattle([SpeciesId.SNORLAX, SpeciesId.MEOWTH]); + // TODO: fix this bug to pass the test! + it.todo("Parting shot shouldn't allow switch out when mist is active", async () => { + game.override.enemySpecies(SpeciesId.ALTARIA).enemyAbility(AbilityId.NONE).enemyMoveset([MoveId.MIST]); + await game.classicMode.startBattle([SpeciesId.SNORLAX, SpeciesId.MEOWTH]); - const enemyPokemon = game.field.getEnemyPokemon(); - expect(enemyPokemon).toBeDefined(); + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); - game.move.select(MoveId.PARTING_SHOT); + game.move.select(MoveId.PARTING_SHOT); - await game.phaseInterceptor.to("BerryPhase", false); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); - expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); - }, - ); + await game.phaseInterceptor.to("BerryPhase", false); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); + expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + }); - it.todo( - // TODO: fix this bug to pass the test! - "Parting shot shouldn't allow switch out against clear body ability", - async () => { - game.override.enemySpecies(SpeciesId.TENTACOOL).enemyAbility(AbilityId.CLEAR_BODY); - await game.classicMode.startBattle([SpeciesId.SNORLAX, SpeciesId.MEOWTH]); + // TODO: fix this bug to pass the test! + it.todo("Parting shot shouldn't allow switch out against clear body ability", async () => { + game.override.enemySpecies(SpeciesId.TENTACOOL).enemyAbility(AbilityId.CLEAR_BODY); + await game.classicMode.startBattle([SpeciesId.SNORLAX, SpeciesId.MEOWTH]); - const enemyPokemon = game.field.getEnemyPokemon(); - expect(enemyPokemon).toBeDefined(); + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); - game.move.select(MoveId.PARTING_SHOT); + game.move.select(MoveId.PARTING_SHOT); - await game.phaseInterceptor.to("BerryPhase", false); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); - expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); - }, - ); + await game.phaseInterceptor.to("BerryPhase", false); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); + expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + }); - it.todo( - // TODO: fix this bug to pass the test! - "should lower stats without failing if no alive party members available to switch", - async () => { - await game.classicMode.startBattle([SpeciesId.MURKROW, SpeciesId.MEOWTH]); + // TODO: fix this bug to pass the test! + it.todo("should lower stats without failing if no alive party members available to switch", async () => { + await game.classicMode.startBattle([SpeciesId.MURKROW, SpeciesId.MEOWTH]); - const meowth = game.scene.getPlayerParty()[1]; - meowth.hp = 0; + const meowth = game.scene.getPlayerParty()[1]; + meowth.hp = 0; - game.move.select(MoveId.SPLASH); - await game.toNextTurn(); + game.move.select(MoveId.SPLASH); + await game.toNextTurn(); - game.move.select(MoveId.PARTING_SHOT); - game.doSelectPartyPokemon(1); - await game.toEndOfTurn(); + game.move.select(MoveId.PARTING_SHOT); + game.doSelectPartyPokemon(1); + await game.toEndOfTurn(); - const enemyPokemon = game.field.getEnemyPokemon(); - expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); - expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); - }, - ); + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); + }); }); diff --git a/test/ui/battle-info.test.ts b/test/ui/battle-info.test.ts index 8bdd61e05b0..855614b8712 100644 --- a/test/ui/battle-info.test.ts +++ b/test/ui/battle-info.test.ts @@ -38,19 +38,20 @@ describe("UI - Battle Info", () => { .enemySpecies(SpeciesId.CATERPIE); }); - it.each([ExpGainsSpeed.FAST, ExpGainsSpeed.FASTER, ExpGainsSpeed.SKIP])( - "should increase exp gains animation by 2^%i", - async expGainsSpeed => { - game.settings.expGainsSpeed(expGainsSpeed); - vi.spyOn(Math, "pow"); + it.each([ + ExpGainsSpeed.FAST, + ExpGainsSpeed.FASTER, + ExpGainsSpeed.SKIP, + ])("should increase exp gains animation by 2^%i", async expGainsSpeed => { + game.settings.expGainsSpeed(expGainsSpeed); + vi.spyOn(Math, "pow"); - await game.classicMode.startBattle([SpeciesId.CHARIZARD]); + await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - game.move.select(MoveId.SPLASH); - await game.doKillOpponents(); - await game.phaseInterceptor.to(ExpPhase, true); + game.move.select(MoveId.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to(ExpPhase, true); - expect(Math.pow).not.toHaveBeenCalledWith(2, expGainsSpeed); - }, - ); + expect(Math.pow).not.toHaveBeenCalledWith(2, expGainsSpeed); + }); }); diff --git a/test/ui/pokedex.test.ts b/test/ui/pokedex.test.ts index 6b84b253260..11dceedff12 100644 --- a/test/ui/pokedex.test.ts +++ b/test/ui/pokedex.test.ts @@ -61,6 +61,8 @@ describe("UI - Pokedex", () => { game = new GameManager(phaserGame); }); + // #region Helper Functions + /** * Run the game to open the pokedex UI. * @returns The handler for the pokedex UI. @@ -188,9 +190,8 @@ describe("UI - Pokedex", () => { } } - /*************************** - * Tests for Filters * - ***************************/ + // #endregion + // #region Filter Tests it("should filter to show only the pokemon with an ability when filtering by ability", async () => { // await game.importData("test/test-utils/saves/everything.prsv"); @@ -199,13 +200,11 @@ describe("UI - Pokedex", () => { // Get name of overgrow const overgrow = allAbilities[AbilityId.OVERGROW].name; - // @ts-expect-error `filterText` is private - pokedexHandler.filterText.setValue(FilterTextRow.ABILITY_1, overgrow); + pokedexHandler["filterText"].setValue(FilterTextRow.ABILITY_1, overgrow); // filter all species to be the pokemon that have overgrow const overgrowSpecies = getSpeciesWithAbility(AbilityId.OVERGROW); - // @ts-expect-error - `filteredPokemonData` is private - const filteredSpecies = new Set(pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId)); + const filteredSpecies = new Set(pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId)); expect(filteredSpecies).toEqual(overgrowSpecies); }); @@ -245,14 +244,11 @@ describe("UI - Pokedex", () => { const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error `filterText` is private - pokedexHandler.filterText.setValue(FilterTextRow.ABILITY_1, ab1_instance.name); - // @ts-expect-error `filterText` is private - pokedexHandler.filterText.setValue(FilterTextRow.ABILITY_2, ab2_instance.name); + pokedexHandler["filterText"].setValue(FilterTextRow.ABILITY_1, ab1_instance.name); + pokedexHandler["filterText"].setValue(FilterTextRow.ABILITY_2, ab2_instance.name); let whiteListCount = 0; - // @ts-expect-error `filteredPokemonData` is private - for (const species of pokedexHandler.filteredPokemonData) { + for (const species of pokedexHandler["filteredPokemonData"]) { expect(blacklist, "entry must have one of the abilities as a passive").not.toContain(species.species.speciesId); const rawAbility = [species.species.ability1, species.species.ability2, species.species.abilityHidden]; @@ -273,12 +269,10 @@ describe("UI - Pokedex", () => { it("should filter to show only the pokemon with a type when filtering by a single type", async () => { const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - pokedexHandler.filterBar.getFilter(DropDownColumn.TYPES).toggleOptionState(PokemonType.NORMAL + 1); + pokedexHandler["filterBar"].getFilter(DropDownColumn.TYPES).toggleOptionState(PokemonType.NORMAL + 1); const expectedPokemon = getSpeciesWithType(PokemonType.NORMAL); - // @ts-expect-error - `filteredPokemonData` is private - const filteredPokemon = new Set(pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId)); + const filteredPokemon = new Set(pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId)); expect(filteredPokemon).toEqual(expectedPokemon); }); @@ -287,14 +281,11 @@ describe("UI - Pokedex", () => { it.todo("should show only the pokemon with one of the types when filtering by multiple types", async () => { const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - pokedexHandler.filterBar.getFilter(DropDownColumn.TYPES).toggleOptionState(PokemonType.NORMAL + 1); - // @ts-expect-error - `filterBar` is private - pokedexHandler.filterBar.getFilter(DropDownColumn.TYPES).toggleOptionState(PokemonType.FLYING + 1); + pokedexHandler["filterBar"].getFilter(DropDownColumn.TYPES).toggleOptionState(PokemonType.NORMAL + 1); + pokedexHandler["filterBar"].getFilter(DropDownColumn.TYPES).toggleOptionState(PokemonType.FLYING + 1); const expectedPokemon = getSpeciesWithType(PokemonType.NORMAL, PokemonType.FLYING); - // @ts-expect-error - `filteredPokemonData` is private - const filteredPokemon = new Set(pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId)); + const filteredPokemon = new Set(pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId)); expect(filteredPokemon).toEqual(expectedPokemon); }); @@ -304,8 +295,7 @@ describe("UI - Pokedex", () => { await game.importData("./test/test-utils/saves/data_pokedex_tests.prsv"); const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.UNLOCKS); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.UNLOCKS); // Cycling 4 times to get to the "can unlock" for cost reduction for (let i = 0; i < 4; i++) { @@ -322,8 +312,7 @@ describe("UI - Pokedex", () => { SpeciesId.MUDKIP, ]); expect( - // @ts-expect-error - `filteredPokemonData` is private - pokedexHandler.filteredPokemonData.every(pokemon => + pokedexHandler["filteredPokemonData"].every(pokemon => expectedPokemon.has(pokedexHandler.getStarterSpeciesId(pokemon.species.speciesId)), ), ).toBe(true); @@ -333,14 +322,12 @@ describe("UI - Pokedex", () => { await game.importData("./test/test-utils/saves/data_pokedex_tests.prsv"); const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.UNLOCKS); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.UNLOCKS); filter.toggleOptionState(0); // cycle to Passive: Yes expect( - // @ts-expect-error - `filteredPokemonData` is private - pokedexHandler.filteredPokemonData.every( + pokedexHandler["filteredPokemonData"].every( pokemon => pokedexHandler.getStarterSpeciesId(pokemon.species.speciesId) === SpeciesId.MUDKIP, ), ).toBe(true); @@ -350,8 +337,7 @@ describe("UI - Pokedex", () => { await game.importData("./test/test-utils/saves/data_pokedex_tests.prsv"); const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.UNLOCKS); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.UNLOCKS); // Cycling 4 times to get to the "can unlock" for passive const expectedPokemon = new Set([ @@ -367,8 +353,7 @@ describe("UI - Pokedex", () => { filter.toggleOptionState(0); expect( - // @ts-expect-error - `filteredPokemonData` is private - pokedexHandler.filteredPokemonData.every(pokemon => + pokedexHandler["filteredPokemonData"].every(pokemon => expectedPokemon.has(pokedexHandler.getStarterSpeciesId(pokemon.species.speciesId)), ), ).toBe(true); @@ -380,14 +365,12 @@ describe("UI - Pokedex", () => { const expectedPokemon = new Set([SpeciesId.TREECKO, SpeciesId.CYNDAQUIL, SpeciesId.TOTODILE]); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.UNLOCKS); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.UNLOCKS); // Cycle 1 time for cost reduction filter.toggleOptionState(1); expect( - // @ts-expect-error - `filteredPokemonData` is private - pokedexHandler.filteredPokemonData.every(pokemon => + pokedexHandler["filteredPokemonData"].every(pokemon => expectedPokemon.has(pokedexHandler.getStarterSpeciesId(pokemon.species.speciesId)), ), ).toBe(true); @@ -399,15 +382,13 @@ describe("UI - Pokedex", () => { const expectedPokemon = new Set([SpeciesId.CYNDAQUIL, SpeciesId.TOTODILE]); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.UNLOCKS); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.UNLOCKS); // Cycle 2 times for one cost reduction filter.toggleOptionState(1); filter.toggleOptionState(1); expect( - // @ts-expect-error - `filteredPokemonData` is private - pokedexHandler.filteredPokemonData.every(pokemon => + pokedexHandler["filteredPokemonData"].every(pokemon => expectedPokemon.has(pokedexHandler.getStarterSpeciesId(pokemon.species.speciesId)), ), ).toBe(true); @@ -417,16 +398,14 @@ describe("UI - Pokedex", () => { await game.importData("./test/test-utils/saves/data_pokedex_tests.prsv"); const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.UNLOCKS); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.UNLOCKS); // Cycle 3 time for two cost reductions filter.toggleOptionState(1); filter.toggleOptionState(1); filter.toggleOptionState(1); expect( - // @ts-expect-error - `filteredPokemonData` is private - pokedexHandler.filteredPokemonData.every( + pokedexHandler["filteredPokemonData"].every( pokemon => pokedexHandler.getStarterSpeciesId(pokemon.species.speciesId) === SpeciesId.TREECKO, ), ).toBe(true); @@ -435,12 +414,10 @@ describe("UI - Pokedex", () => { it("filtering by shiny status shows the caught pokemon with the selected shiny tier", async () => { await game.importData("./test/test-utils/saves/data_pokedex_tests.prsv"); const pokedexHandler = await runToOpenPokedex(); - // @ts-expect-error - `filterBar` is private - const filter = pokedexHandler.filterBar.getFilter(DropDownColumn.CAUGHT); + const filter = pokedexHandler["filterBar"].getFilter(DropDownColumn.CAUGHT); filter.toggleOptionState(3); - // @ts-expect-error - `filteredPokemonData` is private - let filteredPokemon = pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId); + let filteredPokemon = pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId); // Red shiny expect(filteredPokemon.length).toBe(1); @@ -450,15 +427,13 @@ describe("UI - Pokedex", () => { filter.toggleOptionState(3); filter.toggleOptionState(2); - // @ts-expect-error - `filteredPokemonData` is private - filteredPokemon = pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId); + filteredPokemon = pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId); expect(filteredPokemon.length).toBe(1); expect(filteredPokemon[0], "tier 2 shiny").toBe(SpeciesId.RATTATA); filter.toggleOptionState(2); filter.toggleOptionState(1); - // @ts-expect-error - `filteredPokemonData` is private - filteredPokemon = pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId); + filteredPokemon = pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId); expect(filteredPokemon.length).toBe(1); expect(filteredPokemon[0], "tier 3 shiny").toBe(SpeciesId.EKANS); @@ -466,78 +441,66 @@ describe("UI - Pokedex", () => { filter.toggleOptionState(1); filter.toggleOptionState(4); - // @ts-expect-error - `filteredPokemonData` is private - filteredPokemon = pokedexHandler.filteredPokemonData.map(pokemon => pokemon.species.speciesId); + filteredPokemon = pokedexHandler["filteredPokemonData"].map(pokemon => pokemon.species.speciesId); expect(filteredPokemon.length).toBe(27); expect(filteredPokemon, "not shiny").not.toContain(SpeciesId.CATERPIE); expect(filteredPokemon, "not shiny").not.toContain(SpeciesId.RATTATA); expect(filteredPokemon, "not shiny").not.toContain(SpeciesId.EKANS); }); - /**************************** - * Tests for UI Input * - ****************************/ + // #endregion + // #region UI Input Tests // TODO: fix cursor wrapping - it.todo( - "should wrap the cursor to the top when moving to an empty entry when there are more than 81 pokemon", - async () => { - const pokedexHandler = await runToOpenPokedex(); + it.todo("should wrap the cursor to the top when moving to an empty entry when there are more than 81 pokemon", async () => { + const pokedexHandler = await runToOpenPokedex(); - // Filter by gen 2 so we can pan a specific amount. - // @ts-expect-error `filterBar` is private - pokedexHandler.filterBar.getFilter(DropDownColumn.GEN).options[2].toggleOptionState(); - pokedexHandler.updateStarters(); - // @ts-expect-error - `filteredPokemonData` is private - expect(pokedexHandler.filteredPokemonData.length, "pokemon in gen2").toBe(100); + // Filter by gen 2 so we can pan a specific amount. + pokedexHandler["filterBar"].getFilter(DropDownColumn.GEN).options[2].toggleOptionState(); + pokedexHandler.updateStarters(); + expect(pokedexHandler["filteredPokemonData"].length, "pokemon in gen2").toBe(100); - // Let's try to pan to the right to see what the pokemon it points to is. + // Let's try to pan to the right to see what the pokemon it points to is. - // pan to the right once and down 11 times - pokedexHandler.processInput(Button.RIGHT); - // Nab the pokemon that is selected for comparison later. + // pan to the right once and down 11 times + pokedexHandler.processInput(Button.RIGHT); + // Nab the pokemon that is selected for comparison later. - // @ts-expect-error - `lastSpecies` is private - const selectedPokemon = pokedexHandler.lastSpeciesId.speciesId; - for (let i = 0; i < 11; i++) { - pokedexHandler.processInput(Button.DOWN); - } + const selectedPokemon = pokedexHandler["lastSpeciesId"].speciesId; + for (let i = 0; i < 11; i++) { + pokedexHandler.processInput(Button.DOWN); + } - // @ts-expect-error `lastSpecies` is private - expect(selectedPokemon).toEqual(pokedexHandler.lastSpeciesId.speciesId); - }, - ); + expect(selectedPokemon).toEqual(pokedexHandler["lastSpeciesId"].speciesId); + }); - /**************************** - * Tests for Pokédex Pages * - ****************************/ + // #endregion + // #region Pokedex Pages Tests it("should show caught battle form as caught", async () => { await game.importData("./test/test-utils/saves/data_pokedex_tests_v2.prsv"); const pageHandler = await runToPokedexPage(getPokemonSpecies(SpeciesId.VENUSAUR), { form: 1 }); - // @ts-expect-error - `species` is private - expect(pageHandler.species.speciesId).toEqual(SpeciesId.VENUSAUR); + expect(pageHandler["species"].speciesId).toEqual(SpeciesId.VENUSAUR); - // @ts-expect-error - `formIndex` is private - expect(pageHandler.formIndex).toEqual(1); + expect(pageHandler["formIndex"]).toEqual(1); expect(pageHandler.isFormCaught()).toEqual(true); expect(pageHandler.isSeen()).toEqual(true); }); - //TODO: check tint of the sprite + // TODO: check tint of the sprite it("should show uncaught battle form as seen", async () => { await game.importData("./test/test-utils/saves/data_pokedex_tests_v2.prsv"); const pageHandler = await runToPokedexPage(getPokemonSpecies(SpeciesId.VENUSAUR), { form: 2 }); - // @ts-expect-error - `species` is private - expect(pageHandler.species.speciesId).toEqual(SpeciesId.VENUSAUR); + expect(pageHandler["species"].speciesId).toEqual(SpeciesId.VENUSAUR); - // @ts-expect-error - `formIndex` is private - expect(pageHandler.formIndex).toEqual(2); + expect(pageHandler["formIndex"]).toEqual(2); expect(pageHandler.isFormCaught()).toEqual(false); expect(pageHandler.isSeen()).toEqual(true); }); + + // #endregion }); From a2b4727d630700698b99d0b45547ce6e3c9f53bd Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 29 Nov 2025 17:42:23 -0500 Subject: [PATCH 032/101] [Refactor] Remove `main.ts` default export, clean up manifest code https://github.com/pagefaultgames/pokerogue/pull/6768 --- src/battle-scene.ts | 10 ++-- src/loading-scene.ts | 1 - src/main.ts | 58 ++++++++++------------- src/plugins/cache-busted-loader-plugin.ts | 37 +++++++-------- src/scene-base.ts | 26 +++++----- src/typings/phaser/index.d.ts | 5 ++ src/utils/cookies.ts | 5 +- 7 files changed, 68 insertions(+), 74 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index b0199980981..c537728442c 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -720,12 +720,10 @@ export class BattleScene extends SceneBase { } cachedFetch(url: string, init?: RequestInit): Promise { - const manifest = this.game["manifest"]; - if (manifest) { - const timestamp = manifest[`/${url.replace("./", "")}`]; - if (timestamp) { - url += `?t=${timestamp}`; - } + const { manifest } = this.game; + const timestamp = manifest?.[`/${url.replace("./", "")}`]; + if (timestamp) { + url += `?t=${timestamp}`; } return fetch(url, init); } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index a4cbaf9ae64..d87faa322a4 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -25,7 +25,6 @@ export class LoadingScene extends SceneBase { preload() { localPing(); - this.load["manifest"] = this.game["manifest"]; this.loadImage("loading_bg", "arenas"); this.loadImage("logo", ""); diff --git a/src/main.ts b/src/main.ts index 9903a257af6..7fdb2a7a024 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ -import "#app/polyfills"; -// All polyfills MUST be loaded first for side effects +import "#app/polyfills"; // All polyfills MUST be loaded first for side effects +import { BattleScene } from "#app/battle-scene"; +import { LoadingScene } from "#app/loading-scene"; import { InvertPostFX } from "#app/pipelines/invert"; import { initI18n } from "#app/plugins/i18n"; import { isBeta, isDev } from "#constants/app-constants"; @@ -31,6 +32,11 @@ window.addEventListener("unhandledrejection", event => { //alert(errorString); }); +interface GuideObject + extends Pick, + Pick, + Pick {} + /** * Set this object's position relative to another object with a given offset. * @param guideObject - The object to base this object's position off of; must have defined @@ -41,9 +47,7 @@ window.addEventListener("unhandledrejection", event => { */ function setPositionRelative( this: T, - guideObject: Pick & - Pick & - Pick, + guideObject: GuideObject, x: number, y: number, ): T { @@ -59,17 +63,9 @@ Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative; Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative; -document.fonts.load("16px emerald").then(() => document.fonts.load("10px pkmnems")); -// biome-ignore lint: TODO -let game; -// biome-ignore lint: TODO -let manifest; - -const startGame = async () => { +async function startGame(gameManifest?: Record): Promise { await initI18n(); - const LoadingScene = (await import("./loading-scene")).LoadingScene; - const BattleScene = (await import("./battle-scene")).BattleScene; - game = new Phaser.Game({ + const game = new Phaser.Game({ type: Phaser.WEBGL, parent: "app", scale: { @@ -121,22 +117,18 @@ const startGame = async () => { version, }); game.sound.pauseOnBlur = false; - if (manifest) { - game["manifest"] = manifest; - } -}; + game.manifest = gameManifest; +} -fetch("/manifest.json") - .then(res => res.json()) - .then(jsonResponse => { - manifest = jsonResponse.manifest; - }) - .catch(err => { - // Manifest not found (likely local build or path error on live) - console.log(`Manifest not found. ${err}`); - }) - .finally(() => { - startGame(); - }); - -export default game; +let manifest: Record | undefined; +try { + const loadFonts = Promise.all([document.fonts.load("16px emerald"), document.fonts.load("10px pkmnems")]); + const [jsonResponse] = await Promise.all([fetch("/manifest.json").then(r => r.json()), loadFonts]); + manifest = jsonResponse.manifest; +} catch (err) { + // Manifest not found (likely local build or path error on live) + // TODO: Do we want actual error handling here? + console.log("Manifest not found:", err); +} finally { + await startGame(manifest); +} diff --git a/src/plugins/cache-busted-loader-plugin.ts b/src/plugins/cache-busted-loader-plugin.ts index da0850c613c..3509f4fb55c 100644 --- a/src/plugins/cache-busted-loader-plugin.ts +++ b/src/plugins/cache-busted-loader-plugin.ts @@ -1,28 +1,27 @@ +import { globalScene } from "#app/global-scene"; import { coerceArray } from "#utils/array"; -let manifest: object; - export class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin { - get manifest() { - return manifest; - } + addFile(files: Phaser.Loader.File | Phaser.Loader.File[]): void { + files = coerceArray(files); + const { manifest } = globalScene.game; - set manifest(manifestObj: object) { - manifest = manifestObj; - } + if (!manifest) { + super.addFile(files); + return; + } - addFile(file): void { - file = coerceArray(file); - - file.forEach(item => { - if (manifest) { - const timestamp = manifest[`/${item.url.replace(/\/\//g, "/")}`]; - if (timestamp) { - item.url += `?t=${timestamp}`; - } + for (const item of files) { + if (typeof item.url !== "string") { + continue; } - }); - super.addFile(file); + const timestamp = manifest[`/${item.url.replace(/\/\//g, "/")}`]; + if (timestamp) { + item.url += `?t=${timestamp}`; + } + } + + super.addFile(files); } } diff --git a/src/scene-base.ts b/src/scene-base.ts index 26a680f2162..72acca868ad 100644 --- a/src/scene-base.ts +++ b/src/scene-base.ts @@ -18,12 +18,16 @@ export class SceneBase extends Phaser.Scene { }; getCachedUrl(url: string): string { - const manifest = this.game["manifest"]; - if (manifest) { - const timestamp = manifest[`/${url}`]; - if (timestamp) { - url += `?t=${timestamp}`; - } + const manifest = this.game.manifest; + if (!manifest) { + return url; + } + + // TODO: This is inconsistent with how the battle scene cached fetch + // uses the manifest + const timestamp = manifest[`/${url}`]; + if (timestamp) { + url += `?t=${timestamp}`; } return url; } @@ -40,10 +44,7 @@ export class SceneBase extends Phaser.Scene { } } - loadSpritesheet(key: string, folder: string, size: number, filename?: string) { - if (!filename) { - filename = `${key}.png`; - } + loadSpritesheet(key: string, folder: string, size: number, filename = `${key}.png`) { this.load.spritesheet(key, this.getCachedUrl(`images/${folder}/${filename}`), { frameWidth: size, frameHeight: size, @@ -58,10 +59,7 @@ export class SceneBase extends Phaser.Scene { } } - loadAtlas(key: string, folder: string, filenameRoot?: string) { - if (!filenameRoot) { - filenameRoot = key; - } + loadAtlas(key: string, folder: string, filenameRoot = key) { if (folder) { folder += "/"; } diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index 275aafba353..aa4d257f9e0 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -51,4 +51,9 @@ declare module "phaser" { } } } + + interface Game { + /** A manifest used to cache various files requested from the server. */ + manifest?: Record; + } } diff --git a/src/utils/cookies.ts b/src/utils/cookies.ts index c95c777243a..b3ab554e6c7 100644 --- a/src/utils/cookies.ts +++ b/src/utils/cookies.ts @@ -1,8 +1,11 @@ import { isBeta } from "#constants/app-constants"; +// 90 days +const COOKIE_EXPIRATION_BUFFER = 3600000 * 24 * 30 * 3; + export function setCookie(cName: string, cValue: string): void { const expiration = new Date(); - expiration.setTime(Date.now() + 3600000 * 24 * 30 * 3 /*7*/); + expiration.setTime(Date.now() + COOKIE_EXPIRATION_BUFFER); document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Expires=${expiration.toUTCString()}`; } From 477b8de7d18a9e36fcc436e8fea83a98945ef11c Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:58:34 -0600 Subject: [PATCH 033/101] [Dev] Improve logging in `EnemyPokemon#getNextMove` (#6803) --- src/field/pokemon.ts | 55 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 1750dcae56f..5c59706b517 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6585,11 +6585,10 @@ export class EnemyPokemon extends Pokemon { */ const moveScores = movePool.map(() => 0); const moveTargets = Object.fromEntries(movePool.map(m => [m.moveId, this.getNextTargets(m.moveId)])); - for (const m in movePool) { - const pokemonMove = movePool[m]; + movePool.forEach((pokemonMove, moveIndex) => { const move = pokemonMove.getMove(); - let moveScore = moveScores[m]; + let moveScore = moveScores[moveIndex]; const targetScores: number[] = []; for (const mt of moveTargets[move.id]) { @@ -6611,10 +6610,7 @@ export class EnemyPokemon extends Pokemon { console.error(`Move ${move.name} returned score of NaN`); targetScore = 0; } - /** - * If this move is unimplemented, or the move is known to fail when used, set its - * target score to -20 - */ + // If this move is unimplemented, or the move is known to fail when used, set its target score to -20 if ( (move.name.endsWith(" (N)") || !move.applyConditions(this, target, -1)) && ![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id) @@ -6645,7 +6641,7 @@ export class EnemyPokemon extends Pokemon { targetScore /= 1.5; } } - /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ + // If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point if (!targetScore) { targetScore = -20; } @@ -6656,10 +6652,8 @@ export class EnemyPokemon extends Pokemon { moveScore += Math.max(...targetScores); // could make smarter by checking opponent def/spdef - moveScores[m] = moveScore; - } - - console.log(moveScores); + moveScores[moveIndex] = moveScore; + }); // Sort the move pool in decreasing order of move score const sortedMovePool = movePool.slice(0); @@ -6668,37 +6662,42 @@ export class EnemyPokemon extends Pokemon { const scoreB = moveScores[movePool.indexOf(b)]; return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; }); - let r = 0; + let chosenMoveIndex = 0; if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while (r < sortedMovePool.length - 1 && globalScene.randBattleSeedInt(8) >= 5) { - r++; + while (chosenMoveIndex < sortedMovePool.length - 1 && globalScene.randBattleSeedInt(8) >= 5) { + chosenMoveIndex++; } } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. while ( - r < sortedMovePool.length - 1 - && moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])] + chosenMoveIndex < sortedMovePool.length - 1 + && moveScores[movePool.indexOf(sortedMovePool[chosenMoveIndex + 1])] + / moveScores[movePool.indexOf(sortedMovePool[chosenMoveIndex])] >= 0 && globalScene.randBattleSeedInt(100) < Math.round( - (moveScores[movePool.indexOf(sortedMovePool[r + 1])] - / moveScores[movePool.indexOf(sortedMovePool[r])]) + (moveScores[movePool.indexOf(sortedMovePool[chosenMoveIndex + 1])] + / moveScores[movePool.indexOf(sortedMovePool[chosenMoveIndex])]) * 50, ) ) { - r++; + chosenMoveIndex++; } } - console.log( - movePool.map(m => m.getName()), - moveScores, - r, - sortedMovePool.map(m => m.getName()), - ); + + const chosenMove = sortedMovePool[chosenMoveIndex]; + + // biome-ignore format: For some reason this gets broken into multiple lines + console.log("Move Pool:", movePool.map((m) => m.getName())); + console.log("Move Scores:", moveScores); + // biome-ignore format: For some reason this gets broken into multiple lines + console.log("Sorted Move Pool:", sortedMovePool.map((m) => m.getName())); + console.log("Chosen Move:", chosenMove.getName()); + return { - move: sortedMovePool[r]!.moveId, - targets: moveTargets[sortedMovePool[r]!.moveId], + move: chosenMove.moveId, + targets: moveTargets[chosenMove.moveId], useMode: MoveUseMode.NORMAL, }; } From e2be51f3f7773b4b2230f984871a7f684eea1051 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:42:51 -0800 Subject: [PATCH 034/101] [Bug] Revert change to imports in `main.ts` --- src/main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index 7fdb2a7a024..9516fe9e6ab 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,5 @@ import "#app/polyfills"; // All polyfills MUST be loaded first for side effects -import { BattleScene } from "#app/battle-scene"; -import { LoadingScene } from "#app/loading-scene"; import { InvertPostFX } from "#app/pipelines/invert"; import { initI18n } from "#app/plugins/i18n"; import { isBeta, isDev } from "#constants/app-constants"; @@ -65,6 +63,8 @@ Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative async function startGame(gameManifest?: Record): Promise { await initI18n(); + const LoadingScene = (await import("./loading-scene")).LoadingScene; + const BattleScene = (await import("./battle-scene")).BattleScene; const game = new Phaser.Game({ type: Phaser.WEBGL, parent: "app", From c12d9140fc16007134735b0b28d2697f7fddaa17 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 29 Nov 2025 20:52:45 -0800 Subject: [PATCH 035/101] [Misc] Remove now-obsolete `noUnusedImports` suppression comments --- src/@types/ability-types.ts | 4 +--- src/@types/arena-tags.ts | 1 - src/@types/battler-tags.ts | 5 +---- src/@types/helpers/type-helpers.ts | 2 -- src/@types/species-gen-types.ts | 1 - src/data/abilities/ability.ts | 7 ++----- src/data/arena-tag.ts | 5 +---- src/data/balance/pokemon-evolutions.ts | 1 - src/data/moves/move-condition.ts | 1 - src/data/positional-tags/positional-tag.ts | 2 -- src/field/arena.ts | 5 +---- src/modifier/init-modifier-pools.ts | 5 +---- src/phase-tree.ts | 8 ++------ src/phases/move-phase.ts | 5 +---- src/phases/positional-tag-phase.ts | 7 ++----- src/system/version-migration/version-converter.ts | 2 +- test/test-utils/helpers/field-helper.ts | 5 +---- test/test-utils/helpers/overrides-helper.ts | 6 +----- test/test-utils/matchers/to-be-at-phase.ts | 3 --- test/test-utils/matchers/to-have-ability-applied.ts | 5 +---- test/test-utils/matchers/to-have-arena-tag.ts | 1 - test/test-utils/matchers/to-have-battler-tag.ts | 5 +---- test/test-utils/matchers/to-have-fainted.ts | 1 - test/test-utils/matchers/to-have-full-hp.ts | 1 - test/test-utils/matchers/to-have-hp.ts | 1 - test/test-utils/matchers/to-have-positional-tag.ts | 5 +---- test/test-utils/matchers/to-have-shown-message.ts | 1 - test/test-utils/matchers/to-have-stat-stage.ts | 5 +---- test/test-utils/matchers/to-have-status-effect.ts | 7 ++----- test/test-utils/matchers/to-have-taken-damage.ts | 5 +---- test/test-utils/matchers/to-have-terrain.ts | 5 +---- test/test-utils/matchers/to-have-used-move.ts | 5 +---- test/test-utils/matchers/to-have-used-pp.ts | 5 +---- test/test-utils/matchers/to-have-weather.ts | 5 +---- test/test-utils/setup/test-end-log.ts | 1 - 35 files changed, 26 insertions(+), 107 deletions(-) diff --git a/src/@types/ability-types.ts b/src/@types/ability-types.ts index 36c636664d1..dc3b348c507 100644 --- a/src/@types/ability-types.ts +++ b/src/@types/ability-types.ts @@ -1,4 +1,5 @@ import type { AbAttrConstructorMap } from "#abilities/ability"; +import type { applyAbAttrs } from "#abilities/apply-ab-attrs"; import type { BattleStat } from "#enums/stat"; import type { Pokemon } from "#field/pokemon"; import type { Move } from "#moves/move"; @@ -6,9 +7,6 @@ import type { Move } from "#moves/move"; // intentionally re-export all types from abilities to have this be the centralized place to import ability types export type * from "#abilities/ability"; -// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment -import type { applyAbAttrs } from "#abilities/apply-ab-attrs"; - export type AbAttrCondition = (pokemon: Pokemon) => boolean; export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; diff --git a/src/@types/arena-tags.ts b/src/@types/arena-tags.ts index 73f26af052a..cd4796f397c 100644 --- a/src/@types/arena-tags.ts +++ b/src/@types/arena-tags.ts @@ -1,6 +1,5 @@ import type { ArenaTagTypeMap } from "#data/arena-tag"; import type { ArenaTagType } from "#enums/arena-tag-type"; -// biome-ignore lint/correctness/noUnusedImports: TSDocs import type { SessionSaveData } from "#types/save-data"; import type { ObjectValues } from "#types/type-helpers"; diff --git a/src/@types/battler-tags.ts b/src/@types/battler-tags.ts index ec72c811447..9c3add9fb23 100644 --- a/src/@types/battler-tags.ts +++ b/src/@types/battler-tags.ts @@ -1,10 +1,7 @@ -// biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment import type { AbilityBattlerTag, BattlerTagTypeMap, SerializableBattlerTag, TypeBoostTag } from "#data/battler-tags"; import type { AbilityId } from "#enums/ability-id"; -import type { SessionSaveData } from "#types/save-data"; -// biome-ignore-end lint/correctness/noUnusedImports: Used in a TSDoc comment - import type { BattlerTagType } from "#enums/battler-tag-type"; +import type { SessionSaveData } from "#types/save-data"; import type { InferKeys, ObjectValues } from "#types/type-helpers"; /** diff --git a/src/@types/helpers/type-helpers.ts b/src/@types/helpers/type-helpers.ts index 6c4110e00bc..7f2affe4119 100644 --- a/src/@types/helpers/type-helpers.ts +++ b/src/@types/helpers/type-helpers.ts @@ -2,9 +2,7 @@ * A collection of custom utility types that aid in type checking and ensuring strict type conformity */ -// biome-ignore-start lint/correctness/noUnusedImports: Used in a tsdoc comment import type { AbAttr } from "#abilities/ability"; -// biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment /** * Exactly matches the type of the argument, preventing adding additional properties. diff --git a/src/@types/species-gen-types.ts b/src/@types/species-gen-types.ts index 1561a427d3f..f46a7cfeda9 100644 --- a/src/@types/species-gen-types.ts +++ b/src/@types/species-gen-types.ts @@ -1,4 +1,3 @@ -// biome-ignore lint/correctness/noUnusedImports: Used in TSDoc comment import type { EvoLevelThresholdKind } from "#enums/evo-level-threshold-kind"; import type { SpeciesId } from "#enums/species-id"; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index f29e5eada8c..f703c4aa75f 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1,9 +1,5 @@ -/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { BattleScene } from "#app/battle-scene"; -import type { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers"; -/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import { applyAbAttrs } from "#abilities/apply-ab-attrs"; +import type { BattleScene } from "#app/battle-scene"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import type { EntryHazardTag, SuppressAbilitiesTag } from "#data/arena-tag"; @@ -11,6 +7,7 @@ import type { BattlerTag } from "#data/battler-tags"; import { GroundedTag } from "#data/battler-tags"; import { getBerryEffectFunc } from "#data/berry"; import { allAbilities, allMoves } from "#data/data-lists"; +import type { SpeciesFormChangeRevertWeatherFormTrigger } from "#data/form-change-triggers"; import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers"; import { Gender } from "#data/gender"; import { getPokeballName } from "#data/pokeball"; diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 763ac3621b9..67e07ac47f6 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -44,11 +44,8 @@ * @module */ -// biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports -import type { BattlerTag } from "#app/data/battler-tags"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports - import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#abilities/apply-ab-attrs"; +import type { BattlerTag } from "#app/data/battler-tags"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonBattleAnim } from "#data/battle-anims"; diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 04c3660d2eb..592f5837cff 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,4 +1,3 @@ -// biome-ignore lint/correctness/noUnusedImports: Used in TSDoc comments import type { determineEnemySpecies } from "#app/ai/ai-species-gen"; import { defaultStarterSpecies } from "#app/constants"; import { globalScene } from "#app/global-scene"; diff --git a/src/data/moves/move-condition.ts b/src/data/moves/move-condition.ts index fd923d3ca93..f637b4d43a1 100644 --- a/src/data/moves/move-condition.ts +++ b/src/data/moves/move-condition.ts @@ -1,4 +1,3 @@ -// biome-ignore lint/correctness/noUnusedImports: Used in a TSDoc comment import type { GameMode } from "#app/game-mode"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/data/positional-tags/positional-tag.ts b/src/data/positional-tags/positional-tag.ts index 44675b528bd..e6d5cb245c5 100644 --- a/src/data/positional-tags/positional-tag.ts +++ b/src/data/positional-tags/positional-tag.ts @@ -1,8 +1,6 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -// biome-ignore-start lint/correctness/noUnusedImports: TSDoc import type { ArenaTag } from "#data/arena-tag"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc import { allMoves } from "#data/data-lists"; import type { BattlerIndex } from "#enums/battler-index"; import type { MoveId } from "#enums/move-id"; diff --git a/src/field/arena.ts b/src/field/arena.ts index dc209226211..35ffce992e5 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,7 +1,3 @@ -// biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports -import type { PositionalTag } from "#data/positional-tags/positional-tag"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports - import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import Overrides from "#app/overrides"; @@ -10,6 +6,7 @@ import type { ArenaTag, ArenaTagTypeMap } from "#data/arena-tag"; import { EntryHazardTag, getArenaTag } from "#data/arena-tag"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#data/form-change-triggers"; import type { PokemonSpecies } from "#data/pokemon-species"; +import type { PositionalTag } from "#data/positional-tags/positional-tag"; import { PositionalTagManager } from "#data/positional-tags/positional-tag-manager"; import { getTerrainClearMessage, getTerrainStartMessage, Terrain, TerrainType } from "#data/terrain"; import { diff --git a/src/modifier/init-modifier-pools.ts b/src/modifier/init-modifier-pools.ts index e6ec69eac7f..a3ae4730356 100644 --- a/src/modifier/init-modifier-pools.ts +++ b/src/modifier/init-modifier-pools.ts @@ -1,7 +1,3 @@ -/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { initModifierTypes } from "#modifiers/modifier-type"; -/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import { timedEventManager } from "#app/global-event-manager"; import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#balance/pokemon-evolutions"; @@ -29,6 +25,7 @@ import { trainerModifierPool, wildModifierPool, } from "#modifiers/modifier-pools"; +import type { initModifierTypes } from "#modifiers/modifier-type"; import { WeightedModifierType } from "#modifiers/modifier-type"; import type { WeightedModifierTypeWeightFunc } from "#types/modifier-types"; diff --git a/src/phase-tree.ts b/src/phase-tree.ts index f0501a6ece6..f348f89818c 100644 --- a/src/phase-tree.ts +++ b/src/phase-tree.ts @@ -1,10 +1,6 @@ -// biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports -import type { PhaseManager } from "#app/@types/phase-types"; -import type { DynamicPhaseMarker } from "#phases/dynamic-phase-marker"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports - -import type { PhaseMap, PhaseString } from "#app/@types/phase-types"; +import type { PhaseManager, PhaseMap, PhaseString } from "#app/@types/phase-types"; import type { Phase } from "#app/phase"; +import type { DynamicPhaseMarker } from "#phases/dynamic-phase-marker"; import type { PhaseConditionFunc } from "#types/phase-types"; /** diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 48fc5f6f960..50dd16a4d3f 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,7 +1,3 @@ -// biome-ignore-start lint/correctness/noUnusedImports: Used in a tsdoc comment -import type { Move, PreUseInterruptAttr } from "#types/move-types"; -// biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment - import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -32,6 +28,7 @@ import type { Pokemon } from "#field/pokemon"; import { applyMoveAttrs } from "#moves/apply-attrs"; import { frenzyMissFunc } from "#moves/move-utils"; import type { PokemonMove } from "#moves/pokemon-move"; +import type { Move, PreUseInterruptAttr } from "#types/move-types"; import type { TurnMove } from "#types/turn-move"; import { applyChallenges } from "#utils/challenge-utils"; import { BooleanHolder, NumberHolder } from "#utils/common"; diff --git a/src/phases/positional-tag-phase.ts b/src/phases/positional-tag-phase.ts index dec572273c5..78eab6dcd44 100644 --- a/src/phases/positional-tag-phase.ts +++ b/src/phases/positional-tag-phase.ts @@ -1,10 +1,7 @@ -// biome-ignore-start lint/correctness/noUnusedImports: TSDocs -import type { PositionalTag } from "#data/positional-tags/positional-tag"; -import type { TurnEndPhase } from "#phases/turn-end-phase"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDocs - import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; +import type { PositionalTag } from "#data/positional-tags/positional-tag"; +import type { TurnEndPhase } from "#phases/turn-end-phase"; /** * Phase to trigger all pending post-turn {@linkcode PositionalTag}s. diff --git a/src/system/version-migration/version-converter.ts b/src/system/version-migration/version-converter.ts index 269e577ca3f..75ffde9934a 100644 --- a/src/system/version-migration/version-converter.ts +++ b/src/system/version-migration/version-converter.ts @@ -1,4 +1,4 @@ -/** biome-ignore-all lint/performance/noNamespaceImport: Convenience */ +/* biome-ignore-all lint/performance/noNamespaceImport: Convenience */ import { version } from "#package.json"; import type { SessionSaveData, SystemSaveData } from "#types/save-data"; diff --git a/test/test-utils/helpers/field-helper.ts b/test/test-utils/helpers/field-helper.ts index 53864c6ddef..16c24ae519f 100644 --- a/test/test-utils/helpers/field-helper.ts +++ b/test/test-utils/helpers/field-helper.ts @@ -1,8 +1,5 @@ -/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { globalScene } from "#app/global-scene"; -/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import type { Ability } from "#abilities/ability"; +import type { globalScene } from "#app/global-scene"; import { allAbilities } from "#data/data-lists"; import type { AbilityId } from "#enums/ability-id"; import type { BattlerIndex } from "#enums/battler-index"; diff --git a/test/test-utils/helpers/overrides-helper.ts b/test/test-utils/helpers/overrides-helper.ts index 32e7cc3f6b9..e938ac348e0 100644 --- a/test/test-utils/helpers/overrides-helper.ts +++ b/test/test-utils/helpers/overrides-helper.ts @@ -1,8 +1,3 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { NewArenaEvent } from "#events/battle-scene"; - -/** biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import { OVERRIDES_COLOR } from "#app/constants/colors"; import type { BattleStyle, RandomTrainerOverride } from "#app/overrides"; import Overrides from "#app/overrides"; @@ -17,6 +12,7 @@ import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; import type { Unlockables } from "#enums/unlockables"; import { WeatherType } from "#enums/weather-type"; +import type { NewArenaEvent } from "#events/battle-scene"; import type { ModifierOverride } from "#modifiers/modifier-type"; import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; diff --git a/test/test-utils/matchers/to-be-at-phase.ts b/test/test-utils/matchers/to-be-at-phase.ts index 7ff76fa0365..461264467d1 100644 --- a/test/test-utils/matchers/to-be-at-phase.ts +++ b/test/test-utils/matchers/to-be-at-phase.ts @@ -1,8 +1,5 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ import type { Phase } from "#app/phase"; import type { GameManager } from "#test/test-utils/game-manager"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc - import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; import type { PhaseString } from "#types/phase-types"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-ability-applied.ts b/test/test-utils/matchers/to-have-ability-applied.ts index 1ed74410de0..9168e1917d7 100644 --- a/test/test-utils/matchers/to-have-ability-applied.ts +++ b/test/test-utils/matchers/to-have-ability-applied.ts @@ -1,9 +1,6 @@ -/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { Pokemon } from "#field/pokemon"; -/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import { getPokemonNameWithAffix } from "#app/messages"; import { AbilityId } from "#enums/ability-id"; +import type { Pokemon } from "#field/pokemon"; import { getEnumStr } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-arena-tag.ts b/test/test-utils/matchers/to-have-arena-tag.ts index a9d619686d1..f54a4365912 100644 --- a/test/test-utils/matchers/to-have-arena-tag.ts +++ b/test/test-utils/matchers/to-have-arena-tag.ts @@ -2,7 +2,6 @@ import type { ArenaTag, ArenaTagTypeMap } from "#data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import type { ArenaTagType } from "#enums/arena-tag-type"; import type { OneOther } from "#test/@types/test-helpers"; -// biome-ignore lint/correctness/noUnusedImports: TSDoc import type { GameManager } from "#test/test-utils/game-manager"; import { getOnelineDiffStr } from "#test/test-utils/string-utils"; import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; diff --git a/test/test-utils/matchers/to-have-battler-tag.ts b/test/test-utils/matchers/to-have-battler-tag.ts index aa86bf8b911..5f014247806 100644 --- a/test/test-utils/matchers/to-have-battler-tag.ts +++ b/test/test-utils/matchers/to-have-battler-tag.ts @@ -1,10 +1,7 @@ -/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { Pokemon } from "#field/pokemon"; -/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import { getPokemonNameWithAffix } from "#app/messages"; import type { BattlerTagTypeMap } from "#data/battler-tags"; import { BattlerTagType } from "#enums/battler-tag-type"; +import type { Pokemon } from "#field/pokemon"; import type { OneOther } from "#test/@types/test-helpers"; import { getEnumStr, getOnelineDiffStr } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; diff --git a/test/test-utils/matchers/to-have-fainted.ts b/test/test-utils/matchers/to-have-fainted.ts index f3e84e7a425..82a3e4f0d35 100644 --- a/test/test-utils/matchers/to-have-fainted.ts +++ b/test/test-utils/matchers/to-have-fainted.ts @@ -1,5 +1,4 @@ import { getPokemonNameWithAffix } from "#app/messages"; -// biome-ignore lint/correctness/noUnusedImports: TSDoc import type { Pokemon } from "#field/pokemon"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-full-hp.ts b/test/test-utils/matchers/to-have-full-hp.ts index 893bb647283..5b9c2124c52 100644 --- a/test/test-utils/matchers/to-have-full-hp.ts +++ b/test/test-utils/matchers/to-have-full-hp.ts @@ -1,5 +1,4 @@ import { getPokemonNameWithAffix } from "#app/messages"; -// biome-ignore lint/correctness/noUnusedImports: TSDoc import type { Pokemon } from "#field/pokemon"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-hp.ts b/test/test-utils/matchers/to-have-hp.ts index e6463383ac2..95c0420f3fe 100644 --- a/test/test-utils/matchers/to-have-hp.ts +++ b/test/test-utils/matchers/to-have-hp.ts @@ -1,5 +1,4 @@ import { getPokemonNameWithAffix } from "#app/messages"; -// biome-ignore lint/correctness/noUnusedImports: TSDoc import type { Pokemon } from "#field/pokemon"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-positional-tag.ts b/test/test-utils/matchers/to-have-positional-tag.ts index 21c9a0c034c..26e5374f717 100644 --- a/test/test-utils/matchers/to-have-positional-tag.ts +++ b/test/test-utils/matchers/to-have-positional-tag.ts @@ -1,10 +1,7 @@ -// biome-ignore-start lint/correctness/noUnusedImports: TSDoc -import type { GameManager } from "#test/test-utils/game-manager"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc - import type { toSerializedPosTag } from "#data/positional-tags/load-positional-tag"; import type { PositionalTagType } from "#enums/positional-tag-type"; import type { OneOther } from "#test/@types/test-helpers"; +import type { GameManager } from "#test/test-utils/game-manager"; import { getOnelineDiffStr } from "#test/test-utils/string-utils"; import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; import { toTitleCase } from "#utils/strings"; diff --git a/test/test-utils/matchers/to-have-shown-message.ts b/test/test-utils/matchers/to-have-shown-message.ts index bf5576ee630..4e19ddfccbf 100644 --- a/test/test-utils/matchers/to-have-shown-message.ts +++ b/test/test-utils/matchers/to-have-shown-message.ts @@ -1,4 +1,3 @@ -// biome-ignore lint/correctness/noUnusedImports: TSDoc import type { GameManager } from "#test/test-utils/game-manager"; import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; import { truncateString } from "#utils/common"; diff --git a/test/test-utils/matchers/to-have-stat-stage.ts b/test/test-utils/matchers/to-have-stat-stage.ts index a9ae910aece..8b3f1072d63 100644 --- a/test/test-utils/matchers/to-have-stat-stage.ts +++ b/test/test-utils/matchers/to-have-stat-stage.ts @@ -1,9 +1,6 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ -import type { Pokemon } from "#field/pokemon"; -/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ - import { getPokemonNameWithAffix } from "#app/messages"; import type { BattleStat } from "#enums/stat"; +import type { Pokemon } from "#field/pokemon"; import { getStatName } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-status-effect.ts b/test/test-utils/matchers/to-have-status-effect.ts index fa5f0346ebd..0e879494e3b 100644 --- a/test/test-utils/matchers/to-have-status-effect.ts +++ b/test/test-utils/matchers/to-have-status-effect.ts @@ -1,10 +1,7 @@ -/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ -import type { Status } from "#data/status-effect"; -import type { Pokemon } from "#field/pokemon"; -/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ - import { getPokemonNameWithAffix } from "#app/messages"; +import type { Status } from "#data/status-effect"; import { StatusEffect } from "#enums/status-effect"; +import type { Pokemon } from "#field/pokemon"; import { getEnumStr, getOnelineDiffStr } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-taken-damage.ts b/test/test-utils/matchers/to-have-taken-damage.ts index 55c163a2dc7..923f4d571a7 100644 --- a/test/test-utils/matchers/to-have-taken-damage.ts +++ b/test/test-utils/matchers/to-have-taken-damage.ts @@ -1,8 +1,5 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ -import type { Pokemon } from "#field/pokemon"; -/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ - import { getPokemonNameWithAffix } from "#app/messages"; +import type { Pokemon } from "#field/pokemon"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import { toDmgValue } from "#utils/common"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-terrain.ts b/test/test-utils/matchers/to-have-terrain.ts index 9b6939168f0..a532b35cf86 100644 --- a/test/test-utils/matchers/to-have-terrain.ts +++ b/test/test-utils/matchers/to-have-terrain.ts @@ -1,8 +1,5 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ -import type { GameManager } from "#test/test-utils/game-manager"; -/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ - import { TerrainType } from "#app/data/terrain"; +import type { GameManager } from "#test/test-utils/game-manager"; import { getEnumStr } from "#test/test-utils/string-utils"; import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/matchers/to-have-used-move.ts b/test/test-utils/matchers/to-have-used-move.ts index 3697b3e0bc6..206557b96e3 100644 --- a/test/test-utils/matchers/to-have-used-move.ts +++ b/test/test-utils/matchers/to-have-used-move.ts @@ -1,9 +1,6 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ -import type { Pokemon } from "#field/pokemon"; -/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ - import { getPokemonNameWithAffix } from "#app/messages"; import type { MoveId } from "#enums/move-id"; +import type { Pokemon } from "#field/pokemon"; import { getOnelineDiffStr, getOrdinal } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { TurnMove } from "#types/turn-move"; diff --git a/test/test-utils/matchers/to-have-used-pp.ts b/test/test-utils/matchers/to-have-used-pp.ts index bdbe4e140ff..4d11d2b5b6d 100644 --- a/test/test-utils/matchers/to-have-used-pp.ts +++ b/test/test-utils/matchers/to-have-used-pp.ts @@ -1,10 +1,7 @@ -// biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports -import type { Pokemon } from "#field/pokemon"; -// biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports - import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { MoveId } from "#enums/move-id"; +import type { Pokemon } from "#field/pokemon"; import { getEnumStr } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import { coerceArray } from "#utils/array"; diff --git a/test/test-utils/matchers/to-have-weather.ts b/test/test-utils/matchers/to-have-weather.ts index 7604cd5f890..e520fcdfe4e 100644 --- a/test/test-utils/matchers/to-have-weather.ts +++ b/test/test-utils/matchers/to-have-weather.ts @@ -1,8 +1,5 @@ -/** biome-ignore-start lint/correctness/noUnusedImports: TSDoc imports */ -import type { GameManager } from "#test/test-utils/game-manager"; -/** biome-ignore-end lint/correctness/noUnusedImports: TSDoc imports */ - import { WeatherType } from "#enums/weather-type"; +import type { GameManager } from "#test/test-utils/game-manager"; import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils"; import { toTitleCase } from "#utils/strings"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; diff --git a/test/test-utils/setup/test-end-log.ts b/test/test-utils/setup/test-end-log.ts index 7f92081e0e1..72c875c5e85 100644 --- a/test/test-utils/setup/test-end-log.ts +++ b/test/test-utils/setup/test-end-log.ts @@ -5,7 +5,6 @@ * @module */ -// biome-ignore lint/correctness/noUnusedImports: TSDoc import type CustomDefaultReporter from "#test/test-utils/reporters/custom-default-reporter"; import { join, relative } from "path"; import chalk from "chalk"; From 03cc0b1af241a45da0e0be11a3c4255ae62d9547 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 29 Nov 2025 21:01:26 -0800 Subject: [PATCH 036/101] [Misc] Remove now-obsolete `noUselessStringConcat` suppression comments --- .dependency-cruiser.cjs | 52 ++++++++++++++++++++------ test/test-utils/helpers/move-helper.ts | 2 - 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index 8b956a11a11..cb1f8f154bf 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -5,7 +5,9 @@ module.exports = { name: "no-non-type-@type-exports", severity: "error", comment: - "Files in @types should not export anything but types and interfaces. The folder is intended to house imports that are removed at runtime, and thus should not contain anything with a bearing on runtime code.", + "Files in `@types/` should not export anything but types and interfaces. " + + "The folder is intended to house imports that are removed at runtime, " + + "and thus should not contain anything with a bearing on runtime code.", from: {}, to: { path: "(^|/)src/@types", @@ -27,7 +29,8 @@ module.exports = { name: "no-circular-at-runtime", severity: "error", comment: - "This dependency is part of a circular relationship. You might want to revise your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", + "This dependency is part of a circular relationship. You might want to revise " + + "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility).", from: {}, to: { circular: true, @@ -39,7 +42,11 @@ module.exports = { { name: "no-orphans", comment: - "This is an orphan module - it's likely not used (anymore?). Either use it or remove it. If it's logical this module is an orphan (i.e. it's a config file), add an exception for it in your dependency-cruiser configuration. By default this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration files (.d.ts), tsconfig.json and some of the babel and webpack configs.", + "This is an orphan module - it's likely not used [anymore]. Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + "add an exception for it in your dependency-cruiser configuration. By default " + + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", severity: "error", from: { orphan: true, @@ -56,7 +63,8 @@ module.exports = { { name: "no-deprecated-core", comment: - "A module depends on a node core module that has been deprecated. Find an alternative - these are bound to exist - node doesn't deprecate lightly.", + "A module depends on a node core module that has been deprecated. " + + "Find an alternative - these are bound to exist - node doesn't deprecate lightly.", severity: "error", from: {}, to: { @@ -88,7 +96,9 @@ module.exports = { { name: "not-to-deprecated", comment: - "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later version of that module, or find an alternative. Deprecated modules are a security risk.", + "This module uses a (version of an) npm module that has been deprecated. " + + "Either upgrade to a later version of that module, or find an alternative. " + + "Deprecated modules are a security risk.", severity: "error", from: {}, to: { @@ -99,7 +109,10 @@ module.exports = { name: "no-non-package-json", severity: "error", comment: - "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. That's problematic as the package either (1) won't be available on live (2 - worse) will be available on live with an non-guaranteed version. Fix it by adding the package to the dependencies in your package.json.", + "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + + "in your package.json.", from: {}, to: { dependencyTypes: ["npm-no-pkg", "npm-unknown"], @@ -108,7 +121,8 @@ module.exports = { { name: "not-to-unresolvable", comment: - "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm module: add it to your package.json. In all other cases you likely already know what to do.", + "This module depends on a module that cannot be found ('resolved to disk'). " + + "If it's an npm module: add it to your package.json. In all other cases you likely already know what to do.", severity: "error", from: {}, to: { @@ -118,7 +132,9 @@ module.exports = { { name: "no-duplicate-dep-types", comment: - "Likely this module depends on an external ('npm') package that occurs more than once in your package.json i.e. bot as a devDependencies and in dependencies. This will cause maintenance problems later on.", + "Likely this module depends on an external ('npm') package that occurs more than once " + + "in your package.json (i.e. both in `devDependencies` and in `dependencies`). " + + "This will cause maintenance problems later on.", severity: "error", from: {}, to: { @@ -135,7 +151,9 @@ module.exports = { { name: "not-to-spec", comment: - "This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. If there's something in a spec that's of use to other modules, it doesn't have that single responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.", + "This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + "responsibility anymore. Factor it out into (e.g.) a separate utility/helper or a mock.", severity: "error", from: {}, to: { @@ -146,7 +164,11 @@ module.exports = { name: "not-to-dev-dep", severity: "error", comment: - "This module depends on an npm package from the 'devDependencies' section of your package.json. It looks like something that ships to production, though. To prevent problems with npm packages that aren't there on production declare it (only!) in the 'dependencies'section of your package.json. If this module is development only - add it to the from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", + "This module depends on an npm package from the 'devDependencies' section of your " + + "package.json. It looks like something that ships to production, though. To prevent problems " + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + "section of your package.json. If this module is development only - add it to the " + + "from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", from: { path: "^(src)", pathNot: ["[.](?:spec|test|setup|script)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", "./test"], @@ -163,7 +185,10 @@ module.exports = { name: "optional-deps-used", severity: "info", comment: - "This module depends on an npm package that is declared as an optional dependency in your package.json. As this makes sense in limited situations only, it's flagged here. If you're using an optional dependency here by design - add an exception to yourdependency-cruiser configuration.", + "This module depends on an npm package that is declared as an optional dependency " + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + "dependency-cruiser configuration.", from: {}, to: { dependencyTypes: ["npm-optional"], @@ -172,7 +197,10 @@ module.exports = { { name: "peer-deps-used", comment: - "This module depends on an npm package that is declared as a peer dependency in your package.json. This makes sense if your package is e.g. a plugin, but in other cases - maybe not so much. If the use of a peer dependency is intentional add an exception to your dependency-cruiser configuration.", + "This module depends on an npm package that is declared as a peer dependency " + + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + + "other cases - maybe not so much. If the use of a peer dependency is intentional " + + "add an exception to your dependency-cruiser configuration.", severity: "error", from: {}, to: { diff --git a/test/test-utils/helpers/move-helper.ts b/test/test-utils/helpers/move-helper.ts index 445aa3aa078..d6bf0801670 100644 --- a/test/test-utils/helpers/move-helper.ts +++ b/test/test-utils/helpers/move-helper.ts @@ -67,7 +67,6 @@ export class MoveHelper extends GameManagerHelper { const movePosition = this.getMovePosition(pkmIndex, move); if (movePosition === -1) { expect.fail( - // biome-ignore lint/complexity/noUselessStringConcat: Biome does not currently detect this as multiline (BUG) `MoveHelper.select called with move '${toTitleCase(MoveId[move])}' not in moveset!` + `\nBattler Index: ${toTitleCase(BattlerIndex[pkmIndex])}` + `\nMoveset: [${this.game.scene @@ -112,7 +111,6 @@ export class MoveHelper extends GameManagerHelper { const movePosition = this.getMovePosition(pkmIndex, move); if (movePosition === -1) { expect.fail( - // biome-ignore lint/complexity/noUselessStringConcat: Biome does not currently detect this as multiline (BUG) `MoveHelper.selectWithTera called with move '${toTitleCase(MoveId[move])}' not in moveset!` + `\nBattler Index: ${toTitleCase(BattlerIndex[pkmIndex])}` + `\nMoveset: [${this.game.scene From cce1653b70480c1bfe78549352428d708972491d Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:25:44 -0500 Subject: [PATCH 037/101] [Refactor] Remove unused `PreSwitchOutClearWeatherAbAttr` class https://github.com/pagefaultgames/pokerogue/pull/6809 --- src/data/abilities/ability.ts | 61 +---------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index f703c4aa75f..bb54c2303f3 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -3361,6 +3361,7 @@ export class CommanderAbAttr extends AbAttr { /** * Base class for ability attributes that apply their effect when their user switches out. */ +// TODO: Clarify the differences between this and `PreLeaveFieldAbAttr` export abstract class PreSwitchOutAbAttr extends AbAttr { constructor(showAbility = true) { super(showAbility); @@ -3393,65 +3394,6 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { } } -/** - * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. - */ -export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { - override apply({ pokemon, simulated }: AbAttrBaseParams): boolean { - // TODO: Evaluate why this is returning a boolean rather than relay - const weatherType = globalScene.arena.weather?.weatherType; - let turnOffWeather = false; - - // Clear weather only if user's ability matches the weather and no other pokemon has the ability. - switch (weatherType) { - case WeatherType.HARSH_SUN: - if ( - pokemon.hasAbility(AbilityId.DESOLATE_LAND) - && globalScene - .getField(true) - .filter(p => p !== pokemon) - .filter(p => p.hasAbility(AbilityId.DESOLATE_LAND)).length === 0 - ) { - turnOffWeather = true; - } - break; - case WeatherType.HEAVY_RAIN: - if ( - pokemon.hasAbility(AbilityId.PRIMORDIAL_SEA) - && globalScene - .getField(true) - .filter(p => p !== pokemon) - .filter(p => p.hasAbility(AbilityId.PRIMORDIAL_SEA)).length === 0 - ) { - turnOffWeather = true; - } - break; - case WeatherType.STRONG_WINDS: - if ( - pokemon.hasAbility(AbilityId.DELTA_STREAM) - && globalScene - .getField(true) - .filter(p => p !== pokemon) - .filter(p => p.hasAbility(AbilityId.DELTA_STREAM)).length === 0 - ) { - turnOffWeather = true; - } - break; - } - - if (simulated) { - return turnOffWeather; - } - - if (turnOffWeather) { - globalScene.arena.trySetWeather(WeatherType.NONE); - return true; - } - - return false; - } -} - export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { override canApply({ pokemon }: AbAttrBaseParams): boolean { return !pokemon.isFullHp(); @@ -6730,7 +6672,6 @@ const AbilityAttrs = Object.freeze({ CommanderAbAttr, PreSwitchOutAbAttr, PreSwitchOutResetStatusAbAttr, - PreSwitchOutClearWeatherAbAttr, PreSwitchOutHealAbAttr, PreSwitchOutFormChangeAbAttr, PreLeaveFieldAbAttr, From 004cb34a4674f2d4dcbb23361c312b809fb34889 Mon Sep 17 00:00:00 2001 From: Lugiad Date: Mon, 1 Dec 2025 17:34:35 +0100 Subject: [PATCH 038/101] [UI/UX] Added fonts for Devanagari and Thai writing systems (#6798) Added fonts for Devanagari and Thai writing systems Co-authored-by: damocleas --- src/plugins/i18n.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index b41cf298186..acb7be75f7a 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -24,6 +24,8 @@ const unicodeRanges = { kana: "U+3040-30FF", CJKCommon: "U+2E80-2EFF,U+3000-303F,U+31C0-31EF,U+3200-32FF,U+3400-4DBF,U+F900-FAFF,U+FE30-FE4F", CJKIdeograph: "U+4E00-9FFF", + devanagari: "U+0900-097F", + thai: "U+0E00-0E7F", specialCharacters: "U+266A,U+2605,U+2665,U+2663", //♪.★,♥,♣ }; @@ -88,6 +90,28 @@ const fonts: LoadingFontFaceProperty[] = [ }), only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "tl"], }, + // devanagari + { + face: new FontFace("emerald", "url(./fonts/8-bit-devanagari.ttf)", { + unicodeRange: unicodeRanges.devanagari, + }), + }, + { + face: new FontFace("pkmnems", "url(./fonts/8-bit-devanagari.ttf)", { + unicodeRange: unicodeRanges.devanagari, + }), + }, + // thai + { + face: new FontFace("emerald", "url(./fonts/fsrebellion.otf)", { + unicodeRange: unicodeRanges.thai, + }), + }, + { + face: new FontFace("pkmnems", "url(./fonts/terrible-thaifix.ttf)", { + unicodeRange: unicodeRanges.thai, + }), + }, ]; //#region Functions From 6d1a69bfea20e5be10f3bcbe68fd44c0416f4857 Mon Sep 17 00:00:00 2001 From: Austin Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:52:54 -0500 Subject: [PATCH 039/101] [Bug] Fix Old Gateau disappearing on reload (#6813) --- src/data/mystery-encounters/encounters/weird-dream-encounter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 56d3dda817f..16c4d681e42 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -463,7 +463,7 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) { // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats if (shouldGetOldGateau(newPokemon)) { const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU(); - const modifier = modType?.newModifier(newPokemon); + const modifier = modType.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU).newModifier(newPokemon); if (modifier) { globalScene.addModifier(modifier, false, false, false, true); } From b0a6b027fab226017c7cafedc2f9906f9871e678 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:23:50 +0100 Subject: [PATCH 040/101] [Misc] Migrate to `@inquirer/prompts` for CLI script input (#6789) * update dependencies * Update create-test * use `validate` for filename check * fix template path * update parse-egg-moves * update scrape-trainer-names * replace use of `inquirer` * Fix package ordering --- package.json | 2 +- pnpm-lock.yaml | 784 ++++++++++++------------- scripts/create-test/interactive.js | 42 +- scripts/parse-egg-moves/interactive.js | 76 +-- scripts/parse-egg-moves/main.js | 2 +- scripts/scrape-trainer-names/main.js | 16 +- 6 files changed, 439 insertions(+), 483 deletions(-) diff --git a/package.json b/package.json index c53bfbe0c86..7a4f87f1e64 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "devDependencies": { "@biomejs/biome": "2.3.8", + "@inquirer/prompts": "^8.0.1", "@ls-lint/ls-lint": "2.3.1", "@types/crypto-js": "^4.2.2", "@types/jsdom": "^27.0.0", @@ -49,7 +50,6 @@ "@vitest/utils": "^4.0.14", "chalk": "^5.6.2", "dependency-cruiser": "^17.3.1", - "inquirer": "^13.0.1", "jsdom": "^27.2.0", "lefthook": "^2.0.4", "msw": "^2.12.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a45d078838e..1607bfce611 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@biomejs/biome': specifier: 2.3.8 version: 2.3.8 + '@inquirer/prompts': + specifier: ^8.0.1 + version: 8.0.1(@types/node@24.10.1) '@ls-lint/ls-lint': specifier: 2.3.1 version: 2.3.1 @@ -62,7 +65,7 @@ importers: version: 24.10.1 '@vitest/coverage-istanbul': specifier: ^4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) + version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) '@vitest/expect': specifier: ^4.0.14 version: 4.0.14 @@ -75,12 +78,9 @@ importers: dependency-cruiser: specifier: ^17.3.1 version: 17.3.1 - inquirer: - specifier: ^13.0.1 - version: 13.0.1(@types/node@24.10.1) jsdom: specifier: ^27.2.0 - version: 27.2.0 + version: 27.2.0(postcss@8.5.6) lefthook: specifier: ^2.0.4 version: 2.0.4 @@ -113,18 +113,18 @@ importers: version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) vitest: specifier: ^4.0.14 - version: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + version: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) vitest-canvas-mock: specifier: ^1.1.2 - version: 1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) + version: 1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) packages: '@acemir/cssom@0.9.24': resolution: {integrity: sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==} - '@asamuzakjp/css-color@4.1.0': - resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==} + '@asamuzakjp/css-color@4.0.5': + resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} '@asamuzakjp/dom-selector@6.7.4': resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} @@ -136,16 +136,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -170,6 +170,10 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} @@ -182,6 +186,11 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} @@ -195,8 +204,12 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} '@babel/types@7.28.5': @@ -284,175 +297,177 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-syntax-patches-for-csstree@1.0.19': - resolution: {integrity: sha512-QW5/SM2ARltEhoKcmRI1LoLf3/C7dHGswwCnfLcoMgqurBT4f8GvwXMgAbK/FwcxthmJRK5MGTtddj0yQn0J9g==} + '@csstools/css-syntax-patches-for-csstree@1.0.14': + resolution: {integrity: sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==} engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@gerrit0/mini-shiki@3.17.0': - resolution: {integrity: sha512-Bpf6WuFar20ZXL6qU6VpVl4bVQfyyYiX+6O4xrns4nkU3Mr8paeupDbS1HENpcLOYj7pN4Rkd/yCaPA0vQwKww==} + '@gerrit0/mini-shiki@3.13.0': + resolution: {integrity: sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==} - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + '@inquirer/ansi@1.0.0': + resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==} engines: {node: '>=18'} '@inquirer/ansi@2.0.1': @@ -468,8 +483,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.21': - resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + '@inquirer/confirm@5.1.18': + resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -486,8 +501,8 @@ packages: '@types/node': optional: true - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + '@inquirer/core@10.2.2': + resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -531,8 +546,8 @@ packages: '@types/node': optional: true - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + '@inquirer/figures@1.0.13': + resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} engines: {node: '>=18'} '@inquirer/figures@2.0.1': @@ -602,8 +617,8 @@ packages: '@types/node': optional: true - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + '@inquirer/type@3.0.8': + resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -662,138 +677,138 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@rollup/rollup-android-arm-eabi@4.53.3': - resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + '@rollup/rollup-android-arm-eabi@4.52.4': + resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.53.3': - resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + '@rollup/rollup-android-arm64@4.52.4': + resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.53.3': - resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + '@rollup/rollup-darwin-arm64@4.52.4': + resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.53.3': - resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + '@rollup/rollup-darwin-x64@4.52.4': + resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.3': - resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + '@rollup/rollup-freebsd-arm64@4.52.4': + resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.3': - resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + '@rollup/rollup-freebsd-x64@4.52.4': + resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': - resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.53.3': - resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + '@rollup/rollup-linux-arm-musleabihf@4.52.4': + resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.53.3': - resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + '@rollup/rollup-linux-arm64-gnu@4.52.4': + resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.53.3': - resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + '@rollup/rollup-linux-arm64-musl@4.52.4': + resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.53.3': - resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + '@rollup/rollup-linux-loong64-gnu@4.52.4': + resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.53.3': - resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + '@rollup/rollup-linux-ppc64-gnu@4.52.4': + resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.53.3': - resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + '@rollup/rollup-linux-riscv64-gnu@4.52.4': + resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.53.3': - resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + '@rollup/rollup-linux-riscv64-musl@4.52.4': + resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.53.3': - resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + '@rollup/rollup-linux-s390x-gnu@4.52.4': + resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.53.3': - resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + '@rollup/rollup-linux-x64-gnu@4.52.4': + resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.53.3': - resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + '@rollup/rollup-linux-x64-musl@4.52.4': + resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openharmony-arm64@4.53.3': - resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + '@rollup/rollup-openharmony-arm64@4.52.4': + resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.3': - resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + '@rollup/rollup-win32-arm64-msvc@4.52.4': + resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.3': - resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + '@rollup/rollup-win32-ia32-msvc@4.52.4': + resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.3': - resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + '@rollup/rollup-win32-x64-gnu@4.52.4': + resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.3': - resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + '@rollup/rollup-win32-x64-msvc@4.52.4': + resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} cpu: [x64] os: [win32] - '@shikijs/engine-oniguruma@3.17.0': - resolution: {integrity: sha512-flSbHZAiOZDNTrEbULY8DLWavu/TyVu/E7RChpLB4WvKX4iHMfj80C6Hi3TjIWaQtHOW0KC6kzMcuB5TO1hZ8Q==} + '@shikijs/engine-oniguruma@3.13.0': + resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==} - '@shikijs/langs@3.17.0': - resolution: {integrity: sha512-icmur2n5Ojb+HAiQu6NEcIIJ8oWDFGGEpiqSCe43539Sabpx7Y829WR3QuUW2zjTM4l6V8Sazgb3rrHO2orEAw==} + '@shikijs/langs@3.13.0': + resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==} - '@shikijs/themes@3.17.0': - resolution: {integrity: sha512-/xEizMHLBmMHwtx4JuOkRf3zwhWD2bmG5BRr0IPjpcWpaq4C3mYEuTk/USAEglN0qPrTwEHwKVpSu/y2jhferA==} + '@shikijs/themes@3.13.0': + resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==} - '@shikijs/types@3.17.0': - resolution: {integrity: sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==} + '@shikijs/types@3.13.0': + resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -801,8 +816,8 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} '@types/crypto-js@4.2.2': resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} @@ -912,15 +927,11 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.31: - resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + baseline-browser-mapping@2.8.12: + resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} hasBin: true bidi-js@1.0.3: @@ -929,8 +940,8 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - browserslist@4.28.0: - resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + browserslist@4.26.3: + resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -946,8 +957,8 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - caniuse-lite@1.0.30001757: - resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + caniuse-lite@1.0.30001747: + resolution: {integrity: sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==} chai@6.2.1: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} @@ -1048,8 +1059,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.262: - resolution: {integrity: sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==} + electron-to-chromium@1.5.230: + resolution: {integrity: sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -1084,8 +1095,8 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} hasBin: true @@ -1262,15 +1273,6 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inquirer@13.0.1: - resolution: {integrity: sha512-+Qob/OSCmHIgyFKa4S+bDk36Nudwt+zpUBGZaSttGMnvsrzbIqtNFS9RutEPc2QAzpQxBP0cV3wmY/c5Vy73qg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - interpret@3.1.1: resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} engines: {node: '>=10.13.0'} @@ -1326,8 +1328,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true jsdom@27.2.0: @@ -1520,8 +1522,8 @@ packages: encoding: optional: true - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.23: + resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -1611,26 +1613,19 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} hasBin: true rettime@0.7.0: resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} - rollup@4.53.3: - resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + rollup@4.52.4: + resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - run-async@4.0.6: - resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} - engines: {node: '>=0.12.0'} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -1648,6 +1643,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -1743,11 +1743,11 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.19: - resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + tldts-core@7.0.16: + resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==} - tldts@7.0.19: - resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + tldts@7.0.16: + resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==} hasBin: true tough-cookie@6.0.0: @@ -1779,9 +1779,6 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-fest@5.2.0: resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} engines: {node: '>=20'} @@ -1824,8 +1821,8 @@ packages: until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - update-browserslist-db@1.1.4: - resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2018,7 +2015,7 @@ snapshots: '@acemir/cssom@0.9.24': {} - '@asamuzakjp/css-color@4.1.0': + '@asamuzakjp/css-color@4.0.5': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -2038,23 +2035,23 @@ snapshots: '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.28.4': {} - '@babel/core@7.28.5': + '@babel/core@7.28.4': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -2064,19 +2061,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': + '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.28.4 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.0 + browserslist: 4.26.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -2084,22 +2081,24 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} @@ -2107,7 +2106,11 @@ snapshots: '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/types': 7.28.4 + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 '@babel/parser@7.28.5': dependencies: @@ -2118,21 +2121,26 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 - '@babel/traverse@7.28.5': + '@babel/traverse@7.28.4': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/types': 7.28.4 debug: 4.4.3 transitivePeerDependencies: - supports-color + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -2191,97 +2199,99 @@ snapshots: dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-syntax-patches-for-csstree@1.0.19': {} + '@csstools/css-syntax-patches-for-csstree@1.0.14(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 '@csstools/css-tokenizer@3.0.4': {} - '@esbuild/aix-ppc64@0.25.12': + '@esbuild/aix-ppc64@0.25.10': optional: true - '@esbuild/android-arm64@0.25.12': + '@esbuild/android-arm64@0.25.10': optional: true - '@esbuild/android-arm@0.25.12': + '@esbuild/android-arm@0.25.10': optional: true - '@esbuild/android-x64@0.25.12': + '@esbuild/android-x64@0.25.10': optional: true - '@esbuild/darwin-arm64@0.25.12': + '@esbuild/darwin-arm64@0.25.10': optional: true - '@esbuild/darwin-x64@0.25.12': + '@esbuild/darwin-x64@0.25.10': optional: true - '@esbuild/freebsd-arm64@0.25.12': + '@esbuild/freebsd-arm64@0.25.10': optional: true - '@esbuild/freebsd-x64@0.25.12': + '@esbuild/freebsd-x64@0.25.10': optional: true - '@esbuild/linux-arm64@0.25.12': + '@esbuild/linux-arm64@0.25.10': optional: true - '@esbuild/linux-arm@0.25.12': + '@esbuild/linux-arm@0.25.10': optional: true - '@esbuild/linux-ia32@0.25.12': + '@esbuild/linux-ia32@0.25.10': optional: true - '@esbuild/linux-loong64@0.25.12': + '@esbuild/linux-loong64@0.25.10': optional: true - '@esbuild/linux-mips64el@0.25.12': + '@esbuild/linux-mips64el@0.25.10': optional: true - '@esbuild/linux-ppc64@0.25.12': + '@esbuild/linux-ppc64@0.25.10': optional: true - '@esbuild/linux-riscv64@0.25.12': + '@esbuild/linux-riscv64@0.25.10': optional: true - '@esbuild/linux-s390x@0.25.12': + '@esbuild/linux-s390x@0.25.10': optional: true - '@esbuild/linux-x64@0.25.12': + '@esbuild/linux-x64@0.25.10': optional: true - '@esbuild/netbsd-arm64@0.25.12': + '@esbuild/netbsd-arm64@0.25.10': optional: true - '@esbuild/netbsd-x64@0.25.12': + '@esbuild/netbsd-x64@0.25.10': optional: true - '@esbuild/openbsd-arm64@0.25.12': + '@esbuild/openbsd-arm64@0.25.10': optional: true - '@esbuild/openbsd-x64@0.25.12': + '@esbuild/openbsd-x64@0.25.10': optional: true - '@esbuild/openharmony-arm64@0.25.12': + '@esbuild/openharmony-arm64@0.25.10': optional: true - '@esbuild/sunos-x64@0.25.12': + '@esbuild/sunos-x64@0.25.10': optional: true - '@esbuild/win32-arm64@0.25.12': + '@esbuild/win32-arm64@0.25.10': optional: true - '@esbuild/win32-ia32@0.25.12': + '@esbuild/win32-ia32@0.25.10': optional: true - '@esbuild/win32-x64@0.25.12': + '@esbuild/win32-x64@0.25.10': optional: true - '@gerrit0/mini-shiki@3.17.0': + '@gerrit0/mini-shiki@3.13.0': dependencies: - '@shikijs/engine-oniguruma': 3.17.0 - '@shikijs/langs': 3.17.0 - '@shikijs/themes': 3.17.0 - '@shikijs/types': 3.17.0 + '@shikijs/engine-oniguruma': 3.13.0 + '@shikijs/langs': 3.13.0 + '@shikijs/themes': 3.13.0 + '@shikijs/types': 3.13.0 '@shikijs/vscode-textmate': 10.0.2 - '@inquirer/ansi@1.0.2': {} + '@inquirer/ansi@1.0.0': {} '@inquirer/ansi@2.0.1': {} @@ -2294,10 +2304,10 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 - '@inquirer/confirm@5.1.21(@types/node@24.10.1)': + '@inquirer/confirm@5.1.18(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.1) - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/core': 10.2.2(@types/node@24.10.1) + '@inquirer/type': 3.0.8(@types/node@24.10.1) optionalDependencies: '@types/node': 24.10.1 @@ -2308,11 +2318,11 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 - '@inquirer/core@10.3.2(@types/node@24.10.1)': + '@inquirer/core@10.2.2(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.1) + '@inquirer/ansi': 1.0.0 + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@24.10.1) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 @@ -2355,7 +2365,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 - '@inquirer/figures@1.0.15': {} + '@inquirer/figures@1.0.13': {} '@inquirer/figures@2.0.1': {} @@ -2420,7 +2430,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.1 - '@inquirer/type@3.0.10(@types/node@24.10.1)': + '@inquirer/type@3.0.8(@types/node@24.10.1)': optionalDependencies: '@types/node': 24.10.1 @@ -2471,86 +2481,86 @@ snapshots: '@open-draft/until@2.1.0': {} - '@rollup/rollup-android-arm-eabi@4.53.3': + '@rollup/rollup-android-arm-eabi@4.52.4': optional: true - '@rollup/rollup-android-arm64@4.53.3': + '@rollup/rollup-android-arm64@4.52.4': optional: true - '@rollup/rollup-darwin-arm64@4.53.3': + '@rollup/rollup-darwin-arm64@4.52.4': optional: true - '@rollup/rollup-darwin-x64@4.53.3': + '@rollup/rollup-darwin-x64@4.52.4': optional: true - '@rollup/rollup-freebsd-arm64@4.53.3': + '@rollup/rollup-freebsd-arm64@4.52.4': optional: true - '@rollup/rollup-freebsd-x64@4.53.3': + '@rollup/rollup-freebsd-x64@4.52.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + '@rollup/rollup-linux-arm-gnueabihf@4.52.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.3': + '@rollup/rollup-linux-arm-musleabihf@4.52.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.3': + '@rollup/rollup-linux-arm64-gnu@4.52.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.53.3': + '@rollup/rollup-linux-arm64-musl@4.52.4': optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.3': + '@rollup/rollup-linux-loong64-gnu@4.52.4': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.3': + '@rollup/rollup-linux-ppc64-gnu@4.52.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.3': + '@rollup/rollup-linux-riscv64-gnu@4.52.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.53.3': + '@rollup/rollup-linux-riscv64-musl@4.52.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.3': + '@rollup/rollup-linux-s390x-gnu@4.52.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.53.3': + '@rollup/rollup-linux-x64-gnu@4.52.4': optional: true - '@rollup/rollup-linux-x64-musl@4.53.3': + '@rollup/rollup-linux-x64-musl@4.52.4': optional: true - '@rollup/rollup-openharmony-arm64@4.53.3': + '@rollup/rollup-openharmony-arm64@4.52.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.3': + '@rollup/rollup-win32-arm64-msvc@4.52.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.3': + '@rollup/rollup-win32-ia32-msvc@4.52.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.53.3': + '@rollup/rollup-win32-x64-gnu@4.52.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.53.3': + '@rollup/rollup-win32-x64-msvc@4.52.4': optional: true - '@shikijs/engine-oniguruma@3.17.0': + '@shikijs/engine-oniguruma@3.13.0': dependencies: - '@shikijs/types': 3.17.0 + '@shikijs/types': 3.13.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.17.0': + '@shikijs/langs@3.13.0': dependencies: - '@shikijs/types': 3.17.0 + '@shikijs/types': 3.13.0 - '@shikijs/themes@3.17.0': + '@shikijs/themes@3.13.0': dependencies: - '@shikijs/types': 3.17.0 + '@shikijs/types': 3.13.0 - '@shikijs/types@3.17.0': + '@shikijs/types@3.13.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -2559,10 +2569,9 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@types/chai@5.2.3': + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 '@types/crypto-js@4.2.2': {} @@ -2590,7 +2599,7 @@ snapshots: '@types/unist@3.0.3': {} - '@vitest/coverage-istanbul@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1))': + '@vitest/coverage-istanbul@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1))': dependencies: '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -2601,14 +2610,14 @@ snapshots: magicast: 0.5.1 obug: 2.1.1 tinyrainbow: 3.0.3 - vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) transitivePeerDependencies: - supports-color '@vitest/expect@4.0.14': dependencies: '@standard-schema/spec': 1.0.0 - '@types/chai': 5.2.3 + '@types/chai': 5.2.2 '@vitest/spy': 4.0.14 '@vitest/utils': 4.0.14 chai: 6.2.1 @@ -2682,11 +2691,9 @@ snapshots: argparse@2.0.1: {} - assertion-error@2.0.1: {} - balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.31: {} + baseline-browser-mapping@2.8.12: {} bidi-js@1.0.3: dependencies: @@ -2696,13 +2703,13 @@ snapshots: dependencies: balanced-match: 1.0.2 - browserslist@4.28.0: + browserslist@4.26.3: dependencies: - baseline-browser-mapping: 2.8.31 - caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.262 - node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.28.0) + baseline-browser-mapping: 2.8.12 + caniuse-lite: 1.0.30001747 + electron-to-chromium: 1.5.230 + node-releases: 2.0.23 + update-browserslist-db: 1.1.3(browserslist@4.26.3) call-bind-apply-helpers@1.0.2: dependencies: @@ -2721,7 +2728,7 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - caniuse-lite@1.0.30001757: {} + caniuse-lite@1.0.30001747: {} chai@6.2.1: {} @@ -2775,11 +2782,13 @@ snapshots: cssfontparser@1.2.1: {} - cssstyle@5.3.3: + cssstyle@5.3.3(postcss@8.5.6): dependencies: - '@asamuzakjp/css-color': 4.1.0 - '@csstools/css-syntax-patches-for-csstree': 1.0.19 + '@asamuzakjp/css-color': 4.0.5 + '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) css-tree: 3.1.0 + transitivePeerDependencies: + - postcss dagre@0.8.5: dependencies: @@ -2832,7 +2841,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.262: {} + electron-to-chromium@1.5.230: {} emoji-regex@10.6.0: {} @@ -2857,34 +2866,34 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.25.12: + esbuild@0.25.10: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 escalade@3.2.0: {} @@ -3041,18 +3050,6 @@ snapshots: ini@4.1.1: {} - inquirer@13.0.1(@types/node@24.10.1): - dependencies: - '@inquirer/ansi': 2.0.1 - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/prompts': 8.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) - mute-stream: 3.0.0 - run-async: 4.0.6 - rxjs: 7.8.2 - optionalDependencies: - '@types/node': 24.10.1 - interpret@3.1.1: {} is-core-module@2.16.1: @@ -3080,11 +3077,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -3109,15 +3106,15 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.1: + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - jsdom@27.2.0: + jsdom@27.2.0(postcss@8.5.6): dependencies: '@acemir/cssom': 0.9.24 '@asamuzakjp/dom-selector': 6.7.4 - cssstyle: 5.3.3 + cssstyle: 5.3.3(postcss@8.5.6) data-urls: 6.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 @@ -3137,6 +3134,7 @@ snapshots: xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil + - postcss - supports-color - utf-8-validate @@ -3238,7 +3236,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.2 markdown-it@14.1.0: dependencies: @@ -3275,7 +3273,7 @@ snapshots: msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@24.10.1) + '@inquirer/confirm': 5.1.18(@types/node@24.10.1) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -3310,7 +3308,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.27: {} + node-releases@2.0.23: {} object-keys@1.1.1: {} @@ -3345,7 +3343,7 @@ snapshots: graphology: 0.25.4(graphology-types@0.24.8) i18next: 22.5.1 i18next-http-backend: 2.7.3 - js-yaml: 4.1.1 + js-yaml: 4.1.0 mustache: 4.2.0 papaparse: 5.5.3 webfontloader: 1.6.28 @@ -3392,7 +3390,7 @@ snapshots: rechoir@0.8.0: dependencies: - resolve: 1.22.11 + resolve: 1.22.10 regexp-tree@0.1.27: {} @@ -3400,7 +3398,7 @@ snapshots: require-from-string@2.0.2: {} - resolve@1.22.11: + resolve@1.22.10: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 @@ -3408,40 +3406,34 @@ snapshots: rettime@0.7.0: {} - rollup@4.53.3: + rollup@4.52.4: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.3 - '@rollup/rollup-android-arm64': 4.53.3 - '@rollup/rollup-darwin-arm64': 4.53.3 - '@rollup/rollup-darwin-x64': 4.53.3 - '@rollup/rollup-freebsd-arm64': 4.53.3 - '@rollup/rollup-freebsd-x64': 4.53.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 - '@rollup/rollup-linux-arm-musleabihf': 4.53.3 - '@rollup/rollup-linux-arm64-gnu': 4.53.3 - '@rollup/rollup-linux-arm64-musl': 4.53.3 - '@rollup/rollup-linux-loong64-gnu': 4.53.3 - '@rollup/rollup-linux-ppc64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-musl': 4.53.3 - '@rollup/rollup-linux-s390x-gnu': 4.53.3 - '@rollup/rollup-linux-x64-gnu': 4.53.3 - '@rollup/rollup-linux-x64-musl': 4.53.3 - '@rollup/rollup-openharmony-arm64': 4.53.3 - '@rollup/rollup-win32-arm64-msvc': 4.53.3 - '@rollup/rollup-win32-ia32-msvc': 4.53.3 - '@rollup/rollup-win32-x64-gnu': 4.53.3 - '@rollup/rollup-win32-x64-msvc': 4.53.3 + '@rollup/rollup-android-arm-eabi': 4.52.4 + '@rollup/rollup-android-arm64': 4.52.4 + '@rollup/rollup-darwin-arm64': 4.52.4 + '@rollup/rollup-darwin-x64': 4.52.4 + '@rollup/rollup-freebsd-arm64': 4.52.4 + '@rollup/rollup-freebsd-x64': 4.52.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 + '@rollup/rollup-linux-arm-musleabihf': 4.52.4 + '@rollup/rollup-linux-arm64-gnu': 4.52.4 + '@rollup/rollup-linux-arm64-musl': 4.52.4 + '@rollup/rollup-linux-loong64-gnu': 4.52.4 + '@rollup/rollup-linux-ppc64-gnu': 4.52.4 + '@rollup/rollup-linux-riscv64-gnu': 4.52.4 + '@rollup/rollup-linux-riscv64-musl': 4.52.4 + '@rollup/rollup-linux-s390x-gnu': 4.52.4 + '@rollup/rollup-linux-x64-gnu': 4.52.4 + '@rollup/rollup-linux-x64-musl': 4.52.4 + '@rollup/rollup-openharmony-arm64': 4.52.4 + '@rollup/rollup-win32-arm64-msvc': 4.52.4 + '@rollup/rollup-win32-ia32-msvc': 4.52.4 + '@rollup/rollup-win32-x64-gnu': 4.52.4 + '@rollup/rollup-win32-x64-msvc': 4.52.4 fsevents: 2.3.3 - run-async@4.0.6: {} - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - safe-buffer@5.1.2: {} safe-regex@2.1.1: @@ -3456,6 +3448,8 @@ snapshots: semver@6.3.1: {} + semver@7.7.2: {} + semver@7.7.3: {} set-function-length@1.2.2: @@ -3534,15 +3528,15 @@ snapshots: tinyrainbow@3.0.3: {} - tldts-core@7.0.19: {} + tldts-core@7.0.16: {} - tldts@7.0.19: + tldts@7.0.16: dependencies: - tldts-core: 7.0.19 + tldts-core: 7.0.16 tough-cookie@6.0.0: dependencies: - tldts: 7.0.19 + tldts: 7.0.16 tr46@0.0.3: {} @@ -3567,8 +3561,6 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tslib@2.8.1: {} - type-fest@5.2.0: dependencies: tagged-tag: 1.0.0 @@ -3587,7 +3579,7 @@ snapshots: typedoc@0.28.14(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.17.0 + '@gerrit0/mini-shiki': 3.13.0 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 @@ -3602,9 +3594,9 @@ snapshots: until-async@3.0.2: {} - update-browserslist-db@1.1.4(browserslist@4.28.0): + update-browserslist-db@1.1.3(browserslist@4.26.3): dependencies: - browserslist: 4.28.0 + browserslist: 4.26.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -3623,24 +3615,24 @@ snapshots: vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1): dependencies: - esbuild: 0.25.12 + esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.53.3 + rollup: 4.52.4 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.1 fsevents: 2.3.3 yaml: 2.8.1 - vitest-canvas-mock@1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)): + vitest-canvas-mock@1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) - vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0)(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1): + vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.14 '@vitest/mocker': 4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) @@ -3664,7 +3656,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.1 - jsdom: 27.2.0 + jsdom: 27.2.0(postcss@8.5.6) transitivePeerDependencies: - jiti - less diff --git a/scripts/create-test/interactive.js b/scripts/create-test/interactive.js index 622e823e9cd..bc09f22e7a9 100644 --- a/scripts/create-test/interactive.js +++ b/scripts/create-test/interactive.js @@ -4,8 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { input, select } from "@inquirer/prompts"; import chalk from "chalk"; -import inquirer from "inquirer"; import { validTestTypes } from "./constants.js"; /** @@ -17,16 +17,10 @@ import { validTestTypes } from "./constants.js"; */ export async function promptTestType() { /** @type {testType | "EXIT"} */ - const choice = ( - await inquirer.prompt([ - { - type: "list", - name: "selectedOption", - message: "What type of test would you like to create?", - choices: [...validTestTypes, "EXIT"], - }, - ]) - ).selectedOption; + const choice = await select({ + message: "What type of test would you like to create?", + choices: [...validTestTypes, "EXIT"], + }); if (choice === "EXIT") { console.log("Exiting..."); @@ -44,22 +38,16 @@ export async function promptTestType() { */ export async function promptFileName(selectedType) { /** @type {string} */ - const fileNameAnswer = ( - await inquirer.prompt([ - { - type: "input", - name: "userInput", - message: `Please provide the name of the ${selectedType}.`, - validate: name => { - const nameProcessed = name.trim().replace(".test.ts", ""); - if (nameProcessed.length === 0) { - return chalk.red.bold("✗ Cannot use an empty string as a file name!"); - } - return true; - }, - }, - ]) - ).userInput; + const fileNameAnswer = await input({ + message: `Please provide the name of the ${selectedType}.`, + validate: name => { + const nameProcessed = name.trim().replace(".test.ts", ""); + if (nameProcessed.length === 0) { + return chalk.red.bold("✗ Cannot use an empty string as a file name!"); + } + return true; + }, + }); // Trim whitespace and any extension suffixes return fileNameAnswer.trim().replace(".test.ts", ""); diff --git a/scripts/parse-egg-moves/interactive.js b/scripts/parse-egg-moves/interactive.js index 8e0f5e68d24..d4997ae8521 100644 --- a/scripts/parse-egg-moves/interactive.js +++ b/scripts/parse-egg-moves/interactive.js @@ -5,8 +5,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ import fs from "fs"; +import { input, select } from "@inquirer/prompts"; import chalk from "chalk"; -import inquirer from "inquirer"; import { showHelpText } from "./help-message.js"; /** @@ -19,16 +19,10 @@ import { showHelpText } from "./help-message.js"; */ export async function runInteractive() { /** @type {"Console" | "File" | "Help" | "Exit"} */ - const answer = await inquirer - .prompt([ - { - type: "list", - name: "type", - message: "Select the method to obtain egg moves.", - choices: ["Console", "File", "Help", "Exit"], - }, - ]) - .then(a => a.type); + const answer = await select({ + message: "Select the method to obtain egg moves.", + choices: ["Console", "File", "Help", "Exit"], + }); if (answer === "Exit") { console.log("Exiting..."); @@ -68,24 +62,18 @@ function promptForValue(type) { * @returns {Promise} The file path inputted by the user. */ async function getFilePath() { - return await inquirer - .prompt([ - { - type: "input", - name: "path", - message: "Please enter the path to the egg move CSV file.", - validate: input => { - if (input.trim() === "") { - return "File path cannot be empty!"; - } - if (!fs.existsSync(input)) { - return "File does not exist!"; - } - return true; - }, - }, - ]) - .then(answer => answer.path); + return await input({ + message: "Please enter the path to the egg move CSV file.", + validate: filePath => { + if (filePath.trim() === "") { + return "File path cannot be empty!"; + } + if (!fs.existsSync(filePath)) { + return "File does not exist!"; + } + return true; + }, + }); } /** @@ -93,22 +81,16 @@ async function getFilePath() { * @returns {Promise} The CSV input from the user. */ async function doPromptConsole() { - return await inquirer - .prompt([ - { - type: "input", - name: "csv", - message: "Please enter the egg move CSV text.", - validate: input => { - if (input.trim() === "") { - return "CSV text cannot be empty!"; - } - if (!input.match(/^[^,]+(,[^,]+){4}$/gm)) { - return "CSV text malformed - should contain 5 consecutive comma-separated values per line!"; - } - return true; - }, - }, - ]) - .then(answer => answer.csv); + return await input({ + message: "Please enter the egg move CSV text.", + validate: value => { + if (value.trim() === "") { + return "CSV text cannot be empty!"; + } + if (!value.match(/^[^,]+(,[^,]+){4}$/gm)) { + return "CSV text malformed - should contain 5 consecutive comma-separated values per line!"; + } + return true; + }, + }); } diff --git a/scripts/parse-egg-moves/main.js b/scripts/parse-egg-moves/main.js index ed7028a4ff3..06d983df02b 100644 --- a/scripts/parse-egg-moves/main.js +++ b/scripts/parse-egg-moves/main.js @@ -24,7 +24,7 @@ const version = "1.0.1"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.join(__dirname, "..", ".."); -const templatePath = path.join(__dirname, "egg-move-template.ts"); +const templatePath = path.join(__dirname, "egg-move-template.boilerplate.ts"); // TODO: Do we want this to be configurable? const eggMoveTargetPath = path.join(projectRoot, "src/data/balance/egg-moves.ts"); diff --git a/scripts/scrape-trainer-names/main.js b/scripts/scrape-trainer-names/main.js index bb5f48c62c3..534a1f7b778 100644 --- a/scripts/scrape-trainer-names/main.js +++ b/scripts/scrape-trainer-names/main.js @@ -7,8 +7,8 @@ import { existsSync, writeFileSync } from "node:fs"; import { format, inspect } from "node:util"; +import { confirm } from "@inquirer/prompts"; import chalk from "chalk"; -import inquirer from "inquirer"; import { JSDOM } from "jsdom"; import { toCamelCase, toPascalSnakeCase, toTitleCase } from "../helpers/strings.js"; import { checkGenderAndType } from "./check-gender.js"; @@ -285,16 +285,10 @@ async function tryWriteFile(outFile, output) { * @returns {Promise} Whether "Yes" or "No" was selected. */ async function promptExisting(outFile) { - return ( - await inquirer.prompt([ - { - type: "confirm", - name: "continue", - message: `File ${chalk.blue(outFile)} already exists!\nDo you want to replace it?`, - default: false, - }, - ]) - ).continue; + return await confirm({ + message: `File ${chalk.blue(outFile)} already exists!\nDo you want to replace it?`, + default: false, + }); } await main(); From e1b0e0f0aebc8f91d22bb981370468d8b637f605 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:54:12 +0100 Subject: [PATCH 041/101] [Balance] Allow candy gain for uncaught pokemon (#6791) * allow candy gain for uncaught mons * carry over friendship * apply suggestions Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Add friendship/candy related tests * Refactor friendship cap tests * Fix typo * Apply suggestions from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Fix test * Update test/field/pokemon.test.ts Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Replace `.startBattle` with `.runToSummon` --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- src/field/pokemon.ts | 7 ++-- src/system/game-data.ts | 9 +---- test/field/pokemon.test.ts | 83 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 5c59706b517..781c5508409 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5956,9 +5956,10 @@ export class PlayerPokemon extends Pokemon { // Add to candy progress for this mon's starter species and its fused species (if it has one) starterData.forEach(([sd, id]: [StarterDataEntry, SpeciesId]) => { sd.friendship = (sd.friendship || 0) + candyFriendshipAmount; - if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[id])) { - globalScene.gameData.addStarterCandy(getPokemonSpecies(id), 1); - sd.friendship = 0; + const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[id]); + if (sd.friendship >= friendshipCap) { + globalScene.gameData.addStarterCandy(getPokemonSpecies(id), Math.floor(sd.friendship / friendshipCap)); + sd.friendship %= friendshipCap; } }); } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index e2ccbc8abd0..86679a2d956 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1803,17 +1803,12 @@ export class GameData { /** * Adds a candy to the player's game data for a given {@linkcode PokemonSpecies}. - * Will do nothing if the player does not have the Pokemon owned in their system save data. * @param species * @param count */ addStarterCandy(species: PokemonSpecies, count: number): void { - // Only gain candies if the Pokemon has already been marked as caught in dex (ignore "rental" pokemon) - const speciesRootForm = species.getRootSpeciesId(); - if (globalScene.gameData.dexData[speciesRootForm].caughtAttr) { - globalScene.candyBar.showStarterSpeciesCandy(species.speciesId, count); - this.starterData[species.speciesId].candyCount += count; - } + globalScene.candyBar.showStarterSpeciesCandy(species.speciesId, count); + this.starterData[species.speciesId].candyCount += count; } /** diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index 94b3a2316ea..075ae36672d 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -1,4 +1,7 @@ import type { BattleScene } from "#app/battle-scene"; +import { RARE_CANDY_FRIENDSHIP_CAP } from "#app/constants"; +import { globalScene } from "#app/global-scene"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#balance/starters"; import { CustomPokemonData } from "#data/pokemon-data"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; @@ -226,4 +229,84 @@ describe("Spec - Pokemon", () => { expect(pokemon.friendship).toBe(friendship); } }); + + describe("Friendship", () => { + it("should cap friendship at 255", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + feebas.addFriendship(999); + + expect(feebas.friendship).toBe(255); + }); + + it("should not go below 0 friendship", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + feebas.addFriendship(-999); + + expect(feebas.friendship).toBe(0); + }); + + it("should respect Rare Candy friendship gain cap", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + feebas.addFriendship(999, true); + + expect(feebas.friendship).toBe(RARE_CANDY_FRIENDSHIP_CAP); + }); + + it("should get 3x candy friendship in classic mode", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const pokemonData = globalScene.gameData.starterData[SpeciesId.FEEBAS]; + feebas.friendship = 0; + pokemonData.friendship = 0; + + feebas.addFriendship(10); + + expect(feebas.friendship).toBe(10); + expect(pokemonData.friendship).toBe(30); + }); + + it("should carry over excess friendship into next candy, even if capped", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const pokemonData = globalScene.gameData.starterData[SpeciesId.FEEBAS]; + feebas.friendship = 0; + pokemonData.friendship = 15; + pokemonData.candyCount = 0; + + const cap = getStarterValueFriendshipCap(speciesStarterCosts[SpeciesId.FEEBAS]); + expect(cap).toBeLessThan(2015); + + feebas.addFriendship(2000, true); + + // Friendship gain was capped, but candy friendship overflowed several times over + expect(feebas.friendship).toBe(RARE_CANDY_FRIENDSHIP_CAP); + expect(pokemonData.friendship).toBe(6015 % cap); + expect(pokemonData.candyCount).toBe(Math.floor(6015 / cap)); + }); + }); + + it("should allow gaining candy for uncaught Pokémon", async () => { + await game.classicMode.runToSummon([SpeciesId.FEEBAS]); + + const feebas = game.field.getPlayerPokemon(); + const pokemonData = globalScene.gameData.starterData[SpeciesId.FEEBAS]; + feebas.friendship = 0; + pokemonData.candyCount = 0; + // mark feebas as uncaught + const dexEntry = globalScene.gameData.dexData[SpeciesId.FEEBAS]; + dexEntry.caughtAttr = 0n; + + feebas.addFriendship(2000); + + expect(dexEntry.caughtAttr).toBe(0n); + expect(pokemonData.candyCount).toBeGreaterThan(0); + }); }); From 17a1a3af977f4a1f4393f10453a866a4584669a6 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:40:01 -0500 Subject: [PATCH 042/101] [Dev] Add git blame ignore file (#6810) * [Dev] Add git blame ignore file * formatted file * added REUSE info to blame file idt we need to copyright this stuff * expanded commit SHAs to use full versions apparently the short ones dont work --- .git-blame-ignore-revs | 87 ++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..93c5d56a60e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2024-2025 NONE +# +# SPDX-License-Identifier: CC0-1.0 + +# This file lists revisions of large-scale formatting, style and code movement changes so that +# they can be excluded from git blame results. +# +# To set this file as the default ignore file for git blame, run: +# $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs + +# ESLint - The Essential Linter and Formatter for JavaScript and TypeScript (#1224) +bac6c22973fedff868e73a989443035c672e8f57 + +# [Refactor] use typescript strict-null (#3259) +a07d2c57a44361f4ad48e024067c9b7638c0d84d + +# [Test] Replace `doAttack()` with `move.select()` in tests (#3567) +828897316e089ba390bc5fd3503e9175c7f45e8e + +# [Misc] Add eslint rule to enforce indenting of `case` statements (#4692) +39abac65be26c2004ea1ca9a686cc8d7b35efb65 + +# [Refactor] Create global scene variable (#4766) +0107b1d47ea4a898f39dd3534fcccd4241c03470 + +# [Misc][Refactor][GitHub] Ditch eslint for biome, and add a formatter (#5495) +408b66f9135004e128d7d737904facc30529c771 + +# [Biome] Add and apply `lint/style/noNamespaceImport` (#5650) +6f56dce7712c609dc78f7ff11650eb6a34f8f661 + +# [Test] Remove deprecated test funcs (#5906) +a33638a7a36b01935e40ea876532c770b53f0994 + +# [Test] Remove redundant entries and `Array.fill()` in moveset overrides (#5907) +cdda539ac5609c1f0fa5da0114ee51d5bff82bf5 + +# [Test] Condensed all `game.override` calls into 1 chained line where possible (#5926) +061c9872658011c1eefba3c10b2de33bdff8a0b0 + +# [Test] Remove unneeded `mockRestore` and `testTimeout` calls in tests (#5927) +0918985a63cc8795bc9a45d8ceacdfaca5f12ee7 + +# [Misc] Improve enum naming (#5933) +9dcb904649474b9ccec52b7d1251ee731b5edabf + +# [Dev] Enable Biome checking of `ability.ts` (#5948) +75beec12a892d28b51d8cbd859a28fc793617736 + +# [Dev] Enable Biome import sorting (#6052) +8cf1b9f766051bd610ac803222272d6e7287571d + +# [Misc] Standardize-file-names (#6137) +51d4c33de056dad78990b82aedd9f1b527d05d6d + +# [Test] Cleaned up tests to use updated test utils; part 1 (#6176) +87340624011bc8a68a9181ac2917cddb81eb073c + +# [Test] Replaced all instances of `game.scene.getXXXPokemon()!` inside tests with `game.field.getXXXPokemon()` (#6178) +8da02bad50995077243c8036aeef215ef42b42d4 + +# [Refactor] Remove `null` from `PhaseManager.currentPhase` signature (#6243) +d5e6670456acf3af98fac5bf7a20dc6cb3854c98 + +# [Dev] Migrated to Biome 2.2.3, added more rules (#6259) +c0da686ba0da1b08b56e4117839d9d45ed95e69a + +# [Misc] Make the repo REUSE compliant (#6474) +6766940fa15202c9995f2fed7287bf85939d9816 + +# [Test] Updated more uses of `game.scene.getEnemyField` and `game.scene.getPlayerField` to use updated test utils (#6524) +3d9e493e5f550ca933413c015bbb644398b1a8a7 + +# [Refactor] Remove `isNullOrUndefined` in favor of loose check against null (#6549) +c7a2c666af0009bb347616cefd9a767fb66b4b46 + +# [Refactor][Dev] Move public to its own submodule (#6590) +c695df777c041e9a2e747fd2767249cbb1235868 + +# [Dev] Improve typescript performance and version bump node and dependencies (#6627) +f4456f6c7cd77ecdb680517364b97fde9c07293f + +# [Dev] Enable linting of `move.ts` and update Biome to 2.3.2 (#6688) +d0ddcaa7a3a9dd262577abf60e40dcc8834faf7b + +# [Dev] Update Biome from `2.3.2` to `2.3.8` (#6799) +b6bd9566e24729e4677f1afb60da159425a9f60d diff --git a/package.json b/package.json index 7a4f87f1e64..4798aa66b2f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched", "typedoc": "typedoc", "depcruise": "depcruise src test", - "postinstall": "lefthook install; git config --local fetch.recurseSubmodules true", + "postinstall": "lefthook install; git config --local fetch.recurseSubmodules true; git config --local blame.ignoreRevsFile .git-blame-ignore-revs", "update-version:patch": "pnpm version patch --force --no-git-tag-version", "update-version:minor": "pnpm version minor --force --no-git-tag-version", "update-locales": "git submodule update --progress --init --recursive --depth 1 locales", From 2253289c760a3bbb0d6f3d1b303493088a599835 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:17:27 +0100 Subject: [PATCH 043/101] [i18n] Localize data type names (#6807) use localized data names --- src/system/game-data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 86679a2d956..556433fa611 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -78,6 +78,7 @@ import { executeIf, fixedInt, NumberHolder, randInt, randSeedItem } from "#utils import { decrypt, encrypt } from "#utils/data"; import { getEnumKeys } from "#utils/enums"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import { toCamelCase } from "#utils/strings"; import { AES, enc } from "crypto-js"; import i18next from "i18next"; @@ -1405,7 +1406,7 @@ export class GameData { reader.onload = (_ => { return e => { - let dataName = GameDataType[dataType].toLowerCase(); + const dataName = i18next.t(`gameData:${toCamelCase(GameDataType[dataType])}`); let dataStr = AES.decrypt(e.target?.result?.toString()!, saveKey).toString(enc.Utf8); // TODO: is this bang correct? let valid = false; try { @@ -1425,7 +1426,6 @@ export class GameData { case GameDataType.RUN_HISTORY: { const data = JSON.parse(dataStr); const keys = Object.keys(data); - dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); keys.forEach(key => { const entryKeys = Object.keys(data[key]); valid = From 507ac17f95982c4aa7ff8e8c7267c622e97e8a89 Mon Sep 17 00:00:00 2001 From: Juan Roman Date: Wed, 3 Dec 2025 20:06:22 -0500 Subject: [PATCH 044/101] [Dev] Update devcontainer image, settings and docs (#6822) * Updated devcontainer image to a version that supports the node >= 24.9.0 prerequisite. Also updated the prerequisite mentioned in CONTRIBUTING.md to match that newer prerequisite. * Update CONTRIBUTING.md with devcontainer instructions Added important note about using pnpm with devcontainers and updated Podman instructions. --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- .devcontainer/devcontainer.json | 5 ++++- .ls-lint.yml | 1 + CONTRIBUTING.md | 10 ++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8dbba3224a8..afc0cd77bdf 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,7 +9,7 @@ { "name": "Node.js & TypeScript", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", + "image": "mcr.microsoft.com/devcontainers/typescript-node:4-24-bookworm", "features": { "ghcr.io/devcontainers/features/github-cli:1": { "installDirectlyFromGitHubRelease": true, @@ -19,6 +19,9 @@ "version": "latest" } }, + "containerEnv": { + "PORT": "8000" + }, "customizations": { "vscode": { "settings": { diff --git a/.ls-lint.yml b/.ls-lint.yml index a7d8bc23e39..7fc955526c4 100644 --- a/.ls-lint.yml +++ b/.ls-lint.yml @@ -25,6 +25,7 @@ ls: <<: *cfg test: *src ignore: + - .pnpm-store - node_modules - .vscode - .github diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0756711d686..505c29d091a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,11 +47,18 @@ This Linux environment comes with all required dependencies needed to start work [codespaces-link]: [devcontainer-ext]: +> [!IMPORTANT] +> Due to quirks of devcontainer port forwarding, you must use **`pnpm start:podman`** to start a local dev server from within a devcontainer. +> All other instructions remain the same as local development. + +### Podman +For those who prefer Docker containers, see [this instructions page](./docs/podman.md) for information on how to setup a development environment with Podman. + ### Local Development #### Prerequisites -- node: >=22.14.0 - [manage with pnpm](https://pnpm.io/cli/env) | [manage with fnm](https://github.com/Schniz/fnm) | [manage with nvm](https://github.com/nvm-sh/nvm) | [manage with volta.sh](https://volta.sh/) +- node: >=24.9.0 - [manage with pnpm](https://pnpm.io/cli/env) | [manage with fnm](https://github.com/Schniz/fnm) | [manage with nvm](https://github.com/nvm-sh/nvm) | [manage with volta.sh](https://volta.sh/) - pnpm: 10.x - [how to install](https://pnpm.io/installation) (not recommended to install via `npm` on Windows native) | [alternate method - volta.sh](https://volta.sh/) - The repository [forked](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and [cloned](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) locally on your device @@ -91,7 +98,6 @@ Notable topics include: - [Linting & Formatting](./docs/linting.md) - [Localization](./docs/localization.md) - [Enemy AI move selection](./docs/enemy-ai.md) -- [Running with Podman](./docs/podman.md) Again, if you have unanswered questions please feel free to ask! From 8ae79450d4add5e9ecad0a0c3cb35bfa3fa7a2d0 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:49:47 -0500 Subject: [PATCH 045/101] [Test] Fix even more `game.scene.getXXX` issues (#6811) * [Test] Fixed even more `game.scene.getXXX` issues * Update fell-stinger.test.ts Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> * Applied review comments * fixed doodle test inconsistencies * fix * applied reviews not going too ham will make another PR later --------- Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> --- test/abilities/own-tempo.test.ts | 9 ++--- test/abilities/sheer-force.test.ts | 10 +++--- test/abilities/victory-star.test.ts | 4 +-- test/moves/baneful-bunker.test.ts | 4 +-- test/moves/destiny-bond.test.ts | 36 ++++++++------------ test/moves/doodle.test.ts | 5 +-- test/moves/fairy-lock.test.ts | 28 ++++++++------- test/moves/fell-stinger.test.ts | 4 +-- test/moves/first-attack-double-power.test.ts | 2 +- test/moves/instruct.test.ts | 4 +-- test/moves/magic-coat.test.ts | 4 +-- test/moves/metal-burst.test.ts | 2 +- test/moves/mirror-move.test.ts | 2 +- test/moves/parting-shot.test.ts | 10 +++--- test/moves/protect.test.ts | 10 +++--- test/moves/quick-guard.test.ts | 2 +- test/moves/revival-blessing.test.ts | 6 ++-- test/moves/taunt.test.ts | 2 +- test/moves/torment.test.ts | 2 +- test/moves/wide-guard.test.ts | 4 +-- test/phases/learn-move-phase.test.ts | 4 +-- 21 files changed, 76 insertions(+), 78 deletions(-) diff --git a/test/abilities/own-tempo.test.ts b/test/abilities/own-tempo.test.ts index c919d9eae53..05d69a950c6 100644 --- a/test/abilities/own-tempo.test.ts +++ b/test/abilities/own-tempo.test.ts @@ -39,13 +39,14 @@ describe("Abilities - Own Tempo", () => { .moveset(MoveId.SKILL_SWAP) .enemyMoveset(MoveId.SPLASH); await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const enemy = game.scene.getEnemyPokemon(); - enemy?.addTag(BattlerTagType.CONFUSED); - expect(enemy?.getTag(BattlerTagType.CONFUSED)).toBeTruthy(); + + const enemy = game.field.getEnemyPokemon(); + enemy.addTag(BattlerTagType.CONFUSED); + expect(enemy).toHaveBattlerTag(BattlerTagType.CONFUSED); game.move.select(MoveId.SKILL_SWAP); await game.phaseInterceptor.to("BerryPhase"); - expect(enemy?.getTag(BattlerTagType.CONFUSED)).toBeFalsy(); + expect(enemy).not.toHaveBattlerTag(BattlerTagType.CONFUSED); }); }); diff --git a/test/abilities/sheer-force.test.ts b/test/abilities/sheer-force.test.ts index 10fd454d6c2..847d1fa98ed 100644 --- a/test/abilities/sheer-force.test.ts +++ b/test/abilities/sheer-force.test.ts @@ -94,7 +94,7 @@ describe("Abilities - Sheer Force", () => { .enemyAbility(AbilityId.COLOR_CHANGE); await game.classicMode.startBattle([SpeciesId.PIDGEOT]); - const enemyPokemon = game.scene.getEnemyPokemon(); + const enemyPokemon = game.field.getEnemyPokemon(); const headbuttMove = allMoves[MoveId.HEADBUTT]; vi.spyOn(headbuttMove, "calculateBattlePower"); const headbuttFlinchAttr = headbuttMove.getAttrs("FlinchAttr")[0]; @@ -106,7 +106,7 @@ describe("Abilities - Sheer Force", () => { await game.move.forceHit(); await game.phaseInterceptor.to("BerryPhase", false); - expect(enemyPokemon?.getTypes()[0]).toBe(PokemonType.WATER); + expect(enemyPokemon.getTypes()[0]).toBe(PokemonType.WATER); expect(headbuttMove.calculateBattlePower).toHaveLastReturnedWith(headbuttMove.power * SHEER_FORCE_MULT); expect(headbuttFlinchAttr.getMoveChance).toHaveLastReturnedWith(0); }); @@ -144,11 +144,11 @@ describe("Abilities - Sheer Force", () => { .enemyLevel(100); await game.classicMode.startBattle([SpeciesId.MELOETTA]); - const playerPokemon = game.scene.getPlayerPokemon(); - const formKeyStart = playerPokemon?.getFormKey(); + const playerPokemon = game.field.getPlayerPokemon(); + const formKeyStart = playerPokemon.getFormKey(); game.move.select(MoveId.RELIC_SONG); await game.phaseInterceptor.to("TurnEndPhase"); - expect(formKeyStart).toBe(playerPokemon?.getFormKey()); + expect(formKeyStart).toBe(playerPokemon.getFormKey()); }); }); diff --git a/test/abilities/victory-star.test.ts b/test/abilities/victory-star.test.ts index 40611f6fbc1..c96c8761e24 100644 --- a/test/abilities/victory-star.test.ts +++ b/test/abilities/victory-star.test.ts @@ -35,7 +35,7 @@ describe("Abilities - Victory Star", () => { it("should increase the accuracy of its user", async () => { await game.classicMode.startBattle([SpeciesId.VICTINI, SpeciesId.MAGIKARP]); - const user = game.scene.getPlayerField()[0]; + const user = game.field.getPlayerPokemon(); vi.spyOn(user, "getAccuracyMultiplier"); game.move.select(MoveId.TACKLE, 0, BattlerIndex.ENEMY); @@ -48,7 +48,7 @@ describe("Abilities - Victory Star", () => { it("should increase the accuracy of its user's ally", async () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.VICTINI]); - const ally = game.scene.getPlayerField()[0]; + const ally = game.field.getPlayerPokemon(); vi.spyOn(ally, "getAccuracyMultiplier"); game.move.select(MoveId.TACKLE, 0, BattlerIndex.ENEMY); diff --git a/test/moves/baneful-bunker.test.ts b/test/moves/baneful-bunker.test.ts index 07fb74ce6a8..727a4bd2781 100644 --- a/test/moves/baneful-bunker.test.ts +++ b/test/moves/baneful-bunker.test.ts @@ -35,8 +35,8 @@ describe("Moves - Baneful Bunker", () => { }); function expectProtected() { - expect(game.scene.getEnemyPokemon()?.hp).toBe(game.scene.getEnemyPokemon()?.getMaxHp()); - expect(game.scene.getPlayerPokemon()?.status?.effect).toBe(StatusEffect.POISON); + expect(game.field.getEnemyPokemon().hp).toBe(game.field.getEnemyPokemon().getMaxHp()); + expect(game.field.getPlayerPokemon().status?.effect).toBe(StatusEffect.POISON); } it("should protect the user and poison attackers that make contact", async () => { diff --git a/test/moves/destiny-bond.test.ts b/test/moves/destiny-bond.test.ts index a71db6fbcda..813d5ffeeb2 100644 --- a/test/moves/destiny-bond.test.ts +++ b/test/moves/destiny-bond.test.ts @@ -1,4 +1,3 @@ -import type { EntryHazardTag } from "#data/arena-tag"; import { allMoves } from "#data/data-lists"; import { AbilityId } from "#enums/ability-id"; import { ArenaTagSide } from "#enums/arena-tag-side"; @@ -152,8 +151,8 @@ describe("Moves - Destiny Bond", () => { await game.setTurnOrder(enemyFirst); await game.phaseInterceptor.to("BerryPhase"); - expect(enemyPokemon.isFainted()).toBe(true); - expect(playerPokemon.isFainted()).toBe(false); + expect(enemyPokemon).toHaveFainted(); + expect(playerPokemon).not.toHaveFainted(); }); it("should not KO an ally", async () => { @@ -167,10 +166,10 @@ describe("Moves - Destiny Bond", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); await game.phaseInterceptor.to("BerryPhase"); - expect(enemyPokemon0.isFainted()).toBe(false); - expect(enemyPokemon1.isFainted()).toBe(false); - expect(playerPokemon0.isFainted()).toBe(true); - expect(playerPokemon1.isFainted()).toBe(false); + expect(enemyPokemon0).not.toHaveFainted(); + expect(enemyPokemon1).not.toHaveFainted(); + expect(playerPokemon0).toHaveFainted(); + expect(playerPokemon1).not.toHaveFainted(); }); it("should not cause a crash if the user is KO'd by Ceaseless Edge", async () => { @@ -187,8 +186,8 @@ describe("Moves - Destiny Bond", () => { await game.setTurnOrder(enemyFirst); await game.phaseInterceptor.to("BerryPhase"); - expect(enemyPokemon.isFainted()).toBe(true); - expect(playerPokemon.isFainted()).toBe(true); + expect(enemyPokemon).toHaveFainted(); + expect(playerPokemon).toHaveFainted(); // Ceaseless Edge spikes effect should still activate expect(game).toHaveArenaTag({ tagType: ArenaTagType.SPIKES, side: ArenaTagSide.ENEMY, layers: 1 }); @@ -198,27 +197,20 @@ describe("Moves - Destiny Bond", () => { game.override.moveset([MoveId.GRASS_PLEDGE, MoveId.WATER_PLEDGE]).battleStyle("double"); await game.classicMode.startBattle(defaultParty); - const enemyPokemon0 = game.scene.getEnemyField()[0]; - const enemyPokemon1 = game.scene.getEnemyField()[1]; - const playerPokemon0 = game.scene.getPlayerField()[0]; - const playerPokemon1 = game.scene.getPlayerField()[1]; + const [playerPokemon0, playerPokemon1, enemyPokemon0, enemyPokemon1] = game.scene.getField(); game.move.select(MoveId.GRASS_PLEDGE, 0, BattlerIndex.ENEMY); game.move.select(MoveId.WATER_PLEDGE, 1, BattlerIndex.ENEMY); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2]); await game.phaseInterceptor.to("BerryPhase"); - expect(enemyPokemon0?.isFainted()).toBe(true); - expect(enemyPokemon1?.isFainted()).toBe(false); - expect(playerPokemon0?.isFainted()).toBe(false); - expect(playerPokemon1?.isFainted()).toBe(true); + expect(enemyPokemon0).toHaveFainted(); + expect(enemyPokemon1).not.toHaveFainted(); + expect(playerPokemon0).not.toHaveFainted(); + expect(playerPokemon1).toHaveFainted(); // Pledge secondary effect should still activate - const tagAfter = game.scene.arena.getTagOnSide( - ArenaTagType.GRASS_WATER_PLEDGE, - ArenaTagSide.ENEMY, - ) as EntryHazardTag; - expect(tagAfter.tagType).toBe(ArenaTagType.GRASS_WATER_PLEDGE); + expect(game).toHaveArenaTag(ArenaTagType.GRASS_WATER_PLEDGE, ArenaTagSide.ENEMY); }); /** diff --git a/test/moves/doodle.test.ts b/test/moves/doodle.test.ts index 8b90d120b24..59895a8da4e 100644 --- a/test/moves/doodle.test.ts +++ b/test/moves/doodle.test.ts @@ -50,8 +50,9 @@ describe("Moves - Doodle", () => { game.move.select(MoveId.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); - expect(game.scene.getPlayerField()[0].getAbility().id).toBe(AbilityId.BALL_FETCH); - expect(game.scene.getPlayerField()[1].getAbility().id).toBe(AbilityId.BALL_FETCH); + const [player1, player2] = game.scene.getPlayerField(); + expect(player1.getAbility().id).toBe(AbilityId.BALL_FETCH); + expect(player2.getAbility().id).toBe(AbilityId.BALL_FETCH); }); it("should activate post-summon abilities", async () => { diff --git a/test/moves/fairy-lock.test.ts b/test/moves/fairy-lock.test.ts index eba139fee22..20b47dc7899 100644 --- a/test/moves/fairy-lock.test.ts +++ b/test/moves/fairy-lock.test.ts @@ -79,8 +79,9 @@ describe("Moves - Fairy Lock", () => { await game.toNextTurn(); - expect(game.scene.getPlayerField()[0].isTrapped()).toEqual(false); - expect(game.scene.getPlayerField()[1].isTrapped()).toEqual(false); + const [player1, player2] = game.scene.getPlayerField(); + expect(player1.isTrapped()).toEqual(false); + expect(player2.isTrapped()).toEqual(false); game.move.select(MoveId.SPLASH); game.doSwitchPokemon(2); @@ -115,8 +116,9 @@ describe("Moves - Fairy Lock", () => { await game.phaseInterceptor.to("BerryPhase"); await game.toNextTurn(); - expect(game.scene.getPlayerField()[0].species.speciesId).not.toBe(SpeciesId.KLEFKI); - expect(game.scene.getPlayerField()[1].species.speciesId).not.toBe(SpeciesId.TYRUNT); + const [player1, player2] = game.scene.getPlayerField(); + expect(player1.species.speciesId).not.toBe(SpeciesId.KLEFKI); + expect(player2.species.speciesId).not.toBe(SpeciesId.TYRUNT); }); it("If a Pokemon faints and is replaced the replacement is also trapped", async () => { @@ -138,15 +140,17 @@ describe("Moves - Fairy Lock", () => { await game.move.selectEnemyMove(MoveId.SPLASH, 1); await game.move.selectEnemyMove(MoveId.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); - expect(game.scene.getPlayerField()[0].isTrapped()).toEqual(true); - expect(game.scene.getPlayerField()[1].isTrapped()).toEqual(true); - expect(game.scene.getEnemyField()[0].isTrapped()).toEqual(true); - expect(game.scene.getEnemyField()[1].isTrapped()).toEqual(true); + + const [player1, player2, enemy1, enemy2] = game.scene.getField(); + expect(player1.isTrapped()).toBe(true); + expect(player2.isTrapped()).toBe(true); + expect(enemy1.isTrapped()).toBe(true); + expect(enemy2.isTrapped()).toBe(true); await game.toNextTurn(); - expect(game.scene.getPlayerField()[0].isTrapped()).toEqual(false); - expect(game.scene.getPlayerField()[1].isTrapped()).toEqual(false); - expect(game.scene.getEnemyField()[0].isTrapped()).toEqual(false); - expect(game.scene.getEnemyField()[1].isTrapped()).toEqual(false); + expect(player1.isTrapped()).toBe(false); + expect(player2.isTrapped()).toBe(false); + expect(enemy1.isTrapped()).toBe(false); + expect(enemy2.isTrapped()).toBe(false); }); }); diff --git a/test/moves/fell-stinger.test.ts b/test/moves/fell-stinger.test.ts index 4550cdffa12..288517c52ac 100644 --- a/test/moves/fell-stinger.test.ts +++ b/test/moves/fell-stinger.test.ts @@ -130,7 +130,7 @@ describe("Moves - Fell Stinger", () => { await game.classicMode.startBattle([SpeciesId.LEAVANNY]); const leadPokemon = game.field.getPlayerPokemon(); - const leftEnemy = game.scene.getEnemyField()[0]!; + const leftEnemy = game.field.getEnemyPokemon(); // Turn 1: set Bind, enemy splashes and does nothing game.move.select(MoveId.BIND, 0, leftEnemy.getBattlerIndex()); @@ -153,7 +153,7 @@ describe("Moves - Fell Stinger", () => { await game.classicMode.startBattle([SpeciesId.LEAVANNY]); const leadPokemon = game.field.getPlayerPokemon(); - const leftEnemy = game.scene.getEnemyField()[0]!; + const leftEnemy = game.field.getEnemyPokemon(); // Turn 1: set Leech Seed, enemy splashes and does nothing game.move.select(MoveId.LEECH_SEED, 0, leftEnemy.getBattlerIndex()); diff --git a/test/moves/first-attack-double-power.test.ts b/test/moves/first-attack-double-power.test.ts index 4172f843872..1ef756adee5 100644 --- a/test/moves/first-attack-double-power.test.ts +++ b/test/moves/first-attack-double-power.test.ts @@ -97,7 +97,7 @@ describe("Moves - Fishious Rend & Bolt Beak", () => { it.each<{ type: string; allyMove: MoveId }>([ { type: "a Dancer-induced", allyMove: MoveId.FIERY_DANCE }, { type: "an Instructed", allyMove: MoveId.INSTRUCT }, - ])("should double power if $type move is used as the target's first action that turn", async ({ allyMove }) => { + ])("should not double power if $type move is used as the target's first action that turn", async ({ allyMove }) => { game.override.battleStyle("double").enemyAbility(AbilityId.DANCER); const powerSpy = vi.spyOn(allMoves[MoveId.FISHIOUS_REND], "calculateBattlePower"); await game.classicMode.startBattle([SpeciesId.DRACOVISH, SpeciesId.ARCTOZOLT]); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index eb3eccff400..474777aa440 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -108,7 +108,7 @@ describe("Moves - Instruct", () => { await game.phaseInterceptor.to("TurnEndPhase", false); instructSuccess(shuckle, MoveId.SONIC_BOOM); - expect(game.scene.getEnemyField()[0].getInverseHp()).toBe(40); + expect(game.field.getEnemyPokemon().getInverseHp()).toBe(40); }); // TODO: Enable test case once gigaton hammer (and blood moon) are reworked @@ -251,7 +251,7 @@ describe("Moves - Instruct", () => { // fiery dance triggered dancer successfully for a total of 4 hits // Enemy level is set to a high value so that it does not faint even after all 4 hits instructSuccess(volcarona, MoveId.FIERY_DANCE); - expect(game.scene.getEnemyField()[0].turnData.attacksReceived.length).toBe(4); + expect(game.field.getEnemyPokemon().turnData.attacksReceived.length).toBe(4); }); it("should not repeat move when switching out", async () => { diff --git a/test/moves/magic-coat.test.ts b/test/moves/magic-coat.test.ts index 73ffbf2c2fb..b5ea8b196ed 100644 --- a/test/moves/magic-coat.test.ts +++ b/test/moves/magic-coat.test.ts @@ -88,7 +88,7 @@ describe("Moves - Magic Coat", () => { game.move.select(MoveId.SPLASH, 1); await game.phaseInterceptor.to("BerryPhase"); - const user = game.scene.getPlayerField()[0]; + const user = game.field.getPlayerPokemon(); expect(user.getStatStage(Stat.ATK)).toBe(-2); }); @@ -133,7 +133,7 @@ describe("Moves - Magic Coat", () => { await game.move.selectEnemyMove(MoveId.SPLASH); await game.phaseInterceptor.to("BerryPhase"); - expect(game.scene.getEnemyField()[0].getStatStage(Stat.ATK)).toBe(0); + expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(0); }); // todo while Mirror Armor is not implemented diff --git a/test/moves/metal-burst.test.ts b/test/moves/metal-burst.test.ts index 024f12d76af..a43b92ac822 100644 --- a/test/moves/metal-burst.test.ts +++ b/test/moves/metal-burst.test.ts @@ -75,6 +75,6 @@ describe("Moves - Metal Burst", () => { expect(enemy1.isFainted()).toBe(true); expect(enemy2.isFainted()).toBe(true); - expect(game.scene.getPlayerField()[0].getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + expect(game.field.getPlayerPokemon().getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); }); }); diff --git a/test/moves/mirror-move.test.ts b/test/moves/mirror-move.test.ts index 50ea4274f49..91df55751cc 100644 --- a/test/moves/mirror-move.test.ts +++ b/test/moves/mirror-move.test.ts @@ -45,7 +45,7 @@ describe("Moves - Mirror Move", () => { await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER]); await game.toNextTurn(); - expect(game.scene.getEnemyField()[0].isFullHp()).toBeFalsy(); + expect(game.field.getEnemyPokemon().isFullHp()).toBeFalsy(); }); it("should apply secondary effects of a move", async () => { diff --git a/test/moves/parting-shot.test.ts b/test/moves/parting-shot.test.ts index 2d452f3adfc..16c6696740b 100644 --- a/test/moves/parting-shot.test.ts +++ b/test/moves/parting-shot.test.ts @@ -42,7 +42,7 @@ describe("Moves - Parting Shot", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); }); test("Parting shot should fail against good as gold ability", async () => { @@ -57,7 +57,7 @@ describe("Moves - Parting Shot", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); }); // TODO: fix this bug to pass the test! @@ -103,7 +103,7 @@ describe("Moves - Parting Shot", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); }); // TODO: fix this bug to pass the test! @@ -119,7 +119,7 @@ describe("Moves - Parting Shot", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); }); // TODO: fix this bug to pass the test! @@ -135,7 +135,7 @@ describe("Moves - Parting Shot", () => { await game.phaseInterceptor.to("BerryPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0); - expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MURKROW); + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.MURKROW); }); // TODO: fix this bug to pass the test! diff --git a/test/moves/protect.test.ts b/test/moves/protect.test.ts index 9a4856d0d99..7fe29cd7568 100644 --- a/test/moves/protect.test.ts +++ b/test/moves/protect.test.ts @@ -57,7 +57,7 @@ describe("Moves - Protect", () => { ])("should have a 1/$chance success rate after $numTurns successful uses", async ({ numTurns, chance }) => { await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); // mock RNG roll to suceed unless exactly the desired chance is hit vi.spyOn(charizard, "randBattleSeedInt").mockImplementation(range => (range !== chance ? 0 : 1)); @@ -106,7 +106,7 @@ describe("Moves - Protect", () => { it("should reset fail chance on move failure", async () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); // force protect to always fail if RNG roll attempt is made vi.spyOn(charizard, "randBattleSeedInt").mockReturnValue(1); @@ -126,7 +126,7 @@ describe("Moves - Protect", () => { it("should reset fail chance on using another move", async () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); // force protect to always fail if RNG roll attempt is made vi.spyOn(charizard, "randBattleSeedInt").mockReturnValue(1); @@ -164,7 +164,7 @@ describe("Moves - Protect", () => { game.override.ability(AbilityId.PSYCHIC_SURGE); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); game.move.select(MoveId.PROTECT); await game.toNextTurn(); @@ -204,7 +204,7 @@ describe("Moves - Protect", () => { game.override.enemyMoveset([MoveId.FUTURE_SIGHT, MoveId.MIGHTY_CLEAVE, MoveId.SPORE]); await game.classicMode.startBattle([SpeciesId.AGGRON]); - const aggron = game.scene.getPlayerPokemon()!; + const aggron = game.field.getPlayerPokemon(); vi.spyOn(aggron, "randBattleSeedInt").mockReturnValue(0); // Turn 1: setup future sight diff --git a/test/moves/quick-guard.test.ts b/test/moves/quick-guard.test.ts index 173d45b412f..90cfcc3a881 100644 --- a/test/moves/quick-guard.test.ts +++ b/test/moves/quick-guard.test.ts @@ -74,7 +74,7 @@ describe("Moves - Quick Guard", () => { game.override.battleStyle("single"); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); // force protect to fail on anything >0 uses vi.spyOn(charizard, "randBattleSeedInt").mockReturnValue(1); diff --git a/test/moves/revival-blessing.test.ts b/test/moves/revival-blessing.test.ts index 8c751458ff7..5424c74e921 100644 --- a/test/moves/revival-blessing.test.ts +++ b/test/moves/revival-blessing.test.ts @@ -94,7 +94,7 @@ describe("Moves - Revival Blessing", () => { .enemyLevel(100); await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC, SpeciesId.GYARADOS]); - const feebas = game.scene.getPlayerField()[0]; + const feebas = game.field.getPlayerPokemon(); game.move.select(MoveId.SPLASH); game.move.select(MoveId.REVIVAL_BLESSING, 1); @@ -112,7 +112,7 @@ describe("Moves - Revival Blessing", () => { expect(feebas.isFainted()).toBe(false); expect(feebas.hp).toBe(toDmgValue(0.5 * feebas.getMaxHp())); - expect(game.scene.getPlayerField()[0]).toBe(feebas); + expect(game.field.getPlayerPokemon()).toBe(feebas); }); it("should not summon multiple pokemon to the same slot when reviving the enemy ally in doubles", async () => { @@ -124,7 +124,7 @@ describe("Moves - Revival Blessing", () => { .startingWave(25); // 2nd rival battle - must have 3+ pokemon await game.classicMode.startBattle([SpeciesId.ARCEUS, SpeciesId.GIRATINA]); - const enemyFainting = game.scene.getEnemyField()[0]; + const enemyFainting = game.field.getEnemyPokemon(); game.move.use(MoveId.JUDGMENT, 0, BattlerIndex.ENEMY); game.move.select(MoveId.SPLASH, 1); diff --git a/test/moves/taunt.test.ts b/test/moves/taunt.test.ts index 6ac158d3a8f..958d20667a6 100644 --- a/test/moves/taunt.test.ts +++ b/test/moves/taunt.test.ts @@ -42,7 +42,7 @@ describe("Moves - Taunt", () => { const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(MoveId.GROWL); expect(move1.result).toBe(MoveResult.SUCCESS); - expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined(); + expect(playerPokemon.getTag(BattlerTagType.TAUNT)).toBeDefined(); // Second turn, Taunt forces Struggle to occur game.move.select(MoveId.GROWL); diff --git a/test/moves/torment.test.ts b/test/moves/torment.test.ts index cdefd8ef005..9c2afa5e4b9 100644 --- a/test/moves/torment.test.ts +++ b/test/moves/torment.test.ts @@ -45,7 +45,7 @@ describe("Moves - Torment", () => { const move1 = playerPokemon.getLastXMoves(1)[0]!; expect(move1.move).toBe(MoveId.TACKLE); expect(move1.result).toBe(MoveResult.SUCCESS); - expect(playerPokemon?.getTag(BattlerTagType.TORMENT)).toBeDefined(); + expect(playerPokemon.getTag(BattlerTagType.TORMENT)).toBeDefined(); // Second turn, Torment forces Struggle to occur game.move.select(MoveId.TACKLE); diff --git a/test/moves/wide-guard.test.ts b/test/moves/wide-guard.test.ts index b45b7327265..efc8858643a 100644 --- a/test/moves/wide-guard.test.ts +++ b/test/moves/wide-guard.test.ts @@ -70,7 +70,7 @@ describe("Moves - Wide Guard", () => { await game.classicMode.startBattle([SpeciesId.CHARIZARD, SpeciesId.BLASTOISE]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); const [snorlax1, snorlax2] = game.scene.getEnemyField(); game.move.select(MoveId.WIDE_GUARD, BattlerIndex.PLAYER); @@ -86,7 +86,7 @@ describe("Moves - Wide Guard", () => { game.override.battleStyle("single"); await game.classicMode.startBattle([SpeciesId.CHARIZARD]); - const charizard = game.scene.getPlayerPokemon()!; + const charizard = game.field.getPlayerPokemon(); // force protect to fail on anything other than a guaranteed success vi.spyOn(charizard, "randBattleSeedInt").mockReturnValue(1); diff --git a/test/phases/learn-move-phase.test.ts b/test/phases/learn-move-phase.test.ts index 3181f9238dd..7ac35241b70 100644 --- a/test/phases/learn-move-phase.test.ts +++ b/test/phases/learn-move-phase.test.ts @@ -30,7 +30,7 @@ describe("Learn Move Phase", () => { game.override.moveset([MoveId.SPLASH]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]); const pokemon = game.field.getPlayerPokemon(); - const newMovePos = pokemon?.getMoveset().length; + const newMovePos = pokemon.getMoveset().length; game.move.select(MoveId.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to(LearnMovePhase); @@ -38,7 +38,7 @@ describe("Learn Move Phase", () => { const levelReq = levelMove[0]; const levelMoveId = levelMove[1]; expect(pokemon.level).toBeGreaterThanOrEqual(levelReq); - expect(pokemon?.moveset[newMovePos]?.moveId).toBe(levelMoveId); + expect(pokemon.moveset[newMovePos]?.moveId).toBe(levelMoveId); }); it("If a pokemon has 4 move slots filled, the chosen move will be deleted and replaced", async () => { From af2bad96f19baca856665f4b0d0d7f8660f05ba0 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:02:31 -0600 Subject: [PATCH 046/101] [Dev] Add lefthook command to update packages on checkout/merge (#6820) * [Dev] Add lefthook command to update packages on checkout/merge * Modify post-checkout to only run on branch checkout Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- lefthook.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lefthook.yml b/lefthook.yml index 9b4be96056f..e66d56f6562 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -18,4 +18,13 @@ pre-commit: post-merge: commands: update-submodules: - run: pnpm update-submodules \ No newline at end of file + run: pnpm update-submodules + update-packages: + run: pnpm i + +post-checkout: + commands: + update-packages: + # cf https://git-scm.com/docs/githooks#_post_checkout: + # The 3rd argument is 1 for branch checkouts and 0 for file checkouts. + run: if test {3} -eq "1"; then pnpm i; fi From f498a8395158fe6ded4450ac30afce3571b0cd02 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Wed, 3 Dec 2025 22:27:11 -0500 Subject: [PATCH 047/101] [Github] Improve test workflow to merge blob reports from all shards (#6687) * [Github] Improved test workflow to merge blobs into 1 big test * fixed workflow not checking out + test failure behavior * fixed the thingy to still merge reports when a shard fails * Fixed workflow being skipped * Fixed inverted conditional * Removed failing test demo * updated workflow to hopefully not clone entire repo * Add .nvmrc to sparse-checkout configuration * fail workflow output test * Include custom reporters, vite/vitest config in sparse checkout selection * Add src/plugins/vite to sparse checkout * Revert to checking out full repo and remove change to instruct test Unfortunately using sparse checkout won't work * Remove redundant comment * Try not using recursive submodules when cloning locales * re-addede recurse submoudules * actually disabled it * Remove extra newline --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> --- .github/workflows/test-shard-template.yml | 28 ++++++++++------ .github/workflows/tests.yml | 39 +++++++++++++++++++++-- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test-shard-template.yml b/.github/workflows/test-shard-template.yml index 6f4728863b4..a69e92ae147 100644 --- a/.github/workflows/test-shard-template.yml +++ b/.github/workflows/test-shard-template.yml @@ -3,27 +3,19 @@ name: Test Template on: workflow_call: inputs: - project: - required: true - type: string shard: required: true type: number totalShards: required: true type: number - skip: - required: true - type: boolean - default: false jobs: test: - # We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented + # We can't use dynamically named jobs until https://github.com/orgs/community/discussions/13261 is implemented name: Shard timeout-minutes: 10 runs-on: ubuntu-latest - if: ${{ !inputs.skip }} steps: - name: Check out Git repository uses: actions/checkout@v4.2.2 @@ -43,4 +35,20 @@ jobs: run: pnpm i - name: Run tests - run: pnpm test:silent --shard=${{ inputs.shard }}/${{ inputs.totalShards }} + run: > + pnpm test:silent + --shard=${{ inputs.shard }}/${{ inputs.totalShards }} + --reporter=blob + --outputFile=test-results/blob-${{ inputs.shard }}.json || true + + # NB: This CANNOT be made into a job output due to not being extractable from the matrix: + # https://github.com/orgs/community/discussions/17245 + - name: Upload test result blobs + uses: actions/upload-artifact@v4 + with: + name: shard-${{ inputs.shard }}-blob + path: test-results/blob-${{ inputs.shard }}.json + retention-days: 1 # don't need to keep for very long since they get merged afterwards + overwrite: true + if-no-files-found: error + if: ${{ !cancelled() }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e1d6e3af60e..a94d3c5a504 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: outputs: all: ${{ steps.filter.outputs.all }} steps: - - name: checkout + - name: Checkout GitHub repository uses: actions/checkout@v4 with: sparse-checkout: | @@ -41,6 +41,7 @@ jobs: run-tests: name: Run Tests needs: check-path-change-filter + if: ${{ needs.check-path-change-filter.outputs.all == 'true'}} strategy: # don't stop upon 1 shard failing fail-fast: false @@ -48,7 +49,39 @@ jobs: shard: [1, 2, 3, 4, 5] uses: ./.github/workflows/test-shard-template.yml with: - project: main shard: ${{ matrix.shard }} totalShards: 5 - skip: ${{ needs.check-path-change-filter.outputs.all != 'true'}} + + check-results: + name: Check Test Results + timeout-minutes: 8 + needs: run-tests + runs-on: ubuntu-latest + if: ${{ needs.run-tests.result != 'skipped' && needs.run-tests.result != 'cancelled' }} + steps: + - name: Check out Git repository + uses: actions/checkout@v4.2.2 + with: + submodules: "recursive" + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + + - name: Install Packages + run: pnpm i + + - name: Download blob artifacts + uses: actions/download-artifact@v4 + with: + pattern: shard-?-blob + path: test-results + merge-multiple: true + + - name: Merge blobs + run: pnpm test:silent --merge-reports=test-results From e13ea6e17d7b90ab5235dc412404e5212607ca66 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Wed, 3 Dec 2025 22:38:36 -0500 Subject: [PATCH 048/101] [Docs] Minor typofixes/addendums to `.md` files (#6817) * [Docs] Minor typofixes to `.md` files * Fixed footnotes * Update linting.md to remove comment * Added footnote for validation workflow --- CONTRIBUTING.md | 7 ++++--- docs/linting.md | 17 +++++++---------- docs/localization.md | 26 ++++++++++++++++---------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 505c29d091a..208dadfd961 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,9 +26,9 @@ If you use any external code, please make sure to follow its licensing informati ## 🛠️ Development Basics -PokéRogue is built with [Typescript](https://www.typescriptlang.org/docs/handbook/intro.html), using the [Phaser](https://github.com/phaserjs/phaser) game framework. +PokéRogue is built with [TypeScript](https://www.typescriptlang.org/docs/handbook/intro.html), using the [Phaser](https://github.com/phaserjs/phaser) game framework. -If you have the motivation and experience with Typescript/Javascript (or are willing to learn), you can contribute by forking the repository and making pull requests with contributions. +If you have the motivation and experience with TypeScript/JavaScript (or are willing to learn), you can contribute by forking the repository and making pull requests with contributions. ## 💻 Environment Setup @@ -97,7 +97,8 @@ Notable topics include: - [Commenting your code](./docs/comments.md) - [Linting & Formatting](./docs/linting.md) - [Localization](./docs/localization.md) -- [Enemy AI move selection](./docs/enemy-ai.md) +- [Enemy AI move selection](./docs/enemy-ai.md) +- [Running with Podman](./docs/podman.md) Again, if you have unanswered questions please feel free to ask! diff --git a/docs/linting.md b/docs/linting.md index 1b3c80d0a33..e0cd67df792 100644 --- a/docs/linting.md +++ b/docs/linting.md @@ -18,7 +18,7 @@ For the most part, Biome attempts to stay "out of your hair", letting you write On the other hand, if Biome complains about a piece of code, **there's probably a good reason why**. Disable comments should be used sparingly or when readabilty demands it - your first instinct should be to fix the code in question, not disable the rule. ## Editor Integration -Biome has integration with many popular code editors. See [these](https://biomejs.dev/guides/editors/first-party-extensions/) [pages](https://biomejs.dev/guides/editors/third-party-extensions/) for information about enabling Biome in your editor of choice. +Biome has integrations with many popular code editors. See [these](https://biomejs.dev/guides/editors/first-party-extensions/) [pages](https://biomejs.dev/guides/editors/third-party-extensions/) for information about enabling Biome in your editor of choice. ## Automated Runs Generally speaking, most users shouldn't need to run Biome directly; in addition to editor integration, a [pre-commit hook](../lefthook.yml) will automatically format and lint all staged files before each commit. @@ -32,7 +32,7 @@ We also have a [Github Action](../.github/workflows/linting.yml) to verify code These are effectively the same commands as run by Lefthook, merely on a project-wide scale. ## Running Biome via CLI -To run you Biome on your files manually, you have 2 main options: +To run Biome on your files manually, you have 2 main options: 1. Run the scripts included in `package.json` (`pnpm biome` and `pnpm biome:all`). \ These have sensible defaults for command-line options, but do not allow altering certain flags (as some cannot be specified twice in the same command) @@ -50,14 +50,11 @@ A full list of flags and options can be found on [their website](https://biomejs ## Linting Rules -We primarily use Biome's [recommended ruleset](https://biomejs.dev/linter/rules/) for linting JS/TS files, with some customizations to better suit our project's needs[^1]. +We primarily use Biome's [recommended ruleset](https://biomejs.dev/linter/rules/) for linting JS/TS files, with some customizations to better suit our project's needs. \ +A complete list of rules can be found in the [`biome.jsonc`](../biome.jsonc) file in the project root. Most rules are accompanied by comments explaining the reasons for their inclusion/exclusion. -Some things to consider: - -- We have disabled rules that prioritize style over performance, such as `useTemplate`. -- Some rules are currently marked as warnings (`warn`) to allow for gradual refactoring without blocking development. **Do not write new code that triggers these rules!** -- The linter is configured to ignore specific files and folders (such as excessively large files or ones in need of refactoring) to improve performance and focus on actionable areas. +> [!IMPORTANT] +> Certain lint rules may be marked as `info` or `warn` to allow for gradual refactoring without blocking development. +> **Do not write new code that triggers these rules!** Any questions about linting rules can be brought up in the `#dev-corner` channel in the community Discord. - -[^1]: A complete list of rules can be found in the [`biome.jsonc`](../biome.jsonc) file in the project root. Many rules are accompanied by comments explaining the reasons for their inclusion (or lack thereof). diff --git a/docs/localization.md b/docs/localization.md index 0cfd4b05057..8d86ff2c35a 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -28,7 +28,7 @@ This repository is integrated into the main one as a [git submodule](https://git In essence, a submodule is a way for one repository (i.e. `pokerogue`) to use another repository (i.e. `pokerogue-locales`) internally. The parent repo (the "superproject") houses a cloned version of the 2nd repository (the "submodule") inside it, making locales effectively a "repository within a repository", so to speak. ->[!TIP] +> [!TIP] > Many popular IDEs have integrated `git` support with special handling around submodules: > > ![Image showing Visual Studio Code's `git` integration in the File Explorer. A blue "S" in the top right hand corner indicates the `locales` folder is a submodule.](https://github.com/user-attachments/assets/00674c3f-72ee-42f7-8b09-4008d466b263 "What the `locales` submodule looks like in VS Code's File Explorer") @@ -97,17 +97,23 @@ If this feature requires new text, the text should be integrated into the code w - For any feature pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), it's best practice to include a source link for any added text. \ [Poké Corpus](https://abcboy101.github.io/poke-corpus/) is a great resource for finding text from the mainline games; otherwise, a video/picture showing the text being displayed should suffice. - You should also [notify the current Head of Translation](#notifying-translation) to ensure a fast response. -3. Your locales should use the following format: - - File names should be in `kebab-case`. Example: `trainer-names.json` - - Key names should be in `camelCase`. Example: `aceTrainer` - - If you make use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context), you need to use `snake_case` for the context key. Example: `aceTrainer_male` -4. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes). -5. The Translation Team will approve the locales PR (after corrections, if necessary), then merge it into `pokerogue-locales`. -6. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment. +3. At this point, you may begin [testing locales integration in your main PR](#documenting-locales-changes). +4. The Translation Team will approve the locales PR (after corrections, if necessary), then merge it into `pokerogue-locales`. +5. The Dev Team will approve your main PR for your feature, then merge it into PokéRogue's beta environment. [^2]: For those wondering, the reason for choosing English specifically is due to it being the master language set in Pontoon (the program used by the Translation Team to perform locale updates). If a key is present in any language _except_ the master language, it won't appear anywhere else in the translation tool, rendering missing English keys quite a hassle. +> [!IMPORTANT] +> The Dev and Translation teams have strict requirements for ensuring consistency of newly added locales entries. +> PRs failing these requirements **will not be mergeable into `locales`**! +> - File names should be in `kebab-case`. Example: `trainer-names.json` +> - Key names should be in `camelCase`. Example: `aceTrainer` +> - Keys making use of i18next's inbuilt [context support](https://www.i18next.com/translation-function/context) must use `snake_case` for the context extension[^3]. Example: `aceTrainer_male` + +[^3]: If your PR introduces a new context extension not already used in the codebase, the validation workflow will be unable to detect it and flag it as invalid. \ +To fix this, update the [`i18nextKeyExtensions`](https://github.com/pagefaultgames/pokerogue-locales/blob/main/.github/scripts/locales-format-checker/constants.js#L30) array with the new entries. + ### Requirements for Modifying Translated Text PRs that modify existing text have different risks with respect to coordination between development and translation, so their requirements are slightly different: @@ -130,9 +136,9 @@ The basic procedure is roughly as follows: ``` 2. Set some of the [in-game overrides](../CONTRIBUTING.md#1---manual-testing) inside `overrides.ts` to values corresponding to the interactions being tested. 3. Start a local dev server (`pnpm start:dev`) and open localhost in your browser. -4. Take screenshots or record a video of the locales changes being displayed in-game using the software of your choice[^2]. +4. Take screenshots or record a video of the locales changes being displayed in-game using the software of your choice[^3]. -[^2]: For those lacking a screen capture device, [OBS Studio](https://obsproject.com) is a popular open-source option. +[^3]: For those lacking a screen capture device, [OBS Studio](https://obsproject.com) is a popular open-source option. > [!NOTE] > For those aiming to film their changes, bear in mind that GitHub has a hard **10mB limit** on uploaded media content. From 6af869cff12d0209917aeb3a8a44ebef946f8c97 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 4 Dec 2025 04:20:21 -0500 Subject: [PATCH 049/101] Merge pull request #6819 from Bertie690/set-position-relative [Misc] Dedupe + move phaser method stubs into separate file --- src/extensions.ts | 66 +++++++++++++++++++++ src/init/init.ts | 2 + src/main.ts | 31 ---------- src/typings/phaser/index.d.ts | 32 ---------- test/setup/vitest.setup.ts | 2 +- test/test-utils/test-file-initialization.ts | 22 +------ typedoc.config.js | 1 + 7 files changed, 72 insertions(+), 84 deletions(-) create mode 100644 src/extensions.ts diff --git a/src/extensions.ts b/src/extensions.ts new file mode 100644 index 00000000000..4ebcf3974da --- /dev/null +++ b/src/extensions.ts @@ -0,0 +1,66 @@ +import "phaser"; + +//#region Methods/Interfaces + +/** + * Interface representing an object that can be passed to {@linkcode setPositionRelative}. + */ +interface GuideObject + extends Pick, + Pick, + Pick {} + +/** + * Set this object's position relative to another object with a given offset. + * @param guideObject - The object to base this object's position off of; must have defined + * x/y co-ordinates, an origin and width/height + * @param x - The X-position to set, relative to `guideObject`'s `x` value + * @param y - The Y-position to set, relative to `guideObject`'s `y` value + * @returns `this` + */ +function setPositionRelative( + this: T, + guideObject: GuideObject, + x: number, + y: number, +): T { + const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); + const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); + return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); +} + +Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative; +Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative; + +//#endregion + +//#region Declaration Merging + +interface hasSetPositionRelative { + /** + * Set this object's position relative to another object with a given offset. + * @param guideObject - The object to base this object's position off of; must have defined + * x/y co-ordinates, an origin and width/height + * @param x - The X-position to set, relative to `guideObject`'s `x` value + * @param y - The Y-position to set, relative to `guideObject`'s `y` value + * @returns `this` + */ + setPositionRelative: typeof setPositionRelative; +} + +declare module "phaser" { + namespace GameObjects { + interface Container extends hasSetPositionRelative {} + interface Sprite extends hasSetPositionRelative {} + interface Image extends hasSetPositionRelative {} + interface NineSlice extends hasSetPositionRelative {} + interface Text extends hasSetPositionRelative {} + interface Rectangle extends hasSetPositionRelative {} + } +} + +//#endregion diff --git a/src/init/init.ts b/src/init/init.ts index b80a35022b9..bfadbe27ad0 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -1,3 +1,5 @@ +import "#app/extensions"; // Setup Phaser extension methods/etc + import { initAbilities } from "#abilities/ability"; import { initBiomes } from "#balance/init-biomes"; import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions"; diff --git a/src/main.ts b/src/main.ts index 9516fe9e6ab..96540e74587 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,37 +30,6 @@ window.addEventListener("unhandledrejection", event => { //alert(errorString); }); -interface GuideObject - extends Pick, - Pick, - Pick {} - -/** - * Set this object's position relative to another object with a given offset. - * @param guideObject - The object to base this object's position off of; must have defined - * x/y co-ordinates, an origin and width/height - * @param x - The X-position to set, relative to `guideObject`'s `x` value - * @param y - The Y-position to set, relative to `guideObject`'s `y` value - * @returns `this` - */ -function setPositionRelative( - this: T, - guideObject: GuideObject, - x: number, - y: number, -): T { - const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); - const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); -} - -Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; -Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative; -Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative; -Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative; -Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative; -Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative; - async function startGame(gameManifest?: Record): Promise { await initI18n(); const LoadingScene = (await import("./loading-scene")).LoadingScene; diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index aa4d257f9e0..59ea27ba899 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -1,38 +1,6 @@ import "phaser"; -/** - * Interface representing an object that can be passed to {@linkcode setPositionRelative}. - * @interface - */ -type GuideObject = Pick & - Pick & - Pick; - -type setPositionRelative = - /** - * Set this object's position relative to another object with a given offset. - * @param guideObject - The object to base this object's position off of; must have defined - * x/y co-ordinates, an origin and width/height - * @param x - The X-position to set, relative to `guideObject`'s `x` value - * @param y - The Y-position to set, relative to `guideObject`'s `y` value - * @returns `this` - */ - (this: T, guideObject: GuideObject, x: number, y: number) => T; - -interface hasSetPositionRelative { - setPositionRelative: setPositionRelative; -} - declare module "phaser" { - namespace GameObjects { - interface Container extends hasSetPositionRelative {} - interface Sprite extends hasSetPositionRelative {} - interface Image extends hasSetPositionRelative {} - interface NineSlice extends hasSetPositionRelative {} - interface Text extends hasSetPositionRelative {} - interface Rectangle extends hasSetPositionRelative {} - } - namespace Math { interface RandomDataGenerator { pick(array: ArrayLike): T; diff --git a/test/setup/vitest.setup.ts b/test/setup/vitest.setup.ts index 0bfbb6613f7..4bd194c7be4 100644 --- a/test/setup/vitest.setup.ts +++ b/test/setup/vitest.setup.ts @@ -42,7 +42,7 @@ vi.mock(import("i18next"), async importOriginal => { } return HttpResponse.json(json); } catch (err) { - console.log(`Failed to load locale ${filename}!`, err); + console.error(`Failed to load locale ${filename}\n`, err); return HttpResponse.json({}); } }), diff --git a/test/test-utils/test-file-initialization.ts b/test/test-utils/test-file-initialization.ts index c172e2d1da8..6a765dfa1f2 100644 --- a/test/test-utils/test-file-initialization.ts +++ b/test/test-utils/test-file-initialization.ts @@ -42,7 +42,8 @@ function initTestFile(): void { * @todo Investigate why this resets on new test suite start */ function setupStubs(): void { - Object.defineProperties(global, { + // TODO: Make this type safe + Object.defineProperties(globalThis, { localStorage: { value: mockLocalStorage(), }, @@ -76,25 +77,6 @@ function setupStubs(): void { }; navigator.getGamepads = () => []; setCookie(SESSION_ID_COOKIE_NAME, "fake_token"); - - /** - * Sets this object's position relative to another object with a given offset - * @param guideObject - The {@linkcode Phaser.GameObjects.GameObject} to base the position off of - * @param x - The relative x position - * @param y - The relative y position - */ - const setPositionRelative = function (guideObject: any, x: number, y: number): any { - const offsetX = guideObject.width * (-0.5 + (0.5 - guideObject.originX)); - const offsetY = guideObject.height * (-0.5 + (0.5 - guideObject.originY)); - return this.setPosition(guideObject.x + offsetX + x, guideObject.y + offsetY + y); - }; - - Phaser.GameObjects.Container.prototype.setPositionRelative = setPositionRelative; - Phaser.GameObjects.Sprite.prototype.setPositionRelative = setPositionRelative; - Phaser.GameObjects.Image.prototype.setPositionRelative = setPositionRelative; - Phaser.GameObjects.NineSlice.prototype.setPositionRelative = setPositionRelative; - Phaser.GameObjects.Text.prototype.setPositionRelative = setPositionRelative; - Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative; HTMLCanvasElement.prototype.getContext = () => mockContext; } diff --git a/typedoc.config.js b/typedoc.config.js index 09d681a4bde..bf1f0860d21 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -16,6 +16,7 @@ const config = { entryPointStrategy: "expand", exclude: [ "src/polyfills.ts", + "src/extensions.ts", "src/vite.env.d.ts", "**/*+.test.ts", "test/test-utils/setup", From ebdbe2e118177d05b80199e55ae3a9a61e8c46c1 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:26:21 -0600 Subject: [PATCH 050/101] [Misc] Bump Assets commit (#6824) --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 9587d57f2d8..5481af203f1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9587d57f2d8fbd48e7fc023e9c02c494eea3cf75 +Subproject commit 5481af203f1de7da88d803f68b01e9ff776cd851 From d8e8dbf8c24864d5a053158cb5fd42d7f2ad219c Mon Sep 17 00:00:00 2001 From: Lugiad Date: Thu, 4 Dec 2025 20:48:49 +0100 Subject: [PATCH 051/101] Merge pull request #6663 from Adri1/norwegian-bokmal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [i18n] Norwegian Bokmål initial commit --- src/plugins/i18n.ts | 1 + src/system/settings/settings-language.ts | 4 ++++ src/ui/handlers/pokedex-page-ui-handler.ts | 4 ++++ src/ui/handlers/starter-select-ui-handler.ts | 4 ++++ src/ui/settings/settings-display-ui-handler.ts | 6 ++++++ src/utils/common.ts | 1 + 6 files changed, 20 insertions(+) diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index acb7be75f7a..4e5cb214091 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -207,6 +207,7 @@ export async function initI18n(): Promise { "ro", "ru", "tl", + "nb-NO", ], backend: { loadPath(lng: string, [ns]: string[]) { diff --git a/src/system/settings/settings-language.ts b/src/system/settings/settings-language.ts index 1ab71117604..5235defab22 100644 --- a/src/system/settings/settings-language.ts +++ b/src/system/settings/settings-language.ts @@ -86,6 +86,10 @@ export const languageOptions = [ label: "Dansk (Needs Help)", handler: () => changeLocaleHandler("da"), }, + { + label: "Norsk bokmål (Needs Help)", + handler: () => changeLocaleHandler("nb-NO"), + }, { label: "Română (Needs Help)", handler: () => changeLocaleHandler("ro"), diff --git a/src/ui/handlers/pokedex-page-ui-handler.ts b/src/ui/handlers/pokedex-page-ui-handler.ts index be11a752541..cb9ed55bbd1 100644 --- a/src/ui/handlers/pokedex-page-ui-handler.ts +++ b/src/ui/handlers/pokedex-page-ui-handler.ts @@ -147,6 +147,10 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 26, }, + "nb-NO": { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, }; const valueReductionMax = 2; diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index e57173cf7f0..5d49fb3c57a 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -171,6 +171,10 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoTextSize: "56px", instructionTextSize: "38px", }, + "nb-NO": { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, }; const valueReductionMax = 2; diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index d513ed8fd11..df39e4a6d0c 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -111,6 +111,12 @@ export class SettingsDisplayUiHandler extends AbstractSettingsUiHandler { label: "Dansk (Needs Help)", }; break; + case "nb-NO": + this.settings[languageIndex].options[0] = { + value: "Norsk bokmål", + label: "Norsk bokmål (Needs Help)", + }; + break; case "ro": this.settings[languageIndex].options[0] = { value: "Română", diff --git a/src/utils/common.ts b/src/utils/common.ts index 056358dd87c..ee93b484625 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -414,6 +414,7 @@ export function hasAllLocalizedSprites(lang?: string): boolean { case "ca": case "ru": case "tl": + case "nb-NO": return true; default: return false; From 3302d231370d0777e8e07192996ef49edc1ea4ae Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:22:13 -0500 Subject: [PATCH 052/101] Merge pull request #6826 from Bertie690/play-tween-typing [Dev] Update typing for `playTween` utility function --- package.json | 1 + pnpm-lock.yaml | 11 +++++++---- src/utils/anim-utils.ts | 21 +++++++++------------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 4798aa66b2f..ab72d9f0538 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "lefthook": "^2.0.4", "msw": "^2.12.3", "phaser3spectorjs": "^0.0.8", + "type-fest": "^5.3.0", "typedoc": "^0.28.14", "typedoc-github-theme": "^0.3.1", "typedoc-plugin-coverage": "^4.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1607bfce611..e8889db4816 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: phaser3spectorjs: specifier: ^0.0.8 version: 0.0.8 + type-fest: + specifier: ^5.3.0 + version: 5.3.0 typedoc: specifier: ^0.28.14 version: 0.28.14(typescript@5.9.3) @@ -1779,8 +1782,8 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - type-fest@5.2.0: - resolution: {integrity: sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==} + type-fest@5.3.0: + resolution: {integrity: sha512-d9CwU93nN0IA1QL+GSNDdwLAu1Ew5ZjTwupvedwg3WdfoH6pIDvYQ2hV0Uc2nKBLPq7NB5apCx57MLS5qlmO5g==} engines: {node: '>=20'} typedoc-github-theme@0.3.1: @@ -3288,7 +3291,7 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 5.2.0 + type-fest: 5.3.0 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -3561,7 +3564,7 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - type-fest@5.2.0: + type-fest@5.3.0: dependencies: tagged-tag: 1.0.0 diff --git a/src/utils/anim-utils.ts b/src/utils/anim-utils.ts index f1a06552d38..d87ab887607 100644 --- a/src/utils/anim-utils.ts +++ b/src/utils/anim-utils.ts @@ -1,22 +1,19 @@ import { globalScene } from "#app/global-scene"; import type { SceneBase } from "#app/scene-base"; +import type { OmitIndexSignature, PickIndexSignature } from "type-fest"; + +type OmitWithoutIndex = PickIndexSignature & Omit, K>; + +interface PlayTweenConfig + extends OmitWithoutIndex {} /** - * Plays a Tween animation, resolving once the animation completes. + * Play a Tween animation and wait for its animation to complete. * @param config - The config for a single Tween - * @param scene - The {@linkcode SceneBase} on which the Tween plays; default {@linkcode globalScene} + * @param scene - (Default {@linkcode globalScene}) The {@linkcode SceneBase} on which the Tween plays * @returns A Promise that resolves once the Tween has been played. - * - * @privateRemarks - * The `config` input should not include an `onComplete` field as that callback is - * used to resolve the Promise containing the Tween animation. - * However, `config`'s type cannot be changed to something like `Omit` - * due to how the type for `TweenBuilderConfig` is defined. */ -export async function playTween( - config: Phaser.Types.Tweens.TweenBuilderConfig, - scene: SceneBase = globalScene, -): Promise { +export async function playTween(config: PlayTweenConfig, scene: SceneBase = globalScene): Promise { await new Promise(resolve => scene.tweens.add({ ...config, From 516bc5dce1f95285c1cba3b8b69476187687fede Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:59:20 -0600 Subject: [PATCH 053/101] [GitHub] Update and optimize workflows (#6825) --- .github/REUSE.toml | 1 + .github/actions/setup-deps/action.yml | 17 ++++++++++++++++ .github/workflows/create-release.yml | 2 +- .github/workflows/deploy-beta.yml | 12 ++---------- .github/workflows/deploy.yml | 12 ++---------- .github/workflows/github-pages.yml | 8 ++++---- .github/workflows/linting.yml | 20 ++----------------- .github/workflows/test-shard-template.yml | 16 +++------------ .github/workflows/tests.yml | 24 +++++++++-------------- package.json | 1 + vite.config.ts | 12 ++++++++---- 11 files changed, 50 insertions(+), 75 deletions(-) create mode 100644 .github/actions/setup-deps/action.yml diff --git a/.github/REUSE.toml b/.github/REUSE.toml index 81fe35d2396..9f6ace7bc53 100644 --- a/.github/REUSE.toml +++ b/.github/REUSE.toml @@ -9,6 +9,7 @@ version = 1 [[annotations]] path = [ "workflows/**/*.yml", + "actions/**/*.yml", "ISSUE_TEMPLATE/**/*.yml", "FUNDING.yml", "CODEOWNERS", diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml new file mode 100644 index 00000000000..50f48ec2205 --- /dev/null +++ b/.github/actions/setup-deps/action.yml @@ -0,0 +1,17 @@ +name: "Setup pnpm and Node.js" +description: "Setup pnpm and Node.js environment for workflows. Requires the repo to be cloned to at least have .nvmrc and package.json" +runs: + using: "composite" + steps: + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version-file: ".nvmrc" + cache: "pnpm" + + - name: Install dependencies + run: pnpm i + shell: bash diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index fea857355a0..5b1b776e760 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -45,7 +45,7 @@ jobs: private-key: ${{ secrets.PAGEFAULT_APP_PRIVATE_KEY }} - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: "recursive" # Always base off of beta branch, regardless of the branch the workflow was triggered from. diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml index 85ec8119eaa..36e9afd45cf 100644 --- a/.github/workflows/deploy-beta.yml +++ b/.github/workflows/deploy-beta.yml @@ -15,20 +15,12 @@ jobs: timeout-minutes: 10 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: "recursive" ref: ${{ vars.BETA_DEPLOY_BRANCH || 'beta'}} - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - - - name: Install dependencies - run: pnpm i + - uses: ./.github/actions/setup-deps - name: Build run: pnpm build:beta diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1f2c1259dd1..a218e2f8e7a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,19 +14,11 @@ jobs: timeout-minutes: 10 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: 'recursive' - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - - name: Install dependencies - run: pnpm i + - uses: ./.github/actions/setup-deps - name: Build run: pnpm build diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index 0e9f6bba0cd..9f84e675fb3 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository for Typedoc - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: pokerogue_docs @@ -42,7 +42,7 @@ jobs: rm -rf assets - name: Checkout asset submodule - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: 'pagefaultgames/pokerogue-assets' ref: ${{ steps.asset-submodule-ref.asset_ref }} @@ -59,13 +59,13 @@ jobs: version: 10 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: "pokerogue_docs/.nvmrc" - name: Checkout repository for Github Pages if: github.event_name == 'push' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: pokerogue_gh ref: gh-pages diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 9f896a11f2a..cdbf5070043 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -25,21 +25,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: "recursive" - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - cache: "pnpm" - - - name: Install Node modules - run: pnpm i + - uses: ./.github/actions/setup-deps # Lint files with Biome-Lint - https://biomejs.dev/linter/ - name: Lint with Biome @@ -64,13 +55,6 @@ jobs: id: typecheck-scripts if: ${{ !cancelled() }} - # NOTE: These steps *must* be ran last for the moment due to deleting files in `assets/`. - # Some asset files do not yet have full licensing information, and thus must be removed - # before checking for REUSE compliance - - name: Prepare for REUSE compliance - run: rm -rf assets/* LICENSES/LicenseRef-* - if: ${{ !cancelled() }} - - name: Check for REUSE compliance id: reuse-lint uses: fsfe/reuse-action@v5 diff --git a/.github/workflows/test-shard-template.yml b/.github/workflows/test-shard-template.yml index a69e92ae147..1c98521e5e5 100644 --- a/.github/workflows/test-shard-template.yml +++ b/.github/workflows/test-shard-template.yml @@ -18,22 +18,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out Git repository - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6 with: submodules: "recursive" - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - cache: "pnpm" - - - name: Install Node.js dependencies - run: pnpm i - + - uses: ./.github/actions/setup-deps + - name: Run tests run: > pnpm test:silent diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a94d3c5a504..c4d3a655e5f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: all: ${{ steps.filter.outputs.all }} steps: - name: Checkout GitHub repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: sparse-checkout: | .github/test-filters.yml @@ -60,28 +60,22 @@ jobs: if: ${{ needs.run-tests.result != 'skipped' && needs.run-tests.result != 'cancelled' }} steps: - name: Check out Git repository - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6 with: - submodules: "recursive" + sparse-checkout: | + .github/actions/setup-deps/action.yml + test/setup + test/test-utils/reporters/custom-default-reporter.ts - - name: Install pnpm - uses: pnpm/action-setup@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - cache: "pnpm" - - - name: Install Packages - run: pnpm i + - uses: ./.github/actions/setup-deps - name: Download blob artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: pattern: shard-?-blob path: test-results merge-multiple: true - name: Merge blobs - run: pnpm test:silent --merge-reports=test-results + run: pnpm test:merge-reports diff --git a/package.json b/package.json index ab72d9f0538..b0fada52c2b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "test:cov": "vitest run --coverage --no-isolate", "test:watch": "vitest watch --coverage --no-isolate", "test:silent": "vitest run --silent='passed-only' --no-isolate", + "test:merge-reports": "MERGE_REPORTS=1 vitest run --merge-reports=test-results --silent=passed-only --configLoader runner", "test:create": "node scripts/create-test/create-test.js", "eggMoves:parse": "node scripts/parse-egg-moves/main.js", "scrape-trainers": "node scripts/scrape-trainer-names/main.js", diff --git a/vite.config.ts b/vite.config.ts index 569c3786b79..51dce9b5054 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,11 +6,15 @@ import { defineConfig, loadEnv, type Rollup, type UserConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; -import { LocaleNamespace } from "./src/plugins/vite/namespaces-i18n-plugin"; -import { minifyJsonPlugin } from "./src/plugins/vite/vite-minify-json-plugin"; export const defaultConfig: UserConfig = { - plugins: [tsconfigPaths(), minifyJsonPlugin(["images", "battle-anims"], true), LocaleNamespace()], + plugins: process.env.MERGE_REPORTS + ? [] + : [ + tsconfigPaths(), + require("./src/plugins/vite/vite-minify-json-plugin").minifyJsonPlugin(["images", "battle-anims"], true), + require("./src/plugins/vite/namespaces-i18n-plugin").LocaleNamespace(), + ], clearScreen: false, appType: "mpa", build: { @@ -41,7 +45,7 @@ export default defineConfig(({ mode, command }) => { keepNames: true, }, server: { - port: !Number.isNaN(envPort) ? envPort : 8000, + port: Number.isNaN(envPort) ? 8000 : envPort, }, }; }); From 3d23a2b28af047f099b5a9f61242f143dd2f5b5b Mon Sep 17 00:00:00 2001 From: Austin Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:34:08 -0500 Subject: [PATCH 054/101] [Balance] Allow male Ralts as a starter in Fairy challenge (#6827) Allow male Ralts as a starter in Fairy challenge --- src/data/challenge.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 22d48432376..739ca336f01 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -790,22 +790,14 @@ export class SingleTypeChallenge extends Challenge { applyStarterSelectModify(speciesId: SpeciesId, dexEntry: DexEntry, _starterDataEntry: StarterDataEntry): boolean { const type = this.value - 1; - if (speciesId === SpeciesId.RALTS) { - if (type === PokemonType.FIGHTING) { - dexEntry.caughtAttr &= ~DexAttr.FEMALE; - } - if (type === PokemonType.FAIRY) { - dexEntry.caughtAttr &= ~DexAttr.MALE; - } - } - if (speciesId === SpeciesId.SNORUNT && type === PokemonType.GHOST) { + if (speciesId === SpeciesId.RALTS && type === PokemonType.FIGHTING) { + dexEntry.caughtAttr &= ~DexAttr.FEMALE; + } else if (speciesId === SpeciesId.SNORUNT && type === PokemonType.GHOST) { dexEntry.caughtAttr &= ~DexAttr.MALE; - } - if (speciesId === SpeciesId.BURMY) { + } else if (speciesId === SpeciesId.BURMY) { if (type === PokemonType.FLYING) { dexEntry.caughtAttr &= ~DexAttr.FEMALE; - } - if ([PokemonType.GRASS, PokemonType.GROUND, PokemonType.STEEL].includes(type)) { + } else if ([PokemonType.GRASS, PokemonType.GROUND, PokemonType.STEEL].includes(type)) { dexEntry.caughtAttr &= ~DexAttr.MALE; } } From 15f668e1b59931cdcbe88a92667a11c2581c3b70 Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:45:44 +0100 Subject: [PATCH 055/101] [UI/UX] Truncate overlapping name in starter select (#6828) * Add name truncation * Update src/ui/handlers/starter-select-ui-handler.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * remove unnecessary length check --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/ui/handlers/starter-select-ui-handler.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index 5d49fb3c57a..86d48ed992c 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -70,6 +70,7 @@ import { padInt, randIntRange, rgbHexToRgba, + truncateString, } from "#utils/common"; import type { StarterPreferences } from "#utils/data"; import { deepCopy, loadStarterPreferences, saveStarterPreferences } from "#utils/data"; @@ -2167,6 +2168,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { } else { this.pokemonNameText.setText(this.lastSpecies.name); } + this.truncateName(); ui.setMode(UiMode.STARTER_SELECT); }, () => { @@ -3554,6 +3556,7 @@ export class StarterSelectUiHandler extends MessageUiHandler { } else { this.pokemonNameText.setText(species.name); } + this.truncateName(); if (this.speciesStarterDexEntry?.caughtAttr) { const colorScheme = starterColors[species.speciesId]; @@ -4674,4 +4677,12 @@ export class StarterSelectUiHandler extends MessageUiHandler { this.starterPreferences = {}; this.originalStarterPreferences = {}; } + + /** + * Truncate the Pokémon name so it won't overlap into the starters. + */ + private truncateName() { + const name = this.pokemonNameText.text; + this.pokemonNameText.setText(truncateString(name, 15)); + } } From 46df6adab38cdffa38c550b9178ca76518c3b205 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:48:28 -0600 Subject: [PATCH 056/101] [Ability] Poison Puppeteer now applies for abilities (#6836) * [Ability] Poison Puppeteer now applies for abilities When a target is poisoned due to an ability of a Pokemon that also has Poison Puppeteer, Poison Puppeteer will now apply its effect * Add tests for Poison Puppeteer * Remove parameter properties from `ObtainStatusEffectPhase` --- src/data/abilities/ability.ts | 6 +- src/data/arena-tag.ts | 2 +- src/data/moves/move.ts | 7 +- src/field/pokemon.ts | 2 +- src/phases/obtain-status-effect-phase.ts | 35 ++++--- test/abilities/poison-puppeteer.test.ts | 112 +++++++++++++++++++++++ 6 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 test/abilities/poison-puppeteer.test.ts diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index bb54c2303f3..f258314157b 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -3627,8 +3627,6 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { export interface ConfusionOnStatusEffectAbAttrParams extends AbAttrBaseParams { /** The status effect that was applied */ effect: StatusEffect; - /** The move that applied the status effect */ - move: Move; /** The opponent that was inflicted with the status effect */ opponent: Pokemon; } @@ -3657,9 +3655,9 @@ export class ConfusionOnStatusEffectAbAttr extends AbAttr { /** * Applies confusion to the target pokemon. */ - override apply({ opponent, simulated, pokemon, move }: ConfusionOnStatusEffectAbAttrParams): void { + override apply({ opponent, simulated, pokemon }: ConfusionOnStatusEffectAbAttrParams): void { if (!simulated) { - opponent.addTag(BattlerTagType.CONFUSED, pokemon.randBattleSeedIntRange(2, 5), move.id, opponent.id); + opponent.addTag(BattlerTagType.CONFUSED, pokemon.randBattleSeedIntRange(2, 5), undefined, opponent.id); } } } diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 67e07ac47f6..5d6b77797a8 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -992,7 +992,7 @@ class ToxicSpikesTag extends EntryHazardTag { // Attempt to poison the target, suppressing any status effect messages const effect = this.layers === 1 ? StatusEffect.POISON : StatusEffect.TOXIC; - return pokemon.trySetStatus(effect, null, 0, this.getMoveName(), false, true); + return pokemon.trySetStatus(effect, undefined, 0, this.getMoveName(), false, true); } getMatchupScoreMultiplier(pokemon: Pokemon): number { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 8b5e80792bd..1073473b48f 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2955,14 +2955,9 @@ export class StatusEffectAttr extends MoveEffectAttr { return false; } - // non-status moves don't play sound effects for failures const quiet = move.category !== MoveCategory.STATUS; - if (target.trySetStatus(this.effect, user, undefined, null, false, quiet)) { - applyAbAttrs("ConfusionOnStatusEffectAbAttr", { pokemon: user, opponent: target, move, effect: this.effect }); - return true; - } - return false; + return target.trySetStatus(this.effect, user, undefined, null, false, quiet); } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 781c5508409..075b25bd62e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4863,7 +4863,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { */ public trySetStatus( effect: StatusEffect, - sourcePokemon: Pokemon | null = null, + sourcePokemon?: Pokemon, sleepTurnsRemaining?: number, sourceText: string | null = null, overrideStatus?: boolean, diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index b9f3e266d87..8b66134cd78 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -13,6 +13,11 @@ import { PokemonPhase } from "#phases/pokemon-phase"; export class ObtainStatusEffectPhase extends PokemonPhase { public readonly phaseName = "ObtainStatusEffectPhase"; + private readonly statusEffect: StatusEffect; + private readonly sourcePokemon?: Pokemon; + private readonly sleepTurnsRemaining?: number; + private readonly statusMessage: string; + /** * @param battlerIndex - The {@linkcode BattlerIndex} of the Pokemon obtaining the status effect. * @param statusEffect - The {@linkcode StatusEffect} being applied. @@ -27,19 +32,20 @@ export class ObtainStatusEffectPhase extends PokemonPhase { */ constructor( battlerIndex: BattlerIndex, - private statusEffect: StatusEffect, - private sourcePokemon: Pokemon | null = null, - private sleepTurnsRemaining?: number, - sourceText: string | null = null, // TODO: This should take `undefined` instead of `null` - private statusMessage = "", + statusEffect: StatusEffect, + sourcePokemon?: Pokemon, + sleepTurnsRemaining?: number, + sourceText: string | null = null, // TODO: this should be `sourceText?: string`, and then remove `?? undefined` below + statusMessage?: string, ) { super(battlerIndex); - this.statusMessage ||= getStatusEffectObtainText( - statusEffect, - getPokemonNameWithAffix(this.getPokemon()), - sourceText ?? undefined, - ); + this.statusEffect = statusEffect; + this.sourcePokemon = sourcePokemon; + this.sleepTurnsRemaining = sleepTurnsRemaining; + this.statusMessage = + statusMessage + || getStatusEffectObtainText(statusEffect, getPokemonNameWithAffix(this.getPokemon()), sourceText ?? undefined); } start() { @@ -58,8 +64,15 @@ export class ObtainStatusEffectPhase extends PokemonPhase { applyAbAttrs("PostSetStatusAbAttr", { pokemon, effect: this.statusEffect, - sourcePokemon: this.sourcePokemon ?? undefined, + sourcePokemon: this.sourcePokemon, }); + if (this.sourcePokemon) { + applyAbAttrs("ConfusionOnStatusEffectAbAttr", { + pokemon: this.sourcePokemon, + opponent: pokemon, + effect: this.statusEffect, + }); + } } this.end(); }); diff --git a/test/abilities/poison-puppeteer.test.ts b/test/abilities/poison-puppeteer.test.ts new file mode 100644 index 00000000000..d17fc1873a4 --- /dev/null +++ b/test/abilities/poison-puppeteer.test.ts @@ -0,0 +1,112 @@ +import { allAbilities } from "#data/data-lists"; +import { AbilityId } from "#enums/ability-id"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { StatusEffect } from "#enums/status-effect"; +import { GameManager } from "#test/test-utils/game-manager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Abilities - Poison Puppeteer", () => { + 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(AbilityId.POISON_PUPPETEER) + .battleStyle("single") + .criticalHits(false) + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.BALL_FETCH) + .enemyLevel(100) + .enemyMoveset(MoveId.SPLASH); + }); + + it("should confuse the target if the user poisons the target directly", async () => { + await game.classicMode.startBattle([SpeciesId.MAREANIE]); + + game.move.use(MoveId.MORTAL_SPIN); + await game.toEndOfTurn(); + + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toHaveStatusEffect(StatusEffect.POISON); + expect(enemyPokemon).toHaveBattlerTag(BattlerTagType.CONFUSED); + }); + + it("should confuse the target if the user badly poisons the target directly", async () => { + await game.classicMode.startBattle([SpeciesId.MAREANIE]); + + game.move.use(MoveId.TOXIC); + await game.toEndOfTurn(); + + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toHaveStatusEffect(StatusEffect.TOXIC); + expect(enemyPokemon).toHaveBattlerTag(BattlerTagType.CONFUSED); + }); + + it("should not confuse the target if the user poisons the target via Toxic Spikes", async () => { + game.override.startingWave(5); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + game.move.use(MoveId.TOXIC_SPIKES); + await game.toNextTurn(); + + game.move.use(MoveId.SPLASH); + game.forceEnemyToSwitch(); + await game.toEndOfTurn(); + + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toHaveStatusEffect(StatusEffect.POISON); + expect(enemyPokemon).not.toHaveBattlerTag(BattlerTagType.CONFUSED); + }); + + it("should not confuse the target if the user paralyzes the target", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + game.move.use(MoveId.NUZZLE); + await game.toEndOfTurn(); + + const enemyPokemon = game.field.getEnemyPokemon(); + expect(enemyPokemon).toHaveStatusEffect(StatusEffect.PARALYSIS); + expect(enemyPokemon).not.toHaveBattlerTag(BattlerTagType.CONFUSED); + }); + + it("should confuse the target if the target was poisoned due to Synchronize", async () => { + game.override.passiveAbility(AbilityId.SYNCHRONIZE).enemyAbility(AbilityId.NO_GUARD); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.POISON_POWDER); + await game.toEndOfTurn(); + + expect(game.field.getEnemyPokemon()).toHaveStatusEffect(StatusEffect.POISON); + expect(game.field.getEnemyPokemon()).toHaveBattlerTag(BattlerTagType.CONFUSED); + }); + + it("should confuse the target if the target was poisoned due to Toxic Chain", async () => { + game.override.passiveAbility(AbilityId.TOXIC_CHAIN); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); + + const toxicChainAttr = allAbilities[AbilityId.TOXIC_CHAIN].getAttrs("PostAttackApplyStatusEffectAbAttr")[0]; + // @ts-expect-error: `chance` is private + vi.spyOn(toxicChainAttr, "chance", "get").mockReturnValue(100); + + game.move.use(MoveId.TACKLE); + await game.toEndOfTurn(); + + expect(game.field.getEnemyPokemon()).toHaveStatusEffect(StatusEffect.TOXIC); + expect(game.field.getEnemyPokemon()).toHaveBattlerTag(BattlerTagType.CONFUSED); + }); +}); From 3f5c37c88123ca42bb9661ecb81ebb9639e4f62c Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:00:20 -0600 Subject: [PATCH 057/101] [Balance] The cost of buying same-species eggs can be reduced (#6837) * [Balance] The cost of buying same-species eggs can be reduced After hatching a certain number of eggs for a starter, the cost of buying same-species eggs for that starter will be reduced (up to 50%) * Add test to validate array lengths for egg costs --- src/data/balance/starters.ts | 76 +++++++++++++------ src/ui/handlers/pokedex-page-ui-handler.ts | 6 +- src/ui/handlers/pokedex-ui-handler.ts | 9 ++- src/ui/handlers/starter-select-ui-handler.ts | 7 +- test/data/balance/starter-candy-costs.test.ts | 10 +++ 5 files changed, 77 insertions(+), 31 deletions(-) create mode 100644 test/data/balance/starter-candy-costs.test.ts diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index 197a0d318bd..9e66004e95f 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -7,10 +7,11 @@ export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 3; export const FRIENDSHIP_GAIN_FROM_BATTLE = 3; export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 6; export const FRIENDSHIP_LOSS_FROM_FAINT = 5; +// #endregion /** * Function to get the cumulative friendship threshold at which a candy is earned - * @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts} + * @param starterCost - The cost of the starter, found in {@linkcode speciesStarterCosts} * @returns aforementioned threshold */ export function getStarterValueFriendshipCap(starterCost: number): number { @@ -618,43 +619,72 @@ export const speciesStarterCosts = { [SpeciesId.BLOODMOON_URSALUNA]: 5, }; -const starterCandyCosts: { passive: number; costReduction: [number, number]; egg: number; }[] = [ - { passive: 40, costReduction: [ 25, 60 ], egg: 30 }, // 1 Cost - { passive: 40, costReduction: [ 25, 60 ], egg: 30 }, // 2 Cost - { passive: 35, costReduction: [ 20, 50 ], egg: 25 }, // 3 Cost - { passive: 30, costReduction: [ 15, 40 ], egg: 20 }, // 4 Cost - { passive: 25, costReduction: [ 12, 35 ], egg: 18 }, // 5 Cost - { passive: 20, costReduction: [ 10, 30 ], egg: 15 }, // 6 Cost - { passive: 15, costReduction: [ 8, 20 ], egg: 12 }, // 7 Cost - { passive: 10, costReduction: [ 5, 15 ], egg: 10 }, // 8 Cost - { passive: 10, costReduction: [ 5, 15 ], egg: 10 }, // 9 Cost - { passive: 10, costReduction: [ 5, 15 ], egg: 10 }, // 10 Cost +interface StarterCandyCosts { + /** The candy cost to unlock the starter's passive ability */ + readonly passive: number; + /** The candy costs to reduce the starter's point cost */ + readonly costReduction: readonly [number, number]; + /** The costs to buy a same-species egg */ + readonly eggCosts: readonly [number, ...number[]]; + /** The number of eggs required to hatch to reduce the cost for buying more eggs */ + readonly eggCostReductionThresholds: readonly number[]; +} + +const allStarterCandyCosts: readonly StarterCandyCosts[] = [ + { passive: 40, costReduction: [25, 60], eggCosts: [30, 27, 22, 15], eggCostReductionThresholds: [20, 40, 80] }, // 1 Cost + { passive: 40, costReduction: [25, 60], eggCosts: [30, 27, 22, 15], eggCostReductionThresholds: [20, 40, 80] }, // 2 Cost + { passive: 35, costReduction: [20, 50], eggCosts: [25, 22, 18, 12], eggCostReductionThresholds: [20, 40, 80] }, // 3 Cost + { passive: 30, costReduction: [15, 40], eggCosts: [20, 18, 15, 10], eggCostReductionThresholds: [15, 30, 60] }, // 4 Cost + { passive: 25, costReduction: [12, 35], eggCosts: [18, 16, 13, 9], eggCostReductionThresholds: [15, 30, 60] }, // 5 Cost + { passive: 20, costReduction: [10, 30], eggCosts: [15, 13, 11, 7], eggCostReductionThresholds: [15, 30, 60] }, // 6 Cost + { passive: 15, costReduction: [8, 20], eggCosts: [12, 10, 9, 6], eggCostReductionThresholds: [10, 20, 40] }, // 7 Cost + { passive: 10, costReduction: [5, 15], eggCosts: [10, 9, 7, 5], eggCostReductionThresholds: [10, 20, 40] }, // 8 Cost + { passive: 10, costReduction: [5, 15], eggCosts: [10, 9, 7, 5], eggCostReductionThresholds: [10, 20, 40] }, // 9 Cost + { passive: 10, costReduction: [5, 15], eggCosts: [10, 9, 7, 5], eggCostReductionThresholds: [8, 16, 32] }, // 10 Cost ]; /** - * Getter for {@linkcode starterCandyCosts} for passive unlock candy cost based on initial point cost - * @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts} + * Getter for {@linkcode allStarterCandyCosts} for passive unlock candy cost based on initial point cost + * @param starterCost - The default point cost of the starter found in {@linkcode speciesStarterCosts} * @returns the candy cost for passive unlock */ export function getPassiveCandyCount(starterCost: number): number { - return starterCandyCosts[starterCost - 1].passive; + return allStarterCandyCosts[starterCost - 1].passive; } /** - * Getter for {@linkcode starterCandyCosts} for value reduction unlock candy cost based on initial point cost - * @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts} + * Getter for {@linkcode allStarterCandyCosts} for value reduction unlock candy cost based on initial point cost + * @param starterCost - The default point cost of the starter found in {@linkcode speciesStarterCosts} * @returns respective candy cost for the two cost reductions as an array 2 numbers */ -export function getValueReductionCandyCounts(starterCost: number): [number, number] { - return starterCandyCosts[starterCost - 1].costReduction; +export function getValueReductionCandyCounts(starterCost: number): readonly [number, number] { + return allStarterCandyCosts[starterCost - 1].costReduction; } /** - * Getter for {@linkcode starterCandyCosts} for egg purchase candy cost based on initial point cost - * @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts} + * Getter for {@linkcode allStarterCandyCosts} for egg purchase candy cost based on initial point cost + * @param starterCost - The default point cost of the starter found in {@linkcode speciesStarterCosts} + * @param hatchCount - The number of eggs hatched of the starter * @returns the candy cost for the purchasable egg */ -export function getSameSpeciesEggCandyCounts(starterCost: number): number { - return starterCandyCosts[starterCost - 1].egg; +export function getSameSpeciesEggCandyCounts(starterCost: number, hatchCount: number): number { + const starterCandyCosts = allStarterCandyCosts[starterCost - 1]; + let eggCostIndex = 0; + while (hatchCount >= starterCandyCosts.eggCostReductionThresholds[eggCostIndex]) { + eggCostIndex++; + } + return starterCandyCosts.eggCosts[eggCostIndex]; } +/** + * ⚠️ This is used for internal testing purposes only and will not be populated outside of the test environment. + * @internal + */ +export const __TEST_allStarterCandyCosts: readonly StarterCandyCosts[] = []; + +if (import.meta.env.NODE_ENV === "test") { + for (const starterCandyCosts of allStarterCandyCosts) { + // @ts-expect-error: done this way to keep it `readonly` + __TEST_allStarterCandyCosts.push(starterCandyCosts); + } +} \ No newline at end of file diff --git a/src/ui/handlers/pokedex-page-ui-handler.ts b/src/ui/handlers/pokedex-page-ui-handler.ts index cb9ed55bbd1..5e87872f7c2 100644 --- a/src/ui/handlers/pokedex-page-ui-handler.ts +++ b/src/ui/handlers/pokedex-page-ui-handler.ts @@ -2020,7 +2020,8 @@ export class PokedexPageUiHandler extends MessageUiHandler { } // Same species egg menu option. - const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId]); + const hatchCount = globalScene.gameData.dexData[this.starterId].hatchedCount; + const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId], hatchCount); options.push({ label: `×${sameSpeciesEggCost} ${i18next.t("pokedexUiHandler:sameSpeciesEgg")}`, handler: () => { @@ -2367,8 +2368,9 @@ export class PokedexPageUiHandler extends MessageUiHandler { isSameSpeciesEggAvailable(): boolean { // Get this species ID's starter data const starterData = globalScene.gameData.starterData[this.starterId]; + const hatchCount = globalScene.gameData.dexData[this.starterId].hatchedCount; - return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId]); + return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId], hatchCount); } setSpecies() { diff --git a/src/ui/handlers/pokedex-ui-handler.ts b/src/ui/handlers/pokedex-ui-handler.ts index b0afd88cf7a..135862fbbd4 100644 --- a/src/ui/handlers/pokedex-ui-handler.ts +++ b/src/ui/handlers/pokedex-ui-handler.ts @@ -893,11 +893,12 @@ export class PokedexUiHandler extends MessageUiHandler { */ isSameSpeciesEggAvailable(speciesId: number): boolean { // Get this species ID's starter data - const starterData = this.gameData.starterData[this.getStarterSpeciesId(speciesId)]; + const { gameData } = this; + const starterId = this.getStarterSpeciesId(speciesId); + const candyCount = gameData.starterData[starterId].candyCount; + const hatchCount = gameData.dexData[starterId].hatchedCount; - return ( - starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)]) - ); + return candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[starterId], hatchCount); } /** diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index 86d48ed992c..c487c0dcfea 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -1417,8 +1417,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { isSameSpeciesEggAvailable(speciesId: number): boolean { // Get this species ID's starter data const starterData = globalScene.gameData.starterData[speciesId]; + const hatchedCount = globalScene.gameData.dexData[speciesId].hatchedCount; - return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[speciesId]); + return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[speciesId], hatchedCount); } /** @@ -2265,7 +2266,9 @@ export class StarterSelectUiHandler extends MessageUiHandler { } // Same species egg menu option. - const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.lastSpecies.speciesId]); + const lastSpeciesId = this.lastSpecies.speciesId; + const hatchedCount = globalScene.gameData.dexData[lastSpeciesId].hatchedCount; + const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[lastSpeciesId], hatchedCount); options.push({ label: `×${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`, handler: () => { diff --git a/test/data/balance/starter-candy-costs.test.ts b/test/data/balance/starter-candy-costs.test.ts new file mode 100644 index 00000000000..ed3ebd24c60 --- /dev/null +++ b/test/data/balance/starter-candy-costs.test.ts @@ -0,0 +1,10 @@ +import { __TEST_allStarterCandyCosts } from "#balance/starters"; +import { describe, expect, it } from "vitest"; + +describe("Starter Candy Costs", () => { + it("should have the proper length of arrays in `allStarterCandyCosts`", () => { + for (const starterCandyCosts of __TEST_allStarterCandyCosts) { + expect(starterCandyCosts.eggCosts).toHaveLength(starterCandyCosts.eggCostReductionThresholds.length + 1); + } + }); +}); From c4b0199b3db2fd5f4ebf972da8e5a31d6f642e37 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:22:42 -0600 Subject: [PATCH 058/101] [Deps] Update jsdom from `27.2.0` to `27.3.0` (#6840) --- package.json | 2 +- pnpm-lock.yaml | 70 +++++++++++++++++++++++++------------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index b0fada52c2b..03833ef5ffa 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@vitest/utils": "^4.0.14", "chalk": "^5.6.2", "dependency-cruiser": "^17.3.1", - "jsdom": "^27.2.0", + "jsdom": "^27.3.0", "lefthook": "^2.0.4", "msw": "^2.12.3", "phaser3spectorjs": "^0.0.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8889db4816..88ec2e6f5c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,7 +65,7 @@ importers: version: 24.10.1 '@vitest/coverage-istanbul': specifier: ^4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) + version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) '@vitest/expect': specifier: ^4.0.14 version: 4.0.14 @@ -79,8 +79,8 @@ importers: specifier: ^17.3.1 version: 17.3.1 jsdom: - specifier: ^27.2.0 - version: 27.2.0(postcss@8.5.6) + specifier: ^27.3.0 + version: 27.3.0(postcss@8.5.6) lefthook: specifier: ^2.0.4 version: 2.0.4 @@ -116,21 +116,21 @@ importers: version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) vitest: specifier: ^4.0.14 - version: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + version: 4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) vitest-canvas-mock: specifier: ^1.1.2 - version: 1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) + version: 1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) packages: - '@acemir/cssom@0.9.24': - resolution: {integrity: sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==} + '@acemir/cssom@0.9.28': + resolution: {integrity: sha512-LuS6IVEivI75vKN8S04qRD+YySP0RmU/cV8UNukhQZvprxF+76Z43TNo/a08eCodaGhT1Us8etqS1ZRY9/Or0A==} - '@asamuzakjp/css-color@4.0.5': - resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} + '@asamuzakjp/css-color@4.1.0': + resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==} - '@asamuzakjp/dom-selector@6.7.4': - resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} + '@asamuzakjp/dom-selector@6.7.6': + resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -1026,8 +1026,8 @@ packages: cssfontparser@1.2.1: resolution: {integrity: sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==} - cssstyle@5.3.3: - resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} + cssstyle@5.3.4: + resolution: {integrity: sha512-KyOS/kJMEq5O9GdPnaf82noigg5X5DYn0kZPJTaAsCUaBizp6Xa1y9D4Qoqf/JazEXWuruErHgVXwjN5391ZJw==} engines: {node: '>=20'} dagre@0.8.5: @@ -1335,8 +1335,8 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsdom@27.2.0: - resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + jsdom@27.3.0: + resolution: {integrity: sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -1434,8 +1434,8 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -2016,23 +2016,23 @@ packages: snapshots: - '@acemir/cssom@0.9.24': {} + '@acemir/cssom@0.9.28': {} - '@asamuzakjp/css-color@4.0.5': + '@asamuzakjp/css-color@4.1.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - lru-cache: 11.2.2 + lru-cache: 11.2.4 - '@asamuzakjp/dom-selector@6.7.4': + '@asamuzakjp/dom-selector@6.7.6': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.1.0 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.2 + lru-cache: 11.2.4 '@asamuzakjp/nwsapi@2.3.9': {} @@ -2602,7 +2602,7 @@ snapshots: '@types/unist@3.0.3': {} - '@vitest/coverage-istanbul@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1))': + '@vitest/coverage-istanbul@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1))': dependencies: '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -2613,7 +2613,7 @@ snapshots: magicast: 0.5.1 obug: 2.1.1 tinyrainbow: 3.0.3 - vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -2785,9 +2785,9 @@ snapshots: cssfontparser@1.2.1: {} - cssstyle@5.3.3(postcss@8.5.6): + cssstyle@5.3.4(postcss@8.5.6): dependencies: - '@asamuzakjp/css-color': 4.0.5 + '@asamuzakjp/css-color': 4.1.0 '@csstools/css-syntax-patches-for-csstree': 1.0.14(postcss@8.5.6) css-tree: 3.1.0 transitivePeerDependencies: @@ -3113,11 +3113,11 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@27.2.0(postcss@8.5.6): + jsdom@27.3.0(postcss@8.5.6): dependencies: - '@acemir/cssom': 0.9.24 - '@asamuzakjp/dom-selector': 6.7.4 - cssstyle: 5.3.3(postcss@8.5.6) + '@acemir/cssom': 0.9.28 + '@asamuzakjp/dom-selector': 6.7.6 + cssstyle: 5.3.4(postcss@8.5.6) data-urls: 6.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 @@ -3219,7 +3219,7 @@ snapshots: lodash@4.17.21: {} - lru-cache@11.2.2: {} + lru-cache@11.2.4: {} lru-cache@5.1.1: dependencies: @@ -3629,13 +3629,13 @@ snapshots: fsevents: 2.3.3 yaml: 2.8.1 - vitest-canvas-mock@1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)): + vitest-canvas-mock@1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) - vitest@4.0.14(@types/node@24.10.1)(jsdom@27.2.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1): + vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.14 '@vitest/mocker': 4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) @@ -3659,7 +3659,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.1 - jsdom: 27.2.0(postcss@8.5.6) + jsdom: 27.3.0(postcss@8.5.6) transitivePeerDependencies: - jiti - less From fd92bcd2a4ebdfbb10f75e7dc5438088654731e2 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:33:21 -0600 Subject: [PATCH 059/101] [Deps] Update packages (#6841) --- package.json | 28 +- pnpm-lock.yaml | 1257 ++++++++++++++++++++++++------------------------ 2 files changed, 632 insertions(+), 653 deletions(-) diff --git a/package.json b/package.json index 03833ef5ffa..c1fdf94cda9 100644 --- a/package.json +++ b/package.json @@ -41,37 +41,37 @@ }, "devDependencies": { "@biomejs/biome": "2.3.8", - "@inquirer/prompts": "^8.0.1", + "@inquirer/prompts": "^8.0.2", "@ls-lint/ls-lint": "2.3.1", "@types/crypto-js": "^4.2.2", "@types/jsdom": "^27.0.0", - "@types/node": "^24.10.1", - "@vitest/coverage-istanbul": "^4.0.14", - "@vitest/expect": "^4.0.14", - "@vitest/utils": "^4.0.14", + "@types/node": "^24.10.2", + "@vitest/coverage-istanbul": "^4.0.15", + "@vitest/expect": "^4.0.15", + "@vitest/utils": "^4.0.15", "chalk": "^5.6.2", - "dependency-cruiser": "^17.3.1", + "dependency-cruiser": "^17.3.2", "jsdom": "^27.3.0", - "lefthook": "^2.0.4", - "msw": "^2.12.3", + "lefthook": "^2.0.9", + "msw": "^2.12.4", "phaser3spectorjs": "^0.0.8", - "type-fest": "^5.3.0", - "typedoc": "^0.28.14", + "type-fest": "^5.3.1", + "typedoc": "^0.28.15", "typedoc-github-theme": "^0.3.1", "typedoc-plugin-coverage": "^4.0.2", "typedoc-plugin-mdn-links": "^5.0.10", "typescript": "^5.9.3", - "vite": "^7.2.4", + "vite": "^7.2.7", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^4.0.14", - "vitest-canvas-mock": "^1.1.2" + "vitest": "^4.0.15", + "vitest-canvas-mock": "^1.1.3" }, "dependencies": { "@material/material-color-utilities": "^0.3.0", "compare-versions": "^6.1.1", "core-js": "^3.47.0", "crypto-js": "^4.2.0", - "i18next": "^25.6.3", + "i18next": "^25.7.2", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", "i18next-korean-postposition-processor": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88ec2e6f5c1..5f17fe9db12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: ^4.2.0 version: 4.2.0 i18next: - specifier: ^25.6.3 - version: 25.6.3(typescript@5.9.3) + specifier: ^25.7.2 + version: 25.7.2(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.2.0 version: 8.2.0 @@ -31,7 +31,7 @@ importers: version: 3.0.2 i18next-korean-postposition-processor: specifier: ^1.0.0 - version: 1.0.0(i18next@25.6.3(typescript@5.9.3)) + version: 1.0.0(i18next@25.7.2(typescript@5.9.3)) json-stable-stringify: specifier: ^1.3.0 version: 1.3.0 @@ -49,8 +49,8 @@ importers: specifier: 2.3.8 version: 2.3.8 '@inquirer/prompts': - specifier: ^8.0.1 - version: 8.0.1(@types/node@24.10.1) + specifier: ^8.0.2 + version: 8.0.2(@types/node@24.10.2) '@ls-lint/ls-lint': specifier: 2.3.1 version: 2.3.1 @@ -61,65 +61,65 @@ importers: specifier: ^27.0.0 version: 27.0.0 '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^24.10.2 + version: 24.10.2 '@vitest/coverage-istanbul': - specifier: ^4.0.14 - version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) + specifier: ^4.0.15 + version: 4.0.15(vitest@4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2)) '@vitest/expect': - specifier: ^4.0.14 - version: 4.0.14 + specifier: ^4.0.15 + version: 4.0.15 '@vitest/utils': - specifier: ^4.0.14 - version: 4.0.14 + specifier: ^4.0.15 + version: 4.0.15 chalk: specifier: ^5.6.2 version: 5.6.2 dependency-cruiser: - specifier: ^17.3.1 - version: 17.3.1 + specifier: ^17.3.2 + version: 17.3.2 jsdom: specifier: ^27.3.0 version: 27.3.0(postcss@8.5.6) lefthook: - specifier: ^2.0.4 - version: 2.0.4 + specifier: ^2.0.9 + version: 2.0.9 msw: - specifier: ^2.12.3 - version: 2.12.3(@types/node@24.10.1)(typescript@5.9.3) + specifier: ^2.12.4 + version: 2.12.4(@types/node@24.10.2)(typescript@5.9.3) phaser3spectorjs: specifier: ^0.0.8 version: 0.0.8 type-fest: - specifier: ^5.3.0 - version: 5.3.0 + specifier: ^5.3.1 + version: 5.3.1 typedoc: - specifier: ^0.28.14 - version: 0.28.14(typescript@5.9.3) + specifier: ^0.28.15 + version: 0.28.15(typescript@5.9.3) typedoc-github-theme: specifier: ^0.3.1 - version: 0.3.1(typedoc@0.28.14(typescript@5.9.3)) + version: 0.3.1(typedoc@0.28.15(typescript@5.9.3)) typedoc-plugin-coverage: specifier: ^4.0.2 - version: 4.0.2(typedoc@0.28.14(typescript@5.9.3)) + version: 4.0.2(typedoc@0.28.15(typescript@5.9.3)) typedoc-plugin-mdn-links: specifier: ^5.0.10 - version: 5.0.10(typedoc@0.28.14(typescript@5.9.3)) + version: 5.0.10(typedoc@0.28.15(typescript@5.9.3)) typescript: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) + specifier: ^7.2.7 + version: 7.2.7(@types/node@24.10.2)(yaml@2.8.2) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.2)(yaml@2.8.2)) vitest: - specifier: ^4.0.14 - version: 4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + specifier: ^4.0.15 + version: 4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2) vitest-canvas-mock: - specifier: ^1.1.2 - version: 1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)) + specifier: ^1.1.3 + version: 1.1.3(vitest@4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2)) packages: @@ -139,16 +139,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -173,10 +173,6 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} @@ -189,11 +185,6 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} @@ -207,12 +198,8 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} '@babel/types@7.28.5': @@ -310,175 +297,175 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@gerrit0/mini-shiki@3.13.0': - resolution: {integrity: sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==} + '@gerrit0/mini-shiki@3.19.0': + resolution: {integrity: sha512-ZSlWfLvr8Nl0T4iA3FF/8VH8HivYF82xQts2DY0tJxZd4wtXJ8AA0nmdW9lmO4hlrh3f9xNwEPtOgqETPqKwDA==} - '@inquirer/ansi@1.0.0': - resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} - '@inquirer/ansi@2.0.1': - resolution: {integrity: sha512-QAZUk6BBncv/XmSEZTscd8qazzjV3E0leUMrEPjxCd51QBgCKmprUGLex5DTsNtURm7LMzv+CLcd6S86xvBfYg==} + '@inquirer/ansi@2.0.2': + resolution: {integrity: sha512-SYLX05PwJVnW+WVegZt1T4Ip1qba1ik+pNJPDiqvk6zS5Y/i8PhRzLpGEtVd7sW0G8cMtkD8t4AZYhQwm8vnww==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/checkbox@5.0.1': - resolution: {integrity: sha512-5VPFBK8jKdsjMK3DTFOlbR0+Kkd4q0AWB7VhWQn6ppv44dr3b7PU8wSJQTC5oA0f/aGW7v/ZozQJAY9zx6PKig==} + '@inquirer/checkbox@5.0.2': + resolution: {integrity: sha512-iTPV4tMMct7iOpwer5qmTP7gjnk1VQJjsNfAaC2b8Q3qiuHM3K2yjjDr5u1MKfkrvp2JD4Flf8sIPpF21pmZmw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -486,8 +473,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.18': - resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==} + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -495,8 +482,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@6.0.1': - resolution: {integrity: sha512-wD+pM7IxLn1TdcQN12Q6wcFe5VpyCuh/I2sSmqO5KjWH2R4v+GkUToHb+PsDGobOe1MtAlXMwGNkZUPc2+L6NA==} + '@inquirer/confirm@6.0.2': + resolution: {integrity: sha512-A0/13Wyi+8iFeNDX6D4zZYKPoBLIEbE4K/219qHcnpXMer2weWvaTo63+2c7mQPPA206DEMSYVOPnEw3meOlCw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -504,8 +491,8 @@ packages: '@types/node': optional: true - '@inquirer/core@10.2.2': - resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==} + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -513,8 +500,8 @@ packages: '@types/node': optional: true - '@inquirer/core@11.0.1': - resolution: {integrity: sha512-Tpf49h50e4KYffVUCXzkx4gWMafUi3aDQDwfVAAGBNnVcXiwJIj4m2bKlZ7Kgyf6wjt1eyXH1wDGXcAokm4Ssw==} + '@inquirer/core@11.0.2': + resolution: {integrity: sha512-lgMRx/n02ciiNELBvFLHtmcjbV5tf5D/I0UYfCg2YbTZWmBZ10/niLd3IjWBxz8LtM27xP+4oLEa06Slmb7p7A==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -522,8 +509,8 @@ packages: '@types/node': optional: true - '@inquirer/editor@5.0.1': - resolution: {integrity: sha512-zDKobHI7Ry++4noiV9Z5VfYgSVpPZoMApviIuGwLOMciQaP+dGzCO+1fcwI441riklRiZg4yURWyEoX0Zy2zZw==} + '@inquirer/editor@5.0.2': + resolution: {integrity: sha512-pXQ4Nf0qmFcJuYB6NlcIIxH6l6zKOwNg1Jh/ZRdKd2dTqBB4OXKUFbFwR2K4LVXVtq15ZFFatBVT+rerYR8hWQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -531,8 +518,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@5.0.1': - resolution: {integrity: sha512-TBrTpAB6uZNnGQHtSEkbvJZIQ3dXZOrwqQSO9uUbwct3G2LitwBCE5YZj98MbQ5nzihzs5pRjY1K9RRLH4WgoA==} + '@inquirer/expand@5.0.2': + resolution: {integrity: sha512-siFG1swxfjFIOxIcehtZkh+KUNB/YCpyfHNEGu+nC/SBXIbgUWibvThLn/WesSxLRGOeSKdNKoTm+GQCKFm6Ww==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -540,8 +527,8 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@2.0.1': - resolution: {integrity: sha512-BPYWJXCAK9w6R+pb2s3WyxUz9ts9SP/LDOUwA9fu7LeuyYgojz83i0DSRwezu736BgMwz14G63Xwj70hSzHohQ==} + '@inquirer/external-editor@2.0.2': + resolution: {integrity: sha512-X/fMXK7vXomRWEex1j8mnj7s1mpnTeP4CO/h2gysJhHLT2WjBnLv4ZQEGpm/kcYI8QfLZ2fgW+9kTKD+jeopLg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -549,16 +536,16 @@ packages: '@types/node': optional: true - '@inquirer/figures@1.0.13': - resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} engines: {node: '>=18'} - '@inquirer/figures@2.0.1': - resolution: {integrity: sha512-KtMxyjLCuDFqAWHmCY9qMtsZ09HnjMsm8H3OvpSIpfhHdfw3/AiGWHNrfRwbyvHPtOJpumm8wGn5fkhtvkWRsg==} + '@inquirer/figures@2.0.2': + resolution: {integrity: sha512-qXm6EVvQx/FmnSrCWCIGtMHwqeLgxABP8XgcaAoywsL0NFga9gD5kfG0gXiv80GjK9Hsoz4pgGwF/+CjygyV9A==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/input@5.0.1': - resolution: {integrity: sha512-cEhEUohCpE2BCuLKtFFZGp4Ief05SEcqeAOq9NxzN5ThOQP8Rl5N/Nt9VEDORK1bRb2Sk/zoOyQYfysPQwyQtA==} + '@inquirer/input@5.0.2': + resolution: {integrity: sha512-hN2YRo1QiEc9lD3mK+CPnTS4TK2RhCMmMmP4nCWwTkmQL2vx9jPJWYk+rbUZpwR1D583ZJk1FI3i9JZXIpi/qg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -566,8 +553,8 @@ packages: '@types/node': optional: true - '@inquirer/number@4.0.1': - resolution: {integrity: sha512-4//zgBGHe8Q/FfCoUXZUrUHyK/q5dyqiwsePz3oSSPSmw1Ijo35ZkjaftnxroygcUlLYfXqm+0q08lnB5hd49A==} + '@inquirer/number@4.0.2': + resolution: {integrity: sha512-4McnjTSYrlthNW1ojkkmP75WLRYhQs7GXm6pDDoIrHqJuV5uUYwfdbB0geHdaKMarAqJQgoOVjzIT0jdWCsKew==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -575,8 +562,8 @@ packages: '@types/node': optional: true - '@inquirer/password@5.0.1': - resolution: {integrity: sha512-UJudHpd7Ia30Q+x+ctYqI9Nh6SyEkaBscpa7J6Ts38oc1CNSws0I1hJEdxbQBlxQd65z5GEJPM4EtNf6tzfWaQ==} + '@inquirer/password@5.0.2': + resolution: {integrity: sha512-oSDziMKiw4G2e4zS+0JRfxuPFFGh6N/9yUaluMgEHp2/Yyj2JGwfDO7XbwtOrxVrz+XsP/iaGyWXdQb9d8A0+g==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -584,8 +571,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@8.0.1': - resolution: {integrity: sha512-MURRu/cyvLm9vchDDaVZ9u4p+ADnY0Mz3LQr0KTgihrrvuKZlqcWwlBC4lkOMvd0KKX4Wz7Ww9+uA7qEpQaqjg==} + '@inquirer/prompts@8.0.2': + resolution: {integrity: sha512-2zK5zY48fZcl6+gG4eqOC/UzZsJckHCRvjXoLuW4D8LKOCVGdcJiSKkLnumSZjR/6PXPINDGOrGHqNxb+sxJDg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -593,8 +580,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@5.0.1': - resolution: {integrity: sha512-vVfVHKUgH6rZmMlyd0jOuGZo0Fw1jfcOqZF96lMwlgavx7g0x7MICe316bV01EEoI+c68vMdbkTTawuw3O+Fgw==} + '@inquirer/rawlist@5.0.2': + resolution: {integrity: sha512-AcNALEdQKUQDeJcpC1a3YC53m1MLv+sMUS+vRZ8Qigs1Yg3Dcdtmi82rscJplogKOY8CXkKW4wvVwHS2ZjCIBQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -602,8 +589,8 @@ packages: '@types/node': optional: true - '@inquirer/search@4.0.1': - resolution: {integrity: sha512-XwiaK5xBvr31STX6Ji8iS3HCRysBXfL/jUbTzufdWTS6LTGtvDQA50oVETt1BJgjKyQBp9vt0VU6AmU/AnOaGA==} + '@inquirer/search@4.0.2': + resolution: {integrity: sha512-hg63w5toohdzE65S3LiGhdfIL0kT+yisbZARf7zw65PvyMUTutTN3eMAvD/B6y/25z88vTrB7kSB45Vz5CbrXg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -611,8 +598,8 @@ packages: '@types/node': optional: true - '@inquirer/select@5.0.1': - resolution: {integrity: sha512-gPByrgYoezGyKMq5KjV7Tuy1JU2ArIy6/sI8sprw0OpXope3VGQwP5FK1KD4eFFqEhKu470Dwe6/AyDPmGRA0Q==} + '@inquirer/select@5.0.2': + resolution: {integrity: sha512-JygTohvQxSNnvt7IKANVlg/eds+yN5sLRilYeGc4ri/9Aqi/2QPoXBMV5Cz/L1VtQv63SnTbPXJZeCK2pSwsOA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -620,8 +607,8 @@ packages: '@types/node': optional: true - '@inquirer/type@3.0.8': - resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -629,8 +616,8 @@ packages: '@types/node': optional: true - '@inquirer/type@4.0.1': - resolution: {integrity: sha512-odO8YwoQAw/eVu/PSPsDDVPmqO77r/Mq7zcoF5VduVqIu2wSRWUgmYb5K9WH1no0SjLnOe8MDKtDL++z6mfo2g==} + '@inquirer/type@4.0.2': + resolution: {integrity: sha512-cae7mzluplsjSdgFA6ACLygb5jC8alO0UUnFPyu0E7tNRPrL+q/f8VcSXp+cjZQ7l5CMpDpi2G1+IQvkOiL1Lw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' @@ -680,138 +667,138 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@rollup/rollup-android-arm-eabi@4.52.4': - resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.4': - resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.4': - resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.4': - resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.4': - resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.4': - resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': - resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.52.4': - resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.52.4': - resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.52.4': - resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.52.4': - resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.52.4': - resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.52.4': - resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.52.4': - resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.52.4': - resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.52.4': - resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.52.4': - resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openharmony-arm64@4.52.4': - resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.4': - resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.4': - resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.4': - resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.4': - resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} cpu: [x64] os: [win32] - '@shikijs/engine-oniguruma@3.13.0': - resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==} + '@shikijs/engine-oniguruma@3.19.0': + resolution: {integrity: sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==} - '@shikijs/langs@3.13.0': - resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==} + '@shikijs/langs@3.19.0': + resolution: {integrity: sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==} - '@shikijs/themes@3.13.0': - resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==} + '@shikijs/themes@3.19.0': + resolution: {integrity: sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==} - '@shikijs/types@3.13.0': - resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==} + '@shikijs/types@3.19.0': + resolution: {integrity: sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -819,8 +806,8 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/crypto-js@4.2.2': resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} @@ -837,8 +824,8 @@ packages: '@types/jsdom@27.0.0': resolution: {integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -849,16 +836,16 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@vitest/coverage-istanbul@4.0.14': - resolution: {integrity: sha512-weQA5DR6/GaHL61WK0mnq9fzEeWxkpEawM4mp2WodMewLHKc1mCITJcmofNSMON2x0O951RjdnFsXsKi8WzSWg==} + '@vitest/coverage-istanbul@4.0.15': + resolution: {integrity: sha512-KR2Nyb/wZYEDkpnOoF4O5tqK0nwrratk5i538a+8vOWXAMRKBdz3Kkmggq6tmh1tdecty/g68NHstKh03a4Jog==} peerDependencies: - vitest: 4.0.14 + vitest: 4.0.15 - '@vitest/expect@4.0.14': - resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} + '@vitest/expect@4.0.15': + resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} - '@vitest/mocker@4.0.14': - resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} + '@vitest/mocker@4.0.15': + resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -868,20 +855,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.14': - resolution: {integrity: sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==} + '@vitest/pretty-format@4.0.15': + resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} - '@vitest/runner@4.0.14': - resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} + '@vitest/runner@4.0.15': + resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} - '@vitest/snapshot@4.0.14': - resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} + '@vitest/snapshot@4.0.15': + resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} - '@vitest/spy@4.0.14': - resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} + '@vitest/spy@4.0.15': + resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} - '@vitest/utils@4.0.14': - resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} + '@vitest/utils@4.0.15': + resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} acorn-jsx-walk@2.0.0: resolution: {integrity: sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==} @@ -930,11 +917,15 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.12: - resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} + baseline-browser-mapping@2.9.5: + resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} hasBin: true bidi-js@1.0.3: @@ -943,8 +934,8 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -960,8 +951,8 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - caniuse-lite@1.0.30001747: - resolution: {integrity: sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==} + caniuse-lite@1.0.30001759: + resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} chai@6.2.1: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} @@ -1053,8 +1044,8 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} - dependency-cruiser@17.3.1: - resolution: {integrity: sha512-yWwszB4GKIBKK/xiHSQ6TVIV6k8byd+gMGT2RMQ+03wb1jGH48cSsLH29iUUZgGtKyLSH51NurbnjXV0+niUjA==} + dependency-cruiser@17.3.2: + resolution: {integrity: sha512-1xx1Q1i7WuW85W1x1cLFcl4L5bjW5bT+qBm6kwnAdIHDA87x/gU7NWKknpBWMilX2lQphxtB1VeU3GVOj3wYqw==} engines: {node: ^20.12||^22||>=24} hasBin: true @@ -1062,8 +1053,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.230: - resolution: {integrity: sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==} + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -1098,8 +1089,8 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true @@ -1120,8 +1111,8 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} fast-deep-equal@3.1.3: @@ -1246,8 +1237,8 @@ packages: i18next@22.5.1: resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==} - i18next@25.6.3: - resolution: {integrity: sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==} + i18next@25.7.2: + resolution: {integrity: sha512-58b4kmLpLv1buWUEwegMDUqZVR5J+rT+WTRFaBGL7lxDuJQQ0NrJFrq+eT2N94aYVR1k1Sr13QITNOL88tZCuw==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -1331,8 +1322,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsdom@27.3.0: @@ -1371,58 +1362,58 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lefthook-darwin-arm64@2.0.4: - resolution: {integrity: sha512-AR63/O5UkM7Sc6x5PhP4vTuztTYRBeBroXApeWGM/8e5uZyoQug/7KTh7xhbCMDf8WJv6vdFeXAQCPSmDyPU3Q==} + lefthook-darwin-arm64@2.0.9: + resolution: {integrity: sha512-DRDqDiDWvQ8CAW7c5o90yApxBu5Pe958Ya2OmgTNLKOEw2tSIlz3YPc0AqWlXxp2KkB6RLv0ottLyJFFRmQBtQ==} cpu: [arm64] os: [darwin] - lefthook-darwin-x64@2.0.4: - resolution: {integrity: sha512-618DVUttSzV9egQiqTQoxGfnR240JoPWYmqRVHhiegnQKZ2lp5XJ+7NMxeRk/ih93VVOLzFO5ky3PbpxTmJgjQ==} + lefthook-darwin-x64@2.0.9: + resolution: {integrity: sha512-Onx2QNWjeVbkDRIbfajcj0U73W51xbhalqRj7yT0JMIIzmyVafnEN2KRrd4i0/UnrquyY2mfrQlVjDM1DoMvCg==} cpu: [x64] os: [darwin] - lefthook-freebsd-arm64@2.0.4: - resolution: {integrity: sha512-mTAQym1BK38fKglHBQ/0GXPznVC4LoStHO5lAI3ZxaEC0FQetqGHYFzhWbIH5sde9JhztE2rL/aBzMHDoAtzSw==} + lefthook-freebsd-arm64@2.0.9: + resolution: {integrity: sha512-0Ew0kabZkl9uid7sz9DxeFjQSI0oBWwMA8eyeQf4z0FWQ1iMay+TSgV8WOSN0OAiS6QECmrl7J+5/S8z4t2XYQ==} cpu: [arm64] os: [freebsd] - lefthook-freebsd-x64@2.0.4: - resolution: {integrity: sha512-sy02aSxd8UMd6XmiPFVl/Em0b78jdZcDSsLwg+bweJQQk0l+vJhOfqFiG11mbnpo+EBIZmRe6OH5LkxeSU36+w==} + lefthook-freebsd-x64@2.0.9: + resolution: {integrity: sha512-bCqj0+bKzMenYkpJty7ZDGR8+RT/PxoYkt5QqIhxCj1b+DjFyOiMAbMiWr7vRm8tLiYK6ieVr5xsvjYPCXAsdA==} cpu: [x64] os: [freebsd] - lefthook-linux-arm64@2.0.4: - resolution: {integrity: sha512-W0Nlr/Cz2QTH9n4k5zNrk3LSsg1C4wHiJi8hrAiQVTaAV/N1XrKqd0DevqQuouuapG6pw/6B1xCgiNPebv9oyw==} + lefthook-linux-arm64@2.0.9: + resolution: {integrity: sha512-jSE+ZIxz++5JkU9vTY0ZCz7VijUvPw9rUXu/ufGq7HYHaxkQlgqGY9p9H9y10Oh1EahWiC3jGlForx5JhHDrCg==} cpu: [arm64] os: [linux] - lefthook-linux-x64@2.0.4: - resolution: {integrity: sha512-N6ySVCtB/DrOZ1ZgPL8WBZTgtoVHvcPKI+LV5wbcGrvA/dzDZFvniadrbDWZg7Tm705efiQzyENjwhhqNkwiww==} + lefthook-linux-x64@2.0.9: + resolution: {integrity: sha512-Xwqew12w+9HiR0bBetTgaRuR9MdHnoe8gnluUSaH/wuGhC9ChEnxRu4ckahll4/Xl75TntlkOZOMDpnYU5YeEQ==} cpu: [x64] os: [linux] - lefthook-openbsd-arm64@2.0.4: - resolution: {integrity: sha512-VmOhJO3pYzZ/1C2WFXtL/n5pq4/eYOroqJJpwTJfmCHyw4ceLACu8MDyU5AMJhGMkbL8mPxGInJKxg5xhYgGRw==} + lefthook-openbsd-arm64@2.0.9: + resolution: {integrity: sha512-5ErPjbzu2623TKPM1lOcsjzswYeEA1u1f8qPp1uvR8QgWDJlx4xdbOQErF5OsYSWhqAegxQITmrJgdPBvjWuFg==} cpu: [arm64] os: [openbsd] - lefthook-openbsd-x64@2.0.4: - resolution: {integrity: sha512-U8MZz1xlHUdflkQQ2hkMQsei6fSZbs8tuE4EjCIHWnNdnAF4V8sZ6n1KbxsJcoZXPyBZqxZSMu1o/Ye8IAMVKg==} + lefthook-openbsd-x64@2.0.9: + resolution: {integrity: sha512-DhFRJZamG3I+nTPTZV0N3N5VmB4FJSbT/oG4JYDqkvxtqwb3zAL6Q/868uVEgdZl9USxu2yRKI7RnKdllDvdzQ==} cpu: [x64] os: [openbsd] - lefthook-windows-arm64@2.0.4: - resolution: {integrity: sha512-543H3y2JAwNdvwUQ6nlNBG7rdKgoOUgzAa6pYcl6EoqicCRrjRmGhkJu7vUudkkrD2Wjm7tr9hU9poP2g5fRFQ==} + lefthook-windows-arm64@2.0.9: + resolution: {integrity: sha512-zp3mLv67+NBPuX9YrYk3CArkREoko1txAsEPyiB/NXGH3QgLHw9HbR+IeY1TslGlXvzQTBrJaiNcmsQWLFFvAQ==} cpu: [arm64] os: [win32] - lefthook-windows-x64@2.0.4: - resolution: {integrity: sha512-UDEPK9RWKm60xsNOdS/DQOdFba0SFa4w3tpFMXK1AJzmRHhosoKrorXGhtTr6kcM0MGKOtYi8GHsm++ArZ9wvQ==} + lefthook-windows-x64@2.0.9: + resolution: {integrity: sha512-1TMNYvsW4D7MD66CRXkvcVTbNCq93wTH5IjTlSSn5CtJer9PFwBMWZfeFeEBU0c0gGAUq4NmkYN2pS8RZfISvA==} cpu: [x64] os: [win32] - lefthook@2.0.4: - resolution: {integrity: sha512-GNCU2vQWM/UWjiEF23601aILi1aMbPke6viortH7wIO/oVGOCW0H6FdLez4XZDyqnHL9XkTnd0BBVrBbYVMLpA==} + lefthook@2.0.9: + resolution: {integrity: sha512-SiHbJzS6QCp9UlYh4QZ2Bu+zSNEIFTASXz03hTNL5G6frZeXH56831DDqcPEy+729eA05po3n9ByJ0cAv4yQdw==} hasBin: true lie@3.3.0: @@ -1489,8 +1480,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.12.3: - resolution: {integrity: sha512-/5rpGC0eK8LlFqsHaBmL19/PVKxu/CCt8pO1vzp9X6SDLsRDh/Ccudkf3Ur5lyaKxJz9ndAx+LaThdv0ySqB6A==} + msw@2.12.4: + resolution: {integrity: sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -1525,8 +1516,8 @@ packages: encoding: optional: true - node-releases@2.0.23: - resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -1616,16 +1607,16 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true rettime@0.7.0: resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==} - rollup@4.52.4: - resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1646,11 +1637,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -1735,8 +1721,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} @@ -1746,11 +1733,11 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.16: - resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==} + tldts-core@7.0.19: + resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} - tldts@7.0.16: - resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==} + tldts@7.0.19: + resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} hasBin: true tough-cookie@6.0.0: @@ -1782,8 +1769,8 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - type-fest@5.3.0: - resolution: {integrity: sha512-d9CwU93nN0IA1QL+GSNDdwLAu1Ew5ZjTwupvedwg3WdfoH6pIDvYQ2hV0Uc2nKBLPq7NB5apCx57MLS5qlmO5g==} + type-fest@5.3.1: + resolution: {integrity: sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==} engines: {node: '>=20'} typedoc-github-theme@0.3.1: @@ -1803,8 +1790,8 @@ packages: peerDependencies: typedoc: 0.27.x || 0.28.x - typedoc@0.28.14: - resolution: {integrity: sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==} + typedoc@0.28.15: + resolution: {integrity: sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: @@ -1824,8 +1811,8 @@ packages: until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.2.2: + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -1841,8 +1828,8 @@ packages: vite: optional: true - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.7: + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1881,23 +1868,23 @@ packages: yaml: optional: true - vitest-canvas-mock@1.1.2: - resolution: {integrity: sha512-cFPhB0CZDrpdp0ZnlvnFCuq8iKRct/RswWbhqNa2HchlKfwN6FAurLwfKWIdBrYAxIzcwIgME0Ytp/VRkV2APg==} + vitest-canvas-mock@1.1.3: + resolution: {integrity: sha512-zlKJR776Qgd+bcACPh0Pq5MG3xWq+CdkACKY/wX4Jyija0BSz8LH3aCCgwFKYFwtm565+050YFEGG9Ki0gE/Hw==} peerDependencies: vitest: ^3.0.0 || ^4.0.0 - vitest@4.0.14: - resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} + vitest@4.0.15: + resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.14 - '@vitest/browser-preview': 4.0.14 - '@vitest/browser-webdriverio': 4.0.14 - '@vitest/ui': 4.0.14 + '@vitest/browser-playwright': 4.0.15 + '@vitest/browser-preview': 4.0.15 + '@vitest/browser-webdriverio': 4.0.15 + '@vitest/ui': 4.0.15 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1997,8 +1984,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -2038,23 +2025,23 @@ snapshots: '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.4': + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -2064,19 +2051,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -2084,24 +2071,22 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} - '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} @@ -2109,11 +2094,7 @@ snapshots: '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - - '@babel/parser@7.28.4': - dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/parser@7.28.5': dependencies: @@ -2124,26 +2105,21 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -2208,238 +2184,238 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@esbuild/aix-ppc64@0.25.10': + '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.25.10': + '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.25.10': + '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.25.10': + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.25.10': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.25.10': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.25.10': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.25.10': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.25.10': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.25.10': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.25.10': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.25.10': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.25.10': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.25.10': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.25.10': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.25.10': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.25.10': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.25.10': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.25.10': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.25.10': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.25.10': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.25.10': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.25.10': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.25.10': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.25.10': + '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.25.10': + '@esbuild/win32-x64@0.25.12': optional: true - '@gerrit0/mini-shiki@3.13.0': + '@gerrit0/mini-shiki@3.19.0': dependencies: - '@shikijs/engine-oniguruma': 3.13.0 - '@shikijs/langs': 3.13.0 - '@shikijs/themes': 3.13.0 - '@shikijs/types': 3.13.0 + '@shikijs/engine-oniguruma': 3.19.0 + '@shikijs/langs': 3.19.0 + '@shikijs/themes': 3.19.0 + '@shikijs/types': 3.19.0 '@shikijs/vscode-textmate': 10.0.2 - '@inquirer/ansi@1.0.0': {} + '@inquirer/ansi@1.0.2': {} - '@inquirer/ansi@2.0.1': {} + '@inquirer/ansi@2.0.2': {} - '@inquirer/checkbox@5.0.1(@types/node@24.10.1)': + '@inquirer/checkbox@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/ansi': 2.0.1 - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/figures': 2.0.1 - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/confirm@5.1.18(@types/node@24.10.1)': + '@inquirer/confirm@5.1.21(@types/node@24.10.2)': dependencies: - '@inquirer/core': 10.2.2(@types/node@24.10.1) - '@inquirer/type': 3.0.8(@types/node@24.10.1) + '@inquirer/core': 10.3.2(@types/node@24.10.2) + '@inquirer/type': 3.0.10(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/confirm@6.0.1(@types/node@24.10.1)': + '@inquirer/confirm@6.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/core@10.2.2(@types/node@24.10.1)': + '@inquirer/core@10.3.2(@types/node@24.10.2)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@24.10.1) + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.10.2) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/core@11.0.1(@types/node@24.10.1)': + '@inquirer/core@11.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/ansi': 2.0.1 - '@inquirer/figures': 2.0.1 - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/ansi': 2.0.2 + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.2) cli-width: 4.1.0 mute-stream: 3.0.0 signal-exit: 4.1.0 wrap-ansi: 9.0.2 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/editor@5.0.1(@types/node@24.10.1)': + '@inquirer/editor@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/external-editor': 2.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/external-editor': 2.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/expand@5.0.1(@types/node@24.10.1)': + '@inquirer/expand@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/external-editor@2.0.1(@types/node@24.10.1)': + '@inquirer/external-editor@2.0.2(@types/node@24.10.2)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/figures@1.0.13': {} + '@inquirer/figures@1.0.15': {} - '@inquirer/figures@2.0.1': {} + '@inquirer/figures@2.0.2': {} - '@inquirer/input@5.0.1(@types/node@24.10.1)': + '@inquirer/input@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/number@4.0.1(@types/node@24.10.1)': + '@inquirer/number@4.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/password@5.0.1(@types/node@24.10.1)': + '@inquirer/password@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/ansi': 2.0.1 - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/prompts@8.0.1(@types/node@24.10.1)': + '@inquirer/prompts@8.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/checkbox': 5.0.1(@types/node@24.10.1) - '@inquirer/confirm': 6.0.1(@types/node@24.10.1) - '@inquirer/editor': 5.0.1(@types/node@24.10.1) - '@inquirer/expand': 5.0.1(@types/node@24.10.1) - '@inquirer/input': 5.0.1(@types/node@24.10.1) - '@inquirer/number': 4.0.1(@types/node@24.10.1) - '@inquirer/password': 5.0.1(@types/node@24.10.1) - '@inquirer/rawlist': 5.0.1(@types/node@24.10.1) - '@inquirer/search': 4.0.1(@types/node@24.10.1) - '@inquirer/select': 5.0.1(@types/node@24.10.1) + '@inquirer/checkbox': 5.0.2(@types/node@24.10.2) + '@inquirer/confirm': 6.0.2(@types/node@24.10.2) + '@inquirer/editor': 5.0.2(@types/node@24.10.2) + '@inquirer/expand': 5.0.2(@types/node@24.10.2) + '@inquirer/input': 5.0.2(@types/node@24.10.2) + '@inquirer/number': 4.0.2(@types/node@24.10.2) + '@inquirer/password': 5.0.2(@types/node@24.10.2) + '@inquirer/rawlist': 5.0.2(@types/node@24.10.2) + '@inquirer/search': 4.0.2(@types/node@24.10.2) + '@inquirer/select': 5.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/rawlist@5.0.1(@types/node@24.10.1)': + '@inquirer/rawlist@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/search@4.0.1(@types/node@24.10.1)': + '@inquirer/search@4.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/figures': 2.0.1 - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/select@5.0.1(@types/node@24.10.1)': + '@inquirer/select@5.0.2(@types/node@24.10.2)': dependencies: - '@inquirer/ansi': 2.0.1 - '@inquirer/core': 11.0.1(@types/node@24.10.1) - '@inquirer/figures': 2.0.1 - '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@inquirer/ansi': 2.0.2 + '@inquirer/core': 11.0.2(@types/node@24.10.2) + '@inquirer/figures': 2.0.2 + '@inquirer/type': 4.0.2(@types/node@24.10.2) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/type@3.0.8(@types/node@24.10.1)': + '@inquirer/type@3.0.10(@types/node@24.10.2)': optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 - '@inquirer/type@4.0.1(@types/node@24.10.1)': + '@inquirer/type@4.0.2(@types/node@24.10.2)': optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@istanbuljs/schema@0.1.3': {} @@ -2484,86 +2460,86 @@ snapshots: '@open-draft/until@2.1.0': {} - '@rollup/rollup-android-arm-eabi@4.52.4': + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true - '@rollup/rollup-android-arm64@4.52.4': + '@rollup/rollup-android-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-arm64@4.52.4': + '@rollup/rollup-darwin-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-x64@4.52.4': + '@rollup/rollup-darwin-x64@4.53.3': optional: true - '@rollup/rollup-freebsd-arm64@4.52.4': + '@rollup/rollup-freebsd-arm64@4.53.3': optional: true - '@rollup/rollup-freebsd-x64@4.52.4': + '@rollup/rollup-freebsd-x64@4.53.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.4': + '@rollup/rollup-linux-arm-musleabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.4': + '@rollup/rollup-linux-arm64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.4': + '@rollup/rollup-linux-arm64-musl@4.53.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.4': + '@rollup/rollup-linux-loong64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.4': + '@rollup/rollup-linux-ppc64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.4': + '@rollup/rollup-linux-riscv64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.4': + '@rollup/rollup-linux-riscv64-musl@4.53.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.4': + '@rollup/rollup-linux-s390x-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.4': + '@rollup/rollup-linux-x64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-musl@4.52.4': + '@rollup/rollup-linux-x64-musl@4.53.3': optional: true - '@rollup/rollup-openharmony-arm64@4.52.4': + '@rollup/rollup-openharmony-arm64@4.53.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.4': + '@rollup/rollup-win32-arm64-msvc@4.53.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.4': + '@rollup/rollup-win32-ia32-msvc@4.53.3': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.4': + '@rollup/rollup-win32-x64-gnu@4.53.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.4': + '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@shikijs/engine-oniguruma@3.13.0': + '@shikijs/engine-oniguruma@3.19.0': dependencies: - '@shikijs/types': 3.13.0 + '@shikijs/types': 3.19.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.13.0': + '@shikijs/langs@3.19.0': dependencies: - '@shikijs/types': 3.13.0 + '@shikijs/types': 3.19.0 - '@shikijs/themes@3.13.0': + '@shikijs/themes@3.19.0': dependencies: - '@shikijs/types': 3.13.0 + '@shikijs/types': 3.19.0 - '@shikijs/types@3.13.0': + '@shikijs/types@3.19.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -2572,9 +2548,10 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@types/chai@5.2.2': + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 '@types/crypto-js@4.2.2': {} @@ -2588,11 +2565,11 @@ snapshots: '@types/jsdom@27.0.0': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 - '@types/node@24.10.1': + '@types/node@24.10.2': dependencies: undici-types: 7.16.0 @@ -2602,9 +2579,11 @@ snapshots: '@types/unist@3.0.3': {} - '@vitest/coverage-istanbul@4.0.14(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1))': + '@vitest/coverage-istanbul@4.0.15(vitest@4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2))': dependencies: '@istanbuljs/schema': 0.1.3 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -2613,48 +2592,48 @@ snapshots: magicast: 0.5.1 obug: 2.1.1 tinyrainbow: 3.0.3 - vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + vitest: 4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.14': + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 - '@types/chai': 5.2.2 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(vite@7.2.7(@types/node@24.10.2)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.14 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.3(@types/node@24.10.1)(typescript@5.9.3) - vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) + msw: 2.12.4(@types/node@24.10.2)(typescript@5.9.3) + vite: 7.2.7(@types/node@24.10.2)(yaml@2.8.2) - '@vitest/pretty-format@4.0.14': + '@vitest/pretty-format@4.0.15': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.14': + '@vitest/runner@4.0.15': dependencies: - '@vitest/utils': 4.0.14 + '@vitest/utils': 4.0.15 pathe: 2.0.3 - '@vitest/snapshot@4.0.14': + '@vitest/snapshot@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.14 + '@vitest/pretty-format': 4.0.15 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.14': {} + '@vitest/spy@4.0.15': {} - '@vitest/utils@4.0.14': + '@vitest/utils@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.14 + '@vitest/pretty-format': 4.0.15 tinyrainbow: 3.0.3 acorn-jsx-walk@2.0.0: {} @@ -2694,9 +2673,11 @@ snapshots: argparse@2.0.1: {} + assertion-error@2.0.1: {} + balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.12: {} + baseline-browser-mapping@2.9.5: {} bidi-js@1.0.3: dependencies: @@ -2706,13 +2687,13 @@ snapshots: dependencies: balanced-match: 1.0.2 - browserslist@4.26.3: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.8.12 - caniuse-lite: 1.0.30001747 - electron-to-chromium: 1.5.230 - node-releases: 2.0.23 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + baseline-browser-mapping: 2.9.5 + caniuse-lite: 1.0.30001759 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.2(browserslist@4.28.1) call-bind-apply-helpers@1.0.2: dependencies: @@ -2731,7 +2712,7 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - caniuse-lite@1.0.30001747: {} + caniuse-lite@1.0.30001759: {} chai@6.2.1: {} @@ -2815,7 +2796,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - dependency-cruiser@17.3.1: + dependency-cruiser@17.3.2: dependencies: acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) @@ -2844,7 +2825,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.230: {} + electron-to-chromium@1.5.267: {} emoji-regex@10.6.0: {} @@ -2869,34 +2850,34 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.25.10: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -2910,7 +2891,7 @@ snapshots: events@3.3.0: {} - expect-type@1.2.2: {} + expect-type@1.3.0: {} fast-deep-equal@3.1.3: {} @@ -3023,15 +3004,15 @@ snapshots: transitivePeerDependencies: - encoding - i18next-korean-postposition-processor@1.0.0(i18next@25.6.3(typescript@5.9.3)): + i18next-korean-postposition-processor@1.0.0(i18next@25.7.2(typescript@5.9.3)): dependencies: - i18next: 25.6.3(typescript@5.9.3) + i18next: 25.7.2(typescript@5.9.3) i18next@22.5.1: dependencies: '@babel/runtime': 7.28.4 - i18next@25.6.3(typescript@5.9.3): + i18next@25.7.2(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 optionalDependencies: @@ -3080,11 +3061,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -3109,7 +3090,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -3166,48 +3147,48 @@ snapshots: kleur@3.0.3: {} - lefthook-darwin-arm64@2.0.4: + lefthook-darwin-arm64@2.0.9: optional: true - lefthook-darwin-x64@2.0.4: + lefthook-darwin-x64@2.0.9: optional: true - lefthook-freebsd-arm64@2.0.4: + lefthook-freebsd-arm64@2.0.9: optional: true - lefthook-freebsd-x64@2.0.4: + lefthook-freebsd-x64@2.0.9: optional: true - lefthook-linux-arm64@2.0.4: + lefthook-linux-arm64@2.0.9: optional: true - lefthook-linux-x64@2.0.4: + lefthook-linux-x64@2.0.9: optional: true - lefthook-openbsd-arm64@2.0.4: + lefthook-openbsd-arm64@2.0.9: optional: true - lefthook-openbsd-x64@2.0.4: + lefthook-openbsd-x64@2.0.9: optional: true - lefthook-windows-arm64@2.0.4: + lefthook-windows-arm64@2.0.9: optional: true - lefthook-windows-x64@2.0.4: + lefthook-windows-x64@2.0.9: optional: true - lefthook@2.0.4: + lefthook@2.0.9: optionalDependencies: - lefthook-darwin-arm64: 2.0.4 - lefthook-darwin-x64: 2.0.4 - lefthook-freebsd-arm64: 2.0.4 - lefthook-freebsd-x64: 2.0.4 - lefthook-linux-arm64: 2.0.4 - lefthook-linux-x64: 2.0.4 - lefthook-openbsd-arm64: 2.0.4 - lefthook-openbsd-x64: 2.0.4 - lefthook-windows-arm64: 2.0.4 - lefthook-windows-x64: 2.0.4 + lefthook-darwin-arm64: 2.0.9 + lefthook-darwin-x64: 2.0.9 + lefthook-freebsd-arm64: 2.0.9 + lefthook-freebsd-x64: 2.0.9 + lefthook-linux-arm64: 2.0.9 + lefthook-linux-x64: 2.0.9 + lefthook-openbsd-arm64: 2.0.9 + lefthook-openbsd-x64: 2.0.9 + lefthook-windows-arm64: 2.0.9 + lefthook-windows-x64: 2.0.9 lie@3.3.0: dependencies: @@ -3239,7 +3220,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 markdown-it@14.1.0: dependencies: @@ -3274,9 +3255,9 @@ snapshots: ms@2.1.3: {} - msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3): + msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.18(@types/node@24.10.1) + '@inquirer/confirm': 5.1.21(@types/node@24.10.2) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -3291,7 +3272,7 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 5.3.0 + type-fest: 5.3.1 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -3311,7 +3292,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.23: {} + node-releases@2.0.27: {} object-keys@1.1.1: {} @@ -3346,7 +3327,7 @@ snapshots: graphology: 0.25.4(graphology-types@0.24.8) i18next: 22.5.1 i18next-http-backend: 2.7.3 - js-yaml: 4.1.0 + js-yaml: 4.1.1 mustache: 4.2.0 papaparse: 5.5.3 webfontloader: 1.6.28 @@ -3393,7 +3374,7 @@ snapshots: rechoir@0.8.0: dependencies: - resolve: 1.22.10 + resolve: 1.22.11 regexp-tree@0.1.27: {} @@ -3401,7 +3382,7 @@ snapshots: require-from-string@2.0.2: {} - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 @@ -3409,32 +3390,32 @@ snapshots: rettime@0.7.0: {} - rollup@4.52.4: + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.4 - '@rollup/rollup-android-arm64': 4.52.4 - '@rollup/rollup-darwin-arm64': 4.52.4 - '@rollup/rollup-darwin-x64': 4.52.4 - '@rollup/rollup-freebsd-arm64': 4.52.4 - '@rollup/rollup-freebsd-x64': 4.52.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 - '@rollup/rollup-linux-arm-musleabihf': 4.52.4 - '@rollup/rollup-linux-arm64-gnu': 4.52.4 - '@rollup/rollup-linux-arm64-musl': 4.52.4 - '@rollup/rollup-linux-loong64-gnu': 4.52.4 - '@rollup/rollup-linux-ppc64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-musl': 4.52.4 - '@rollup/rollup-linux-s390x-gnu': 4.52.4 - '@rollup/rollup-linux-x64-gnu': 4.52.4 - '@rollup/rollup-linux-x64-musl': 4.52.4 - '@rollup/rollup-openharmony-arm64': 4.52.4 - '@rollup/rollup-win32-arm64-msvc': 4.52.4 - '@rollup/rollup-win32-ia32-msvc': 4.52.4 - '@rollup/rollup-win32-x64-gnu': 4.52.4 - '@rollup/rollup-win32-x64-msvc': 4.52.4 + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 safe-buffer@5.1.2: {} @@ -3451,8 +3432,6 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} - semver@7.7.3: {} set-function-length@1.2.2: @@ -3522,7 +3501,7 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.2: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: dependencies: @@ -3531,15 +3510,15 @@ snapshots: tinyrainbow@3.0.3: {} - tldts-core@7.0.16: {} + tldts-core@7.0.19: {} - tldts@7.0.16: + tldts@7.0.19: dependencies: - tldts-core: 7.0.16 + tldts-core: 7.0.19 tough-cookie@6.0.0: dependencies: - tldts: 7.0.16 + tldts: 7.0.19 tr46@0.0.3: {} @@ -3564,30 +3543,30 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - type-fest@5.3.0: + type-fest@5.3.1: dependencies: tagged-tag: 1.0.0 - typedoc-github-theme@0.3.1(typedoc@0.28.14(typescript@5.9.3)): + typedoc-github-theme@0.3.1(typedoc@0.28.15(typescript@5.9.3)): dependencies: - typedoc: 0.28.14(typescript@5.9.3) + typedoc: 0.28.15(typescript@5.9.3) - typedoc-plugin-coverage@4.0.2(typedoc@0.28.14(typescript@5.9.3)): + typedoc-plugin-coverage@4.0.2(typedoc@0.28.15(typescript@5.9.3)): dependencies: - typedoc: 0.28.14(typescript@5.9.3) + typedoc: 0.28.15(typescript@5.9.3) - typedoc-plugin-mdn-links@5.0.10(typedoc@0.28.14(typescript@5.9.3)): + typedoc-plugin-mdn-links@5.0.10(typedoc@0.28.15(typescript@5.9.3)): dependencies: - typedoc: 0.28.14(typescript@5.9.3) + typedoc: 0.28.15(typescript@5.9.3) - typedoc@0.28.14(typescript@5.9.3): + typedoc@0.28.15(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.13.0 + '@gerrit0/mini-shiki': 3.19.0 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 typescript: 5.9.3 - yaml: 2.8.1 + yaml: 2.8.2 typescript@5.9.3: {} @@ -3597,68 +3576,68 @@ snapshots: until-async@3.0.2: {} - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.2.2(browserslist@4.28.1): dependencies: - browserslist: 4.26.3 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 util-deprecate@1.0.2: {} - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.7(@types/node@24.10.2)(yaml@2.8.2)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) + vite: 7.2.7(@types/node@24.10.2)(yaml@2.8.2) transitivePeerDependencies: - supports-color - typescript - vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1): + vite@7.2.7(@types/node@24.10.2)(yaml@2.8.2): dependencies: - esbuild: 0.25.10 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.4 + rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 fsevents: 2.3.3 - yaml: 2.8.1 + yaml: 2.8.2 - vitest-canvas-mock@1.1.2(vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1)): + vitest-canvas-mock@1.1.3(vitest@4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2)): dependencies: cssfontparser: 1.2.1 moo-color: 1.0.3 - vitest: 4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1) + vitest: 4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2) - vitest@4.0.14(@types/node@24.10.1)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(yaml@2.8.1): + vitest@4.0.15(@types/node@24.10.2)(jsdom@27.3.0(postcss@8.5.6))(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.14 - '@vitest/mocker': 4.0.14(msw@2.12.3(@types/node@24.10.1)(typescript@5.9.3))(vite@7.2.4(@types/node@24.10.1)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.14 - '@vitest/runner': 4.0.14 - '@vitest/snapshot': 4.0.14 - '@vitest/spy': 4.0.14 - '@vitest/utils': 4.0.14 + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(msw@2.12.4(@types/node@24.10.2)(typescript@5.9.3))(vite@7.2.7(@types/node@24.10.2)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 - expect-type: 1.2.2 + expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.4(@types/node@24.10.1)(yaml@2.8.1) + vite: 7.2.7(@types/node@24.10.2)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.10.2 jsdom: 27.3.0(postcss@8.5.6) transitivePeerDependencies: - jiti @@ -3734,7 +3713,7 @@ snapshots: yallist@3.1.1: {} - yaml@2.8.1: {} + yaml@2.8.2: {} yargs-parser@21.1.1: {} From 7456c11be18cc678d0221d2a5a5a5f008da0395d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:31:41 -0600 Subject: [PATCH 060/101] [Refactor] Consolidate `typings/` into `@types/`, and API types (#6823) --- .dependency-cruiser.cjs | 1 + src/@types/api.ts | 147 ++++++++++++++++++ src/@types/api/pokerogue-account-api.ts | 24 --- src/@types/api/pokerogue-admin-api.ts | 32 ---- src/@types/api/pokerogue-api-types.ts | 4 - src/@types/api/pokerogue-save-data-api.ts | 8 - .../api/pokerogue-session-save-data-api.ts | 40 ----- .../api/pokerogue-system-save-data-api.ts | 20 --- src/@types/{helpers => }/enum-types.ts | 0 src/{typings => @types}/i18next.d.ts | 0 .../phaser/index.d.ts => @types/phaser.d.ts} | 0 src/@types/{api => }/pokerogue-daily-api.ts | 2 + src/@types/save-data.ts | 2 +- src/@types/save-migrators.ts | 16 ++ src/@types/session-save-migrator.ts | 6 - src/@types/settings-save-migrator.ts | 5 - src/@types/system-save-migrator.ts | 6 - src/@types/{helpers => }/type-helpers.ts | 0 src/@types/user-info.ts | 7 - src/account.ts | 2 +- src/plugins/api/pokerogue-account-api.ts | 2 +- src/plugins/api/pokerogue-admin-api.ts | 2 +- src/plugins/api/pokerogue-api.ts | 2 +- src/plugins/api/pokerogue-daily-api.ts | 6 +- src/plugins/api/pokerogue-savedata-api.ts | 2 +- .../api/pokerogue-session-savedata-api.ts | 2 +- .../api/pokerogue-system-savedata-api.ts | 2 +- .../version-migration/version-converter.ts | 4 +- .../version-migration/versions/v1_0_4.ts | 4 +- .../version-migration/versions/v1_10_0.ts | 2 +- .../version-migration/versions/v1_7_0.ts | 3 +- .../version-migration/versions/v1_8_3.ts | 2 +- .../version-migration/versions/v1_9_0.ts | 2 +- src/ui/containers/daily-run-scoreboard.ts | 3 + src/ui/handlers/admin-ui-handler.ts | 6 +- .../plugins/api/pokerogue-account-api.test.ts | 2 +- test/plugins/api/pokerogue-admin-api.test.ts | 7 +- test/plugins/api/pokerogue-api.test.ts | 2 +- test/plugins/api/pokerogue-daily-api.test.ts | 2 +- .../api/pokerogue-savedata-api.test.ts | 2 +- .../pokerogue-session-savedata-api.test.ts | 2 +- .../api/pokerogue-system-savedata-api.test.ts | 2 +- tsconfig.json | 2 +- 43 files changed, 195 insertions(+), 192 deletions(-) create mode 100644 src/@types/api.ts delete mode 100644 src/@types/api/pokerogue-account-api.ts delete mode 100644 src/@types/api/pokerogue-admin-api.ts delete mode 100644 src/@types/api/pokerogue-api-types.ts delete mode 100644 src/@types/api/pokerogue-save-data-api.ts delete mode 100644 src/@types/api/pokerogue-session-save-data-api.ts delete mode 100644 src/@types/api/pokerogue-system-save-data-api.ts rename src/@types/{helpers => }/enum-types.ts (100%) rename src/{typings => @types}/i18next.d.ts (100%) rename src/{typings/phaser/index.d.ts => @types/phaser.d.ts} (100%) rename src/@types/{api => }/pokerogue-daily-api.ts (86%) create mode 100644 src/@types/save-migrators.ts delete mode 100644 src/@types/session-save-migrator.ts delete mode 100644 src/@types/settings-save-migrator.ts delete mode 100644 src/@types/system-save-migrator.ts rename src/@types/{helpers => }/type-helpers.ts (100%) delete mode 100644 src/@types/user-info.ts diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index cb1f8f154bf..54b09beee95 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -20,6 +20,7 @@ module.exports = { comment: "Files in 'enums/' and '@types/' must only use type imports.", from: { path: ["(^|/)src/@types", "(^|/)src/enums"], + pathNot: ["(^|/)src/@types/phaser[.]d[.]ts"], }, to: { dependencyTypesNot: ["type-only"], diff --git a/src/@types/api.ts b/src/@types/api.ts new file mode 100644 index 00000000000..283cc9993c6 --- /dev/null +++ b/src/@types/api.ts @@ -0,0 +1,147 @@ +import type { SessionSaveData, SystemSaveData } from "#types/save-data"; + +export interface UserInfo { + username: string; + lastSessionSlot: number; + discordId: string; + googleId: string; + hasAdminRole: boolean; +} + +export interface TitleStatsResponse { + playerCount: number; + battleCount: number; +} + +// #region Account API + +export interface AccountInfoResponse extends UserInfo {} + +export interface AccountLoginRequest { + username: string; + password: string; +} + +export interface AccountLoginResponse { + token: string; +} + +export interface AccountRegisterRequest { + username: string; + password: string; +} + +export interface AccountChangePwRequest { + password: string; +} +export interface AccountChangePwResponse { + success: boolean; +} + +// #endregion +// #region Admin API + +export interface SearchAccountRequest { + username: string; +} + +export interface DiscordRequest extends SearchAccountRequest { + discordId: string; +} + +export interface GoogleRequest extends SearchAccountRequest { + googleId: string; +} + +export interface SearchAccountResponse { + username: string; + discordId: string; + googleId: string; + lastLoggedIn: string; + registered: string; + systemData?: SystemSaveData; +} + +/** Third party login services */ +export type AdminUiHandlerService = "discord" | "google"; +/** Mode for the admin UI handler */ +export type AdminUiHandlerServiceMode = "Link" | "Unlink"; + +export interface PokerogueAdminApiParams extends Record { + discord: DiscordRequest; + google: GoogleRequest; +} + +// #endregion + +export interface UpdateAllSavedataRequest { + system: SystemSaveData; + session: SessionSaveData; + sessionSlotId: number; + clientSessionId: string; +} + +// #region Session Save API + +export interface UpdateSessionSavedataRequest { + slot: number; + trainerId: number; + secretId: number; + clientSessionId: string; +} + +/** This is **NOT** related to {@linkcode ClearSessionSavedataRequest} */ +export interface NewClearSessionSavedataRequest { + slot: number; + isVictory: boolean; + clientSessionId: string; +} + +export interface GetSessionSavedataRequest { + slot: number; + clientSessionId: string; +} + +export interface DeleteSessionSavedataRequest { + slot: number; + clientSessionId: string; +} + +/** This is **NOT** related to {@linkcode NewClearSessionSavedataRequest} */ +export interface ClearSessionSavedataRequest { + slot: number; + trainerId: number; + clientSessionId: string; +} + +/** Pokerogue API response for path: `/savedata/session/clear` */ +export interface ClearSessionSavedataResponse { + /** Contains the error message if any occured */ + error?: string; + /** Is `true` if the request was successfully processed */ + success?: boolean; +} + +// #endregion +// #region System Save API + +export interface GetSystemSavedataRequest { + clientSessionId: string; +} + +export interface UpdateSystemSavedataRequest { + clientSessionId: string; + trainerId?: number; + secretId?: number; +} + +export interface VerifySystemSavedataRequest { + clientSessionId: string; +} + +export interface VerifySystemSavedataResponse { + valid: boolean; + systemData: SystemSaveData; +} + +// #endregion diff --git a/src/@types/api/pokerogue-account-api.ts b/src/@types/api/pokerogue-account-api.ts deleted file mode 100644 index 779592483fb..00000000000 --- a/src/@types/api/pokerogue-account-api.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { UserInfo } from "#types/user-info"; - -export interface AccountInfoResponse extends UserInfo {} - -export interface AccountLoginRequest { - username: string; - password: string; -} - -export interface AccountLoginResponse { - token: string; -} - -export interface AccountRegisterRequest { - username: string; - password: string; -} - -export interface AccountChangePwRequest { - password: string; -} -export interface AccountChangePwResponse { - success: boolean; -} diff --git a/src/@types/api/pokerogue-admin-api.ts b/src/@types/api/pokerogue-admin-api.ts deleted file mode 100644 index 7ec59f8fb6e..00000000000 --- a/src/@types/api/pokerogue-admin-api.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { SystemSaveData } from "#types/save-data"; - -export interface SearchAccountRequest { - username: string; -} - -export interface DiscordRequest extends SearchAccountRequest { - discordId: string; -} - -export interface GoogleRequest extends SearchAccountRequest { - googleId: string; -} - -export interface SearchAccountResponse { - username: string; - discordId: string; - googleId: string; - lastLoggedIn: string; - registered: string; - systemData?: SystemSaveData; -} - -/** Third party login services */ -export type AdminUiHandlerService = "discord" | "google"; -/** Mode for the admin UI handler */ -export type AdminUiHandlerServiceMode = "Link" | "Unlink"; - -export interface PokerogueAdminApiParams extends Record { - discord: DiscordRequest; - google: GoogleRequest; -} diff --git a/src/@types/api/pokerogue-api-types.ts b/src/@types/api/pokerogue-api-types.ts deleted file mode 100644 index 79755b23a54..00000000000 --- a/src/@types/api/pokerogue-api-types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface TitleStatsResponse { - playerCount: number; - battleCount: number; -} diff --git a/src/@types/api/pokerogue-save-data-api.ts b/src/@types/api/pokerogue-save-data-api.ts deleted file mode 100644 index c33d775f114..00000000000 --- a/src/@types/api/pokerogue-save-data-api.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { SessionSaveData, SystemSaveData } from "#types/save-data"; - -export interface UpdateAllSavedataRequest { - system: SystemSaveData; - session: SessionSaveData; - sessionSlotId: number; - clientSessionId: string; -} diff --git a/src/@types/api/pokerogue-session-save-data-api.ts b/src/@types/api/pokerogue-session-save-data-api.ts deleted file mode 100644 index bd606ef7e9c..00000000000 --- a/src/@types/api/pokerogue-session-save-data-api.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface UpdateSessionSavedataRequest { - slot: number; - trainerId: number; - secretId: number; - clientSessionId: string; -} - -/** This is **NOT** similar to {@linkcode ClearSessionSavedataRequest} */ -export interface NewClearSessionSavedataRequest { - slot: number; - isVictory: boolean; - clientSessionId: string; -} - -export interface GetSessionSavedataRequest { - slot: number; - clientSessionId: string; -} - -export interface DeleteSessionSavedataRequest { - slot: number; - clientSessionId: string; -} - -/** This is **NOT** similar to {@linkcode NewClearSessionSavedataRequest} */ -export interface ClearSessionSavedataRequest { - slot: number; - trainerId: number; - clientSessionId: string; -} - -/** - * Pokerogue API response for path: `/savedata/session/clear` - */ -export interface ClearSessionSavedataResponse { - /** Contains the error message if any occured */ - error?: string; - /** Is `true` if the request was successfully processed */ - success?: boolean; -} diff --git a/src/@types/api/pokerogue-system-save-data-api.ts b/src/@types/api/pokerogue-system-save-data-api.ts deleted file mode 100644 index 2e79b4fb92c..00000000000 --- a/src/@types/api/pokerogue-system-save-data-api.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { SystemSaveData } from "#types/save-data"; - -export interface GetSystemSavedataRequest { - clientSessionId: string; -} - -export interface UpdateSystemSavedataRequest { - clientSessionId: string; - trainerId?: number; - secretId?: number; -} - -export interface VerifySystemSavedataRequest { - clientSessionId: string; -} - -export interface VerifySystemSavedataResponse { - valid: boolean; - systemData: SystemSaveData; -} diff --git a/src/@types/helpers/enum-types.ts b/src/@types/enum-types.ts similarity index 100% rename from src/@types/helpers/enum-types.ts rename to src/@types/enum-types.ts diff --git a/src/typings/i18next.d.ts b/src/@types/i18next.d.ts similarity index 100% rename from src/typings/i18next.d.ts rename to src/@types/i18next.d.ts diff --git a/src/typings/phaser/index.d.ts b/src/@types/phaser.d.ts similarity index 100% rename from src/typings/phaser/index.d.ts rename to src/@types/phaser.d.ts diff --git a/src/@types/api/pokerogue-daily-api.ts b/src/@types/pokerogue-daily-api.ts similarity index 86% rename from src/@types/api/pokerogue-daily-api.ts rename to src/@types/pokerogue-daily-api.ts index 838af2a2a34..a53f87bafe9 100644 --- a/src/@types/api/pokerogue-daily-api.ts +++ b/src/@types/pokerogue-daily-api.ts @@ -1,10 +1,12 @@ import type { ScoreboardCategory } from "#ui/daily-run-scoreboard"; +/** @deprecated */ export interface GetDailyRankingsRequest { category: ScoreboardCategory; page?: number; } +/** @deprecated */ export interface GetDailyRankingsPageCountRequest { category: ScoreboardCategory; } diff --git a/src/@types/save-data.ts b/src/@types/save-data.ts index ae359c20949..eab55ab5bcf 100644 --- a/src/@types/save-data.ts +++ b/src/@types/save-data.ts @@ -131,7 +131,7 @@ export type RunHistoryData = Record; export interface RunEntry { entry: SessionSaveData; isVictory: boolean; - /*Automatically set to false at the moment - implementation TBD*/ + /** Automatically set to false at the moment - implementation TBD */ isFavorite: boolean; } diff --git a/src/@types/save-migrators.ts b/src/@types/save-migrators.ts new file mode 100644 index 00000000000..a17a3b74fab --- /dev/null +++ b/src/@types/save-migrators.ts @@ -0,0 +1,16 @@ +import type { SessionSaveData, SystemSaveData } from "#types/save-data"; + +export interface SessionSaveMigrator { + version: string; + migrate: (data: SessionSaveData) => void; +} + +export interface SettingsSaveMigrator { + version: string; + migrate: (data: object) => void; +} + +export interface SystemSaveMigrator { + version: string; + migrate: (data: SystemSaveData) => void; +} diff --git a/src/@types/session-save-migrator.ts b/src/@types/session-save-migrator.ts deleted file mode 100644 index c8f53236c63..00000000000 --- a/src/@types/session-save-migrator.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { SessionSaveData } from "./save-data"; - -export interface SessionSaveMigrator { - version: string; - migrate: (data: SessionSaveData) => void; -} diff --git a/src/@types/settings-save-migrator.ts b/src/@types/settings-save-migrator.ts deleted file mode 100644 index aae3df7cc60..00000000000 --- a/src/@types/settings-save-migrator.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface SettingsSaveMigrator { - version: string; - // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings - migrate: (data: Object) => void; -} diff --git a/src/@types/system-save-migrator.ts b/src/@types/system-save-migrator.ts deleted file mode 100644 index 4cbcc4c3e15..00000000000 --- a/src/@types/system-save-migrator.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { SystemSaveData } from "./save-data"; - -export interface SystemSaveMigrator { - version: string; - migrate: (data: SystemSaveData) => void; -} diff --git a/src/@types/helpers/type-helpers.ts b/src/@types/type-helpers.ts similarity index 100% rename from src/@types/helpers/type-helpers.ts rename to src/@types/type-helpers.ts diff --git a/src/@types/user-info.ts b/src/@types/user-info.ts deleted file mode 100644 index c8a0c6ecb26..00000000000 --- a/src/@types/user-info.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface UserInfo { - username: string; - lastSessionSlot: number; - discordId: string; - googleId: string; - hasAdminRole: boolean; -} diff --git a/src/account.ts b/src/account.ts index 5a35c31a57a..cde0706d810 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,6 +1,6 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { bypassLogin } from "#constants/app-constants"; -import type { UserInfo } from "#types/user-info"; +import type { UserInfo } from "#types/api"; import { randomString } from "#utils/common"; export let loggedInUser: UserInfo | null = null; diff --git a/src/plugins/api/pokerogue-account-api.ts b/src/plugins/api/pokerogue-account-api.ts index 22f86413618..fa9414210bc 100644 --- a/src/plugins/api/pokerogue-account-api.ts +++ b/src/plugins/api/pokerogue-account-api.ts @@ -6,7 +6,7 @@ import type { AccountLoginRequest, AccountLoginResponse, AccountRegisterRequest, -} from "#types/api/pokerogue-account-api"; +} from "#types/api"; import { removeCookie, setCookie } from "#utils/cookies"; /** diff --git a/src/plugins/api/pokerogue-admin-api.ts b/src/plugins/api/pokerogue-admin-api.ts index 106aa0d2f52..d7334b585b4 100644 --- a/src/plugins/api/pokerogue-admin-api.ts +++ b/src/plugins/api/pokerogue-admin-api.ts @@ -5,7 +5,7 @@ import type { PokerogueAdminApiParams, SearchAccountRequest, SearchAccountResponse, -} from "#types/api/pokerogue-admin-api"; +} from "#types/api"; export class PokerogueAdminApi extends ApiBase { public readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!"; diff --git a/src/plugins/api/pokerogue-api.ts b/src/plugins/api/pokerogue-api.ts index ce232685107..c87372a7597 100644 --- a/src/plugins/api/pokerogue-api.ts +++ b/src/plugins/api/pokerogue-api.ts @@ -3,7 +3,7 @@ import { PokerogueAccountApi } from "#api/pokerogue-account-api"; import { PokerogueAdminApi } from "#api/pokerogue-admin-api"; import { PokerogueDailyApi } from "#api/pokerogue-daily-api"; import { PokerogueSavedataApi } from "#api/pokerogue-savedata-api"; -import type { TitleStatsResponse } from "#types/api/pokerogue-api-types"; +import type { TitleStatsResponse } from "#types/api"; /** * A wrapper for PokéRogue API requests. diff --git a/src/plugins/api/pokerogue-daily-api.ts b/src/plugins/api/pokerogue-daily-api.ts index 5ea3846e60e..cb447913c3d 100644 --- a/src/plugins/api/pokerogue-daily-api.ts +++ b/src/plugins/api/pokerogue-daily-api.ts @@ -1,13 +1,11 @@ import { ApiBase } from "#api/api-base"; -import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/api/pokerogue-daily-api"; +import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/pokerogue-daily-api"; import type { RankingEntry } from "#ui/daily-run-scoreboard"; /** * A wrapper for daily-run PokéRogue API requests. */ export class PokerogueDailyApi extends ApiBase { - //#region Public - /** * Request the daily-run seed. * @returns The active daily-run seed as `string`. @@ -25,6 +23,7 @@ export class PokerogueDailyApi extends ApiBase { /** * Get the daily rankings for a {@linkcode ScoreboardCategory}. * @param params The {@linkcode GetDailyRankingsRequest} to send + * @deprecated */ public async getRankings(params: GetDailyRankingsRequest) { try { @@ -41,6 +40,7 @@ export class PokerogueDailyApi extends ApiBase { /** * Get the page count of the daily rankings for a {@linkcode ScoreboardCategory}. * @param params The {@linkcode GetDailyRankingsPageCountRequest} to send. + * @deprecated */ public async getRankingsPageCount(params: GetDailyRankingsPageCountRequest) { try { diff --git a/src/plugins/api/pokerogue-savedata-api.ts b/src/plugins/api/pokerogue-savedata-api.ts index f91e7bd027f..b3da2e07568 100644 --- a/src/plugins/api/pokerogue-savedata-api.ts +++ b/src/plugins/api/pokerogue-savedata-api.ts @@ -2,7 +2,7 @@ import { ApiBase } from "#api/api-base"; import { PokerogueSessionSavedataApi } from "#api/pokerogue-session-savedata-api"; import { PokerogueSystemSavedataApi } from "#api/pokerogue-system-savedata-api"; import { MAX_INT_ATTR_VALUE } from "#app/constants"; -import type { UpdateAllSavedataRequest } from "#types/api/pokerogue-save-data-api"; +import type { UpdateAllSavedataRequest } from "#types/api"; /** * A wrapper for PokéRogue savedata API requests. diff --git a/src/plugins/api/pokerogue-session-savedata-api.ts b/src/plugins/api/pokerogue-session-savedata-api.ts index e1c67ef7245..491de13781d 100644 --- a/src/plugins/api/pokerogue-session-savedata-api.ts +++ b/src/plugins/api/pokerogue-session-savedata-api.ts @@ -6,7 +6,7 @@ import type { GetSessionSavedataRequest, NewClearSessionSavedataRequest, UpdateSessionSavedataRequest, -} from "#types/api/pokerogue-session-save-data-api"; +} from "#types/api"; import type { SessionSaveData } from "#types/save-data"; /** diff --git a/src/plugins/api/pokerogue-system-savedata-api.ts b/src/plugins/api/pokerogue-system-savedata-api.ts index 137d4adb18f..7116928cf89 100644 --- a/src/plugins/api/pokerogue-system-savedata-api.ts +++ b/src/plugins/api/pokerogue-system-savedata-api.ts @@ -4,7 +4,7 @@ import type { UpdateSystemSavedataRequest, VerifySystemSavedataRequest, VerifySystemSavedataResponse, -} from "#types/api/pokerogue-system-save-data-api"; +} from "#types/api"; /** * A wrapper for PokéRogue system savedata API requests. diff --git a/src/system/version-migration/version-converter.ts b/src/system/version-migration/version-converter.ts index 75ffde9934a..512f4ac56a9 100644 --- a/src/system/version-migration/version-converter.ts +++ b/src/system/version-migration/version-converter.ts @@ -2,9 +2,7 @@ import { version } from "#package.json"; import type { SessionSaveData, SystemSaveData } from "#types/save-data"; -import type { SessionSaveMigrator } from "#types/session-save-migrator"; -import type { SettingsSaveMigrator } from "#types/settings-save-migrator"; -import type { SystemSaveMigrator } from "#types/system-save-migrator"; +import type { SessionSaveMigrator, SettingsSaveMigrator, SystemSaveMigrator } from "#types/save-migrators"; import { compareVersions } from "compare-versions"; /* diff --git a/src/system/version-migration/versions/v1_0_4.ts b/src/system/version-migration/versions/v1_0_4.ts index 5342396d576..07c7d48c747 100644 --- a/src/system/version-migration/versions/v1_0_4.ts +++ b/src/system/version-migration/versions/v1_0_4.ts @@ -5,9 +5,7 @@ import { AbilityAttr } from "#enums/ability-attr"; import { DexAttr } from "#enums/dex-attr"; import { SettingKeys } from "#system/settings"; import type { SessionSaveData, SystemSaveData } from "#types/save-data"; -import type { SessionSaveMigrator } from "#types/session-save-migrator"; -import type { SettingsSaveMigrator } from "#types/settings-save-migrator"; -import type { SystemSaveMigrator } from "#types/system-save-migrator"; +import type { SessionSaveMigrator, SettingsSaveMigrator, SystemSaveMigrator } from "#types/save-migrators"; /** * Migrate ability starter data if empty for caught species. diff --git a/src/system/version-migration/versions/v1_10_0.ts b/src/system/version-migration/versions/v1_10_0.ts index eab0b0cc78e..f81a14749d9 100644 --- a/src/system/version-migration/versions/v1_10_0.ts +++ b/src/system/version-migration/versions/v1_10_0.ts @@ -3,7 +3,7 @@ import type { MoveId } from "#enums/move-id"; import type { MoveResult } from "#enums/move-result"; import { MoveUseMode } from "#enums/move-use-mode"; import type { SessionSaveData } from "#types/save-data"; -import type { SessionSaveMigrator } from "#types/session-save-migrator"; +import type { SessionSaveMigrator } from "#types/save-migrators"; import type { TurnMove } from "#types/turn-move"; /** Prior signature of `TurnMove`; used to ensure parity */ diff --git a/src/system/version-migration/versions/v1_7_0.ts b/src/system/version-migration/versions/v1_7_0.ts index e526ccd2c2b..6d9871c6152 100644 --- a/src/system/version-migration/versions/v1_7_0.ts +++ b/src/system/version-migration/versions/v1_7_0.ts @@ -1,8 +1,7 @@ import { globalScene } from "#app/global-scene"; import { DexAttr } from "#enums/dex-attr"; import type { SessionSaveData, SystemSaveData } from "#types/save-data"; -import type { SessionSaveMigrator } from "#types/session-save-migrator"; -import type { SystemSaveMigrator } from "#types/system-save-migrator"; +import type { SessionSaveMigrator, SystemSaveMigrator } from "#types/save-migrators"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; /** diff --git a/src/system/version-migration/versions/v1_8_3.ts b/src/system/version-migration/versions/v1_8_3.ts index 0d6fd87a56c..b2696201388 100644 --- a/src/system/version-migration/versions/v1_8_3.ts +++ b/src/system/version-migration/versions/v1_8_3.ts @@ -1,7 +1,7 @@ import { DexAttr } from "#enums/dex-attr"; import { SpeciesId } from "#enums/species-id"; import type { SystemSaveData } from "#types/save-data"; -import type { SystemSaveMigrator } from "#types/system-save-migrator"; +import type { SystemSaveMigrator } from "#types/save-migrators"; import { getPokemonSpecies } from "#utils/pokemon-utils"; /** diff --git a/src/system/version-migration/versions/v1_9_0.ts b/src/system/version-migration/versions/v1_9_0.ts index eee60571884..b51e3633a0b 100644 --- a/src/system/version-migration/versions/v1_9_0.ts +++ b/src/system/version-migration/versions/v1_9_0.ts @@ -2,7 +2,7 @@ import { MoveId } from "#enums/move-id"; import { PokemonMove } from "#moves/pokemon-move"; import type { PokemonData } from "#system/pokemon-data"; import type { SessionSaveData } from "#types/save-data"; -import type { SessionSaveMigrator } from "#types/session-save-migrator"; +import type { SessionSaveMigrator } from "#types/save-migrators"; /** * Migrate all lingering rage fist data inside `CustomPokemonData`, diff --git a/src/ui/containers/daily-run-scoreboard.ts b/src/ui/containers/daily-run-scoreboard.ts index 456c8edde01..b23d6ee46bd 100644 --- a/src/ui/containers/daily-run-scoreboard.ts +++ b/src/ui/containers/daily-run-scoreboard.ts @@ -7,6 +7,7 @@ import { executeIf } from "#utils/common"; import { getEnumKeys } from "#utils/enums"; import i18next from "i18next"; +/** @deprecated */ export interface RankingEntry { rank: number; username: string; @@ -15,11 +16,13 @@ export interface RankingEntry { } // Don't forget to update translations when adding a new category +/** @deprecated */ export enum ScoreboardCategory { DAILY, WEEKLY, } +/** @deprecated */ export class DailyRunScoreboard extends Phaser.GameObjects.Container { private loadingLabel: Phaser.GameObjects.Text; private titleLabel: Phaser.GameObjects.Text; diff --git a/src/ui/handlers/admin-ui-handler.ts b/src/ui/handlers/admin-ui-handler.ts index 988c1cd5a7a..20589e9ff46 100644 --- a/src/ui/handlers/admin-ui-handler.ts +++ b/src/ui/handlers/admin-ui-handler.ts @@ -6,11 +6,7 @@ import { Button } from "#enums/buttons"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import { GameData } from "#system/game-data"; -import type { - AdminUiHandlerService, - AdminUiHandlerServiceMode, - SearchAccountResponse, -} from "#types/api/pokerogue-admin-api"; +import type { AdminUiHandlerService, AdminUiHandlerServiceMode, SearchAccountResponse } from "#types/api"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; diff --git a/test/plugins/api/pokerogue-account-api.test.ts b/test/plugins/api/pokerogue-account-api.test.ts index b830289c773..ec309b87069 100644 --- a/test/plugins/api/pokerogue-account-api.test.ts +++ b/test/plugins/api/pokerogue-account-api.test.ts @@ -2,7 +2,7 @@ import { PokerogueAccountApi } from "#api/pokerogue-account-api"; import { SESSION_ID_COOKIE_NAME } from "#app/constants"; import { initServerForApiTests } from "#test/test-utils/test-file-initialization"; import { getApiBaseUrl } from "#test/test-utils/test-utils"; -import type { AccountInfoResponse } from "#types/api/pokerogue-account-api"; +import type { AccountInfoResponse } from "#types/api"; import * as CookieUtils from "#utils/cookies"; import * as cookies from "#utils/cookies"; import { HttpResponse, http } from "msw"; diff --git a/test/plugins/api/pokerogue-admin-api.test.ts b/test/plugins/api/pokerogue-admin-api.test.ts index 1b60c4cb272..ece0333bde7 100644 --- a/test/plugins/api/pokerogue-admin-api.test.ts +++ b/test/plugins/api/pokerogue-admin-api.test.ts @@ -1,12 +1,7 @@ import { PokerogueAdminApi } from "#api/pokerogue-admin-api"; import { initServerForApiTests } from "#test/test-utils/test-file-initialization"; import { getApiBaseUrl } from "#test/test-utils/test-utils"; -import type { - DiscordRequest, - GoogleRequest, - SearchAccountRequest, - SearchAccountResponse, -} from "#types/api/pokerogue-admin-api"; +import type { DiscordRequest, GoogleRequest, SearchAccountRequest, SearchAccountResponse } from "#types/api"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/plugins/api/pokerogue-api.test.ts b/test/plugins/api/pokerogue-api.test.ts index afd7b3dd608..73d11c26ba6 100644 --- a/test/plugins/api/pokerogue-api.test.ts +++ b/test/plugins/api/pokerogue-api.test.ts @@ -1,7 +1,7 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { initServerForApiTests } from "#test/test-utils/test-file-initialization"; import { getApiBaseUrl } from "#test/test-utils/test-utils"; -import type { TitleStatsResponse } from "#types/api/pokerogue-api-types"; +import type { TitleStatsResponse } from "#types/api"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/plugins/api/pokerogue-daily-api.test.ts b/test/plugins/api/pokerogue-daily-api.test.ts index ef5dfddada5..25f927a8766 100644 --- a/test/plugins/api/pokerogue-daily-api.test.ts +++ b/test/plugins/api/pokerogue-daily-api.test.ts @@ -1,7 +1,7 @@ import { PokerogueDailyApi } from "#api/pokerogue-daily-api"; import { initServerForApiTests } from "#test/test-utils/test-file-initialization"; import { getApiBaseUrl } from "#test/test-utils/test-utils"; -import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/api/pokerogue-daily-api"; +import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#types/pokerogue-daily-api"; import { type RankingEntry, ScoreboardCategory } from "#ui/daily-run-scoreboard"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; diff --git a/test/plugins/api/pokerogue-savedata-api.test.ts b/test/plugins/api/pokerogue-savedata-api.test.ts index 5dbaf9ff542..98ecd182ca9 100644 --- a/test/plugins/api/pokerogue-savedata-api.test.ts +++ b/test/plugins/api/pokerogue-savedata-api.test.ts @@ -1,7 +1,7 @@ import { PokerogueSavedataApi } from "#api/pokerogue-savedata-api"; import { initServerForApiTests } from "#test/test-utils/test-file-initialization"; import { getApiBaseUrl } from "#test/test-utils/test-utils"; -import type { UpdateAllSavedataRequest } from "#types/api/pokerogue-save-data-api"; +import type { UpdateAllSavedataRequest } from "#types/api"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/plugins/api/pokerogue-session-savedata-api.test.ts b/test/plugins/api/pokerogue-session-savedata-api.test.ts index d91db4425cb..35058e94884 100644 --- a/test/plugins/api/pokerogue-session-savedata-api.test.ts +++ b/test/plugins/api/pokerogue-session-savedata-api.test.ts @@ -8,7 +8,7 @@ import type { GetSessionSavedataRequest, NewClearSessionSavedataRequest, UpdateSessionSavedataRequest, -} from "#types/api/pokerogue-session-save-data-api"; +} from "#types/api"; import type { SessionSaveData } from "#types/save-data"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; diff --git a/test/plugins/api/pokerogue-system-savedata-api.test.ts b/test/plugins/api/pokerogue-system-savedata-api.test.ts index 3480b00b206..f17bf4f47e7 100644 --- a/test/plugins/api/pokerogue-system-savedata-api.test.ts +++ b/test/plugins/api/pokerogue-system-savedata-api.test.ts @@ -6,7 +6,7 @@ import type { UpdateSystemSavedataRequest, VerifySystemSavedataRequest, VerifySystemSavedataResponse, -} from "#types/api/pokerogue-system-save-data-api"; +} from "#types/api"; import type { SystemSaveData } from "#types/save-data"; import { HttpResponse, http } from "msw"; import type { SetupServerApi } from "msw/node"; diff --git a/tsconfig.json b/tsconfig.json index bf42d7204ea..c48b17ef128 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -61,7 +61,7 @@ "./src/system/*.ts" ], "#trainers/*": ["./src/data/trainers/*.ts"], - "#types/*": ["./src/@types/helpers/*.ts", "./src/@types/*.ts", "./src/typings/phaser/*.ts"], + "#types/*": ["./src/@types/*.ts"], "#ui/*": [ "./src/ui/battle-info/*.ts", "./src/ui/containers/*.ts", From 95c85f68af5752cc8e4bb2d85f5bdb27ad5fa799 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 13 Dec 2025 13:35:28 -0500 Subject: [PATCH 061/101] [Balance] Update Invalid Sketch Moves - Removed Chatter, Revival Blessing, and Tera Starstorm from being invalid moves to be gained with the move Sketch This seems to have been an oversight when implementing the list to work with the game, as there is no reason to restrict these with the game systems here. --- src/data/moves/invalid-moves.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/data/moves/invalid-moves.ts b/src/data/moves/invalid-moves.ts index e55eedc29aa..98ab419ab46 100644 --- a/src/data/moves/invalid-moves.ts +++ b/src/data/moves/invalid-moves.ts @@ -259,15 +259,10 @@ export const noAbilityTypeOverrideMoves: ReadonlySet = new Set([ /** Set of all moves that cannot be copied by {@linkcode MoveId.SKETCH}. */ export const invalidSketchMoves: ReadonlySet = new Set([ MoveId.NONE, - MoveId.CHATTER, MoveId.MIRROR_MOVE, MoveId.SLEEP_TALK, MoveId.STRUGGLE, MoveId.SKETCH, - MoveId.REVIVAL_BLESSING, - MoveId.TERA_STARSTORM, - MoveId.BREAKNECK_BLITZ__PHYSICAL, - MoveId.BREAKNECK_BLITZ__SPECIAL, ]); /** Set of all moves that cannot be locked into by {@linkcode MoveId.ENCORE}. */ From 37fdb18391e6c164f9a2bf46197632c2704fe410 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 13 Dec 2025 13:37:30 -0500 Subject: [PATCH 062/101] Update Locales --- locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales b/locales index 967f2eb12a8..646984ca220 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit 967f2eb12a86c6fcc9292f6b1d2fadb66a8f197b +Subproject commit 646984ca22006c424bb477391e334bb480234da3 From 0db8454af154dfbf943b5e9dab55e53284705914 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:40:17 -0800 Subject: [PATCH 063/101] [i18n] Update locales submodule --- locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales b/locales index 646984ca220..3a9418f4083 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit 646984ca22006c424bb477391e334bb480234da3 +Subproject commit 3a9418f40832eaf86c6b4161dfc60562ae9bf756 From 86c1c5f68da0513e8907e1b10ff23eb38bb1a64d Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Tue, 16 Dec 2025 06:18:36 +0100 Subject: [PATCH 064/101] [Bug] Stop AI from Terastallizing after reviving (#6848) --- src/field/trainer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 7386c2d9e17..2504801d1e4 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -793,6 +793,7 @@ export class Trainer extends Phaser.GameObjects.Container { this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA && !pokemon.isTerastallized && this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex) + && !globalScene.currentBattle.enemyFaintsHistory.some(f => f.pokemon.id === pokemon.id) ) { return true; } From 943ddf8afcbc206d73a672333f1c27309ec3e790 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:02:33 -0500 Subject: [PATCH 065/101] [Dev] Enable type-checking for `typedoc.config.js` (#6818) --- typedoc.config.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/typedoc.config.js b/typedoc.config.js index bf1f0860d21..4bf52204514 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -4,12 +4,16 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ + +// @ts-check +/// + import { globSync } from "node:fs"; const dryRun = !!process.env.DRY_RUN?.match(/true/gi); /** - * + * */ const config = { entryPoints: ["./src", "./test/test-utils"], @@ -48,6 +52,7 @@ const config = { out: process.env.CI ? "/tmp/docs" : "./typedoc", name: "PokéRogue", readme: "./README.md", + projectDocuments: ["docs/*.md, CONTRIBUTING.md"], coverageLabel: "Documented", coverageSvgWidth: 120, // Increased from 104 baseline due to adding 2 extra letters favicon: "./favicon.ico", From bde0f65eedbff4f8bf380fdc49bb0328e18df8ce Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 16 Dec 2025 23:36:39 -0600 Subject: [PATCH 066/101] [Dev] Enable typechecking for Vite config files (#6853) --- tsconfig.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index c48b17ef128..c27cd5126b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -78,12 +78,5 @@ }, // Exclude checking for script JS files as those are covered by the folder's `jsconfig.json` "include": ["**/*.ts", "**/*.d.ts"], - "exclude": [ - "node_modules", - "dist", - "vite.config.ts", - "vitest.config.ts", - "vitest.workspace.ts", - "assets/service-worker.js" - ] + "exclude": ["node_modules", "dist"] } From 7280b997e811755a1604e699004846467c14deca Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:19:36 +0100 Subject: [PATCH 067/101] [Docs] Dynamic copyright year in Typedoc footer (#6854) --- typedoc.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typedoc.config.js b/typedoc.config.js index 4bf52204514..cdb0c7dcaad 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -57,7 +57,7 @@ const config = { coverageSvgWidth: 120, // Increased from 104 baseline due to adding 2 extra letters favicon: "./favicon.ico", theme: "typedoc-github-theme", - customFooterHtml: "

Copyright Pagefault Games 2025

", + customFooterHtml: `

Copyright Pagefault Games ${new Date().getFullYear() === 2025 ? "2025" : "2025 - " + new Date().getFullYear()}

`, customFooterHtmlDisableWrapper: true, navigationLinks: { GitHub: "https://github.com/pagefaultgames/pokerogue", From b2b8150856f6f02044b8fb17a35321167ed104d4 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:31:00 -0500 Subject: [PATCH 068/101] [Dev] Add biome rules `noProto` and `useFind` (#6838) --- biome.jsonc | 2 ++ src/data/arena-tag.ts | 2 +- src/data/mystery-encounters/mystery-encounter.ts | 2 +- src/field/pokemon.ts | 2 +- src/ui/handlers/party-ui-handler.ts | 2 +- .../encounters/uncommon-breed-encounter.test.ts | 4 ++-- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index d2678fef289..93660a05283 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -231,6 +231,8 @@ "noBarrelFile": "error" }, "nursery": { + "noProto": "error", + "useFind": "error", "noUselessUndefined": "error", "useMaxParams": { "level": "info", // TODO: Change to "error"... eventually... diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 5d6b77797a8..00159f4736a 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1532,7 +1532,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag { // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all const setter = globalScene .getField(true) - .filter(p => p.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0]; + .find(p => p.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)); // Setter may not exist if both NG Pokemon faint simultaneously if (setter == null) { return; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index caa5347ec97..a4db6bb206e 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -347,7 +347,7 @@ export class MysteryEncounter implements IMysteryEncounter { if (activeMon.length > 0) { this.primaryPokemon = activeMon[0]; } else { - this.primaryPokemon = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle())[0]; + this.primaryPokemon = globalScene.getPlayerParty().find(p => p.isAllowedInBattle()); } return true; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 075b25bd62e..bc51389fdfc 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6087,7 +6087,7 @@ export class PlayerPokemon extends Pokemon { }); }; if (preEvolution.speciesId === SpeciesId.GIMMIGHOUL) { - const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null; + const evotracker = this.getHeldItems().find(m => m instanceof EvoTrackerModifier) ?? null; if (evotracker) { globalScene.removeModifier(evotracker); } diff --git a/src/ui/handlers/party-ui-handler.ts b/src/ui/handlers/party-ui-handler.ts index 9e30f68e0a2..477ef2bf1ef 100644 --- a/src/ui/handlers/party-ui-handler.ts +++ b/src/ui/handlers/party-ui-handler.ts @@ -602,7 +602,7 @@ export class PartyUiHandler extends MessageUiHandler { ); } const hasMatchingModifier = matchingModifiers.some(m => m !== undefined); // checks if any items match - const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us + const partySlot = this.partySlots.find(m => m.getPokemon() === newPokemon)!; // this gets pokemon [p] for us if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer if (hasMatchingModifier) { diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index e5b086ceba9..021aa336205 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -123,7 +123,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); - const statStagePhases = unshiftPhaseSpy.mock.calls.filter(p => p[0] instanceof StatStageChangePhase)[0][0] as any; + const statStagePhases = unshiftPhaseSpy.mock.calls.find(p => p[0] instanceof StatStageChangePhase)?.[0] as any; expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); // Should have used its egg move pre-battle @@ -150,7 +150,7 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); - const statStagePhases = unshiftPhaseSpy.mock.calls.filter(p => p[0] instanceof StatStageChangePhase)[0][0] as any; + const statStagePhases = unshiftPhaseSpy.mock.calls.find(p => p[0] instanceof StatStageChangePhase)?.[0] as any; expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); // Should have used its egg move pre-battle From f48ec4c51ec1b64f89213699ff10ed3755d845f1 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:39:30 -0500 Subject: [PATCH 069/101] [Dev] Ensure `i18n` module is initialized immediately when imported (#6317) * [Dev] Ensure `i18n` module is initialized immediately when imported * Fixed missing await? * Update src/main.ts * Update init.ts * Update src/main.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/plugins/i18n.ts * Update trainer-config.ts * ran biome & made `@module` comment * Update src/plugins/i18n.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/plugins/i18n.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fixed import typo * flubber * Ran Biome * foo * Remove default re-export of `i18next` * Update i18n.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * fixde import issues * Move `i18n` initialization to `main.ts` from `init.ts` * Remove some redundant & incorrect comments from `trainer-config.ts` * Fix tests * Apply Biome --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle.ts | 2 +- src/data/battler-tags.ts | 2 +- .../encounters/berries-abound-encounter.ts | 2 +- .../encounters/delibirdy-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 2 +- src/data/trainers/trainer-config.ts | 71 +------ src/field/trainer.ts | 6 - src/main.ts | 3 +- src/phases/select-target-phase.ts | 2 +- src/plugins/i18n.ts | 173 ++++++++---------- src/ui/text.ts | 2 +- test/abilities/ability-timing.test.ts | 2 +- test/items/light-ball.test.ts | 2 +- test/items/metal-powder.test.ts | 2 +- test/items/quick-powder.test.ts | 2 +- test/items/thick-club.test.ts | 2 +- test/items/toxic-orb.test.ts | 2 +- test/setup/vitest.setup.ts | 2 + test/test-utils/test-file-initialization.ts | 13 +- 20 files changed, 99 insertions(+), 197 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index 5a1856ff91e..09e6e7141b5 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -18,7 +18,6 @@ import { Trainer } from "#field/trainer"; import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "#modifiers/modifier"; import type { CustomModifierSettings } from "#modifiers/modifier-type"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; -import i18next from "#plugins/i18n"; import { MusicPreference } from "#system/settings"; import { trainerConfigs } from "#trainers/trainer-config"; import type { TurnMove } from "#types/turn-move"; @@ -33,6 +32,7 @@ import { } from "#utils/common"; import { getEnumValues } from "#utils/enums"; import { randSeedUniqueItem } from "#utils/random"; +import i18next from "i18next"; export interface TurnCommand { command: Command; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 1d493908010..3a294f05c21 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -82,7 +82,6 @@ import type { Move } from "#moves/move"; import type { MoveEffectPhase } from "#phases/move-effect-phase"; import type { MovePhase } from "#phases/move-phase"; import type { StatStageChangeCallback } from "#phases/stat-stage-change-phase"; -import i18next from "#plugins/i18n"; import type { AbilityBattlerTagType, BattlerTagData, @@ -103,6 +102,7 @@ import type { Mutable } from "#types/type-helpers"; import { coerceArray } from "#utils/array"; import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#utils/common"; import { toCamelCase } from "#utils/strings"; +import i18next from "i18next"; /** Interface containing the serializable fields of `BattlerTag` */ interface BaseBattlerTag { diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 41a3f7dd55a..5124ae149d9 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -34,10 +34,10 @@ import { import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import i18next from "#plugins/i18n"; import { PokemonData } from "#system/pokemon-data"; import { randSeedItem } from "#utils/common"; import { getEnumValues } from "#utils/enums"; +import i18next from "i18next"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/berriesAbound"; diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 8cd4c8bee66..aede4c1568a 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -32,10 +32,10 @@ import { HeldItemRequirement, MoneyRequirement, } from "#mystery-encounters/mystery-encounter-requirements"; -import i18next from "#plugins/i18n"; import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import { randSeedItem } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import i18next from "i18next"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/delibirdy"; 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 80eeeec6162..0b9ed1fe92e 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -26,9 +26,9 @@ import { import { applyModifierTypeToPlayerPokemon } from "#mystery-encounters/encounter-pokemon-utils"; import { type MysteryEncounter, MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import i18next from "#plugins/i18n"; import { randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import i18next from "i18next"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/trashToTreasure"; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 16c4d681e42..0e90c9c562f 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -35,7 +35,6 @@ import { import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; -import i18next from "#plugins/i18n"; import { achvs } from "#system/achv"; import { PokemonData } from "#system/pokemon-data"; import { trainerConfigs } from "#trainers/trainer-config"; @@ -43,6 +42,7 @@ import { TrainerPartyTemplate } from "#trainers/trainer-party-template"; import type { HeldModifierConfig } from "#types/held-modifier-config"; import { NumberHolder, randSeedInt, randSeedShuffle } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; +import i18next from "i18next"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/weirdDream"; diff --git a/src/data/trainers/trainer-config.ts b/src/data/trainers/trainer-config.ts index 44ff3b8092b..3c2899dd23f 100644 --- a/src/data/trainers/trainer-config.ts +++ b/src/data/trainers/trainer-config.ts @@ -21,7 +21,6 @@ import { TrainerVariant } from "#enums/trainer-variant"; import type { EnemyPokemon } from "#field/pokemon"; import type { SpeciesStatBoosterModifier } from "#modifiers/modifier"; import { PokemonMove } from "#moves/pokemon-move"; -import { getIsInitialized, initI18n } from "#plugins/i18n"; import type { EvilTeam } from "#trainers/evil-admin-trainer-pools"; import { evilAdminTrainerPools } from "#trainers/evil-admin-trainer-pools"; import { @@ -211,14 +210,8 @@ export class TrainerConfig { setName(name: string): TrainerConfig { if (name === "Finn") { // Give the rival a localized name - // First check if i18n is initialized - if (!getIsInitialized()) { - initI18n(); - } // This is only the male name, because the female name is handled in a different function (setHasGenders) - if (name === "Finn") { - name = i18next.t("trainerNames:rival"); - } + name = i18next.t("trainerNames:rival"); } this.name = name; @@ -235,11 +228,6 @@ export class TrainerConfig { } setTitle(title: string): TrainerConfig { - // First check if i18n is initialized - if (!getIsInitialized()) { - initI18n(); - } - title = toCamelCase(title); // Get the title from the i18n file @@ -328,11 +316,6 @@ export class TrainerConfig { setHasGenders(nameFemale?: string, femaleEncounterBgm?: TrainerType | string): TrainerConfig { // If the female name is 'Ivy' (the rival), assign a localized name. if (nameFemale === "Ivy") { - // Check if the internationalization (i18n) system is initialized. - if (!getIsInitialized()) { - // Initialize the i18n system if it is not already initialized. - initI18n(); - } // Set the localized name for the female rival. this.nameFemale = i18next.t("trainerNames:rivalFemale"); } else { @@ -406,11 +389,6 @@ export class TrainerConfig { * @returns The updated TrainerConfig instance. */ setDoubleTitle(titleDouble: string): TrainerConfig { - // First check if i18n is initialized - if (!getIsInitialized()) { - initI18n(); - } - titleDouble = toCamelCase(titleDouble); // Get the title from the i18n file @@ -595,10 +573,6 @@ export class TrainerConfig { * @returns The updated TrainerConfig instance. */ initForEvilTeamAdmin(title: string, poolName: EvilTeam, specialtyType?: PokemonType): TrainerConfig { - if (!getIsInitialized()) { - initI18n(); - } - if (specialtyType != null) { this.setSpecialtyType(specialtyType); } @@ -627,10 +601,6 @@ export class TrainerConfig { * @returns The updated TrainerConfig instance. */ initForStatTrainer(_isMale = false): TrainerConfig { - if (!getIsInitialized()) { - initI18n(); - } - this.setPartyTemplates(trainerPartyTemplates.ELITE_FOUR); const nameForCall = toCamelCase(this.name); @@ -659,9 +629,6 @@ export class TrainerConfig { rematch = false, specialtyType?: PokemonType, ): TrainerConfig { - if (!getIsInitialized()) { - initI18n(); - } if (rematch) { this.setPartyTemplates(trainerPartyTemplates.ELITE_FOUR); } else { @@ -703,11 +670,6 @@ export class TrainerConfig { ignoreMinTeraWave = false, teraSlot?: number, ): TrainerConfig { - // Check if the internationalization (i18n) system is initialized. - if (!getIsInitialized()) { - initI18n(); - } - // Set the function to generate the Gym Leader's party template. this.setPartyTemplateFunc(getGymLeaderPartyTemplate); @@ -760,11 +722,6 @@ export class TrainerConfig { specialtyType?: PokemonType, teraSlot?: number, ): TrainerConfig { - // Check if the internationalization (i18n) system is initialized. - if (!getIsInitialized()) { - initI18n(); - } - // Set the party templates for the Elite Four. this.setPartyTemplates(trainerPartyTemplates.ELITE_FOUR); @@ -811,11 +768,6 @@ export class TrainerConfig { * @returns The updated TrainerConfig instance. */ initForChampion(isMale: boolean): TrainerConfig { - // Check if the internationalization (i18n) system is initialized. - if (!getIsInitialized()) { - initI18n(); - } - // Set the party templates for the Champion. this.setPartyTemplates(trainerPartyTemplates.CHAMPION); @@ -846,10 +798,6 @@ export class TrainerConfig { * @returns The updated TrainerConfig instance. */ setLocalizedName(name: string): TrainerConfig { - // Check if the internationalization (i18n) system is initialized. - if (!getIsInitialized()) { - initI18n(); - } this.name = i18next.t(`trainerNames:${toCamelCase(name)}`); return this; } @@ -863,33 +811,20 @@ export class TrainerConfig { getTitle(trainerSlot: TrainerSlot = TrainerSlot.NONE, variant: TrainerVariant): string { const ret = this.name; - // Check if the variant is double and the name for double exists if (!trainerSlot && variant === TrainerVariant.DOUBLE && this.nameDouble) { return this.nameDouble; } - // Female variant if (this.hasGenders) { - // If the name is already set if (this.nameFemale) { - // Check if the variant is either female or this is for the partner in a double battle if ( variant === TrainerVariant.FEMALE || (variant === TrainerVariant.DOUBLE && trainerSlot === TrainerSlot.TRAINER_PARTNER) ) { return this.nameFemale; } - } - // Check if !variant is true, if so return the name, else return the name with _female appended - else if (variant) { - if (!getIsInitialized()) { - initI18n(); - } - // Check if the female version exists in the i18n file - if (i18next.exists(`trainerClasses:${toCamelCase(this.name)}Female`)) { - // If it does, return - return ret + "Female"; - } + } else if (variant && i18next.exists(`trainerClasses:${toCamelCase(this.name)}Female`)) { + return ret + "Female"; } } diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 2504801d1e4..5c76264f1dc 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -13,7 +13,6 @@ import { TrainerType } from "#enums/trainer-type"; import { TrainerVariant } from "#enums/trainer-variant"; import type { EnemyPokemon } from "#field/pokemon"; import type { PersistentModifier } from "#modifiers/modifier"; -import { getIsInitialized, initI18n } from "#plugins/i18n"; import type { TrainerConfig } from "#trainers/trainer-config"; import { trainerConfigs } from "#trainers/trainer-config"; import { TrainerPartyCompoundTemplate, type TrainerPartyTemplate } from "#trainers/trainer-party-template"; @@ -174,11 +173,6 @@ export class Trainer extends Phaser.GameObjects.Container { if (this.name) { // If the title should be included. if (includeTitle) { - // Check if the internationalization (i18n) system is initialized. - if (!getIsInitialized()) { - // Initialize the i18n system if it is not already initialized. - initI18n(); - } // Get the localized trainer class name from the i18n file and set it as the title. // This is used for trainer class names, not titles like "Elite Four, Champion, etc." title = i18next.t(`trainerClasses:${toCamelCase(name)}`); diff --git a/src/main.ts b/src/main.ts index 96540e74587..36c897cafdd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import "#app/polyfills"; // All polyfills MUST be loaded first for side effects +import "#plugins/i18n"; // Initializes i18n on import import { InvertPostFX } from "#app/pipelines/invert"; -import { initI18n } from "#app/plugins/i18n"; import { isBeta, isDev } from "#constants/app-constants"; import { version } from "#package.json"; import Phaser from "phaser"; @@ -31,7 +31,6 @@ window.addEventListener("unhandledrejection", event => { }); async function startGame(gameManifest?: Record): Promise { - await initI18n(); const LoadingScene = (await import("./loading-scene")).LoadingScene; const BattleScene = (await import("./battle-scene")).BattleScene; const game = new Phaser.Game({ diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index 2f439419cd0..194bc2e38b4 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -4,7 +4,7 @@ import type { BattlerIndex } from "#enums/battler-index"; import { Command } from "#enums/command"; import { UiMode } from "#enums/ui-mode"; import { PokemonPhase } from "#phases/pokemon-phase"; -import i18next from "#plugins/i18n"; +import i18next from "i18next"; export class SelectTargetPhase extends PokemonPhase { public readonly phaseName = "SelectTargetPhase"; diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 4e5cb214091..fadfaa38168 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -3,7 +3,7 @@ import { toKebabCase } from "#utils/strings"; import i18next from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import HttpBackend from "i18next-http-backend"; -import processor, { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; +import processor from "i18next-korean-postposition-processor"; import { namespaceMap } from "./utils-plugins"; //#region Interfaces/Types @@ -16,8 +16,6 @@ interface LoadingFontFaceProperty { //#region Constants -let isInitialized = false; - const unicodeRanges = { fullwidth: "U+FF00-FFEF", hangul: "U+1100-11FF,U+3130-318F,U+A960-A97F,U+AC00-D7AF,U+D7B0-D7FF", @@ -147,107 +145,90 @@ function i18nMoneyFormatter(amount: any): string { return `@[MONEY]{${i18next.t("common:money", { amount })}}`; } +// assigned during post-processing in #app/plugins/vite/namespaces-i18n-plugin.ts const nsEn: string[] = []; //#region Exports -/** - * Initialize i18n with fonts +/* + * i18next is a localization library for maintaining and using translation resources. + * + * Q: How do I add a new language? + * A: To add a new language, create a new folder in the locales directory with the language code. + * Each language folder should contain a file for each namespace (ex. menu.ts) with the translations. + * Don't forget to declare new language in `supportedLngs` i18next initializer + * + * Q: How do I add a new namespace? + * A: To add a new namespace, create a new file .json in each language folder with the translations. + * The expected format for the file-name is kebab-case {@link https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case} + * If you want the namespace name to be different from the file name, configure it in namespace-map.ts. + * Then update the config file for that language in its locale directory + * and the CustomTypeOptions interface in the @types/i18next.d.ts file. + * + * Q: How do I make a language selectable in the settings? + * A: In src/system/settings.ts, add a new case to the Setting.Language switch statement. */ -export async function initI18n(): Promise { - // Prevent reinitialization - if (isInitialized) { - return; - } - isInitialized = true; - /** - * i18next is a localization library for maintaining and using translation resources. - * - * Q: How do I add a new language? - * A: To add a new language, create a new folder in the locales directory with the language code. - * Each language folder should contain a file for each namespace (ex. menu.ts) with the translations. - * Don't forget to declare new language in `supportedLngs` i18next initializer - * - * Q: How do I add a new namespace? - * A: To add a new namespace, create a new file .json in each language folder with the translations. - * The expected format for the file-name is kebab-case {@link https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case} - * If you want the namespace name to be different from the file name, configure it in namespacemap.ts. - * Then update the config file for that language in its locale directory - * and the CustomTypeOptions interface in the @types/i18next.d.ts file. - * - * Q: How do I make a language selectable in the settings? - * A: In src/system/settings.ts, add a new case to the Setting.Language switch statement. - */ - - i18next.use(HttpBackend); - i18next.use(LanguageDetector); - i18next.use(processor); - i18next.use(new KoreanPostpositionProcessor()); - await i18next.init({ - fallbackLng: { - "es-419": ["es-ES", "en"], - default: ["en"], - }, - supportedLngs: [ - "en", - "es-ES", - "es-419", // LATAM Spanish - "fr", - "it", - "de", - "zh-Hans", - "zh-Hant", - "pt-BR", - "ko", - "ja", - "ca", - "da", - "tr", - "ro", - "ru", - "tl", - "nb-NO", - ], - backend: { - loadPath(lng: string, [ns]: string[]) { - // Use namespace maps where required - let fileName: string; - if (namespaceMap[ns]) { - fileName = namespaceMap[ns]; - } else if (ns.startsWith("mysteryEncounters/")) { - fileName = toKebabCase(ns + "-dialogue"); // mystery-encounters/a-trainers-test-dialogue - } else { - fileName = toKebabCase(ns); - } - // ex: "./locales/en/move-anims" - return `./locales/${lng}/${fileName}.json?v=${pkg.version}`; +await i18next + .use(HttpBackend) + .use(LanguageDetector) + .use(processor) + .init( + { + fallbackLng: { + "es-419": ["es-ES", "en"], + default: ["en"], }, + supportedLngs: [ + "en", + "es-ES", + "es-419", // LATAM Spanish + "fr", + "it", + "de", + "zh-Hans", + "zh-Hant", + "pt-BR", + "ko", + "ja", + "ca", + "da", + "tr", + "ro", + "ru", + "tl", + "nb-NO", + ], + backend: { + loadPath(lng: string, [ns]: string[]) { + // Use namespace maps where required + let fileName: string; + if (namespaceMap[ns]) { + fileName = namespaceMap[ns]; + } else if (ns.startsWith("mysteryEncounters/")) { + fileName = toKebabCase(ns + "-dialogue"); // mystery-encounters/a-trainers-test-dialogue + } else { + fileName = toKebabCase(ns); + } + // ex: "./locales/en/move-anims" + return `./locales/${lng}/${fileName}.json?v=${pkg.version}`; + }, + }, + defaultNS: "menu", + detection: { + lookupLocalStorage: "prLang", + }, + ns: nsEn, + debug: import.meta.env.VITE_I18N_DEBUG === "1", + interpolation: { + escapeValue: false, + }, + postProcess: ["korean-postposition"], }, - defaultNS: "menu", - ns: nsEn, // assigned with #app/plugins/vite/namespaces-i18n-plugin.ts - detection: { - lookupLocalStorage: "prLang", + async () => { + i18next.services.formatter?.add("money", i18nMoneyFormatter); + await initFonts(localStorage.getItem("prLang") ?? undefined); }, - debug: Number(import.meta.env.VITE_I18N_DEBUG) === 1, - interpolation: { - escapeValue: false, - }, - postProcess: ["korean-postposition"], - }); - - if (i18next.services.formatter) { - i18next.services.formatter.add("money", i18nMoneyFormatter); - } - - await initFonts(localStorage.getItem("prLang") ?? undefined); -} - -export function getIsInitialized(): boolean { - return isInitialized; -} - -// biome-ignore lint/style/noDefaultExport: necessary for i18next usage -export default i18next; + ); //#endregion diff --git a/src/ui/text.ts b/src/ui/text.ts index 87444fd2c7c..b00d8057d29 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -3,8 +3,8 @@ import { EggTier } from "#enums/egg-type"; import { ModifierTier } from "#enums/modifier-tier"; import { TextStyle } from "#enums/text-style"; import { UiTheme } from "#enums/ui-theme"; -import i18next from "#plugins/i18n"; import type { TextStyleOptions } from "#types/ui"; +import i18next from "i18next"; import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import type InputText from "phaser3-rex-plugins/plugins/inputtext"; diff --git a/test/abilities/ability-timing.test.ts b/test/abilities/ability-timing.test.ts index f5315d2b80e..1ca3efc2069 100644 --- a/test/abilities/ability-timing.test.ts +++ b/test/abilities/ability-timing.test.ts @@ -4,8 +4,8 @@ import { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; import { CommandPhase } from "#phases/command-phase"; import { TurnInitPhase } from "#phases/turn-init-phase"; -import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/items/light-ball.test.ts b/test/items/light-ball.test.ts index 280b70f3d5a..4d39d663ff9 100644 --- a/test/items/light-ball.test.ts +++ b/test/items/light-ball.test.ts @@ -2,9 +2,9 @@ import { modifierTypes } from "#data/data-lists"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#modifiers/modifier"; -import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/items/metal-powder.test.ts b/test/items/metal-powder.test.ts index 1a749f0ac3d..09ca8bd071b 100644 --- a/test/items/metal-powder.test.ts +++ b/test/items/metal-powder.test.ts @@ -2,9 +2,9 @@ import { modifierTypes } from "#data/data-lists"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#modifiers/modifier"; -import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/items/quick-powder.test.ts b/test/items/quick-powder.test.ts index 0295361ed13..568f212aeb1 100644 --- a/test/items/quick-powder.test.ts +++ b/test/items/quick-powder.test.ts @@ -2,9 +2,9 @@ import { modifierTypes } from "#data/data-lists"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#modifiers/modifier"; -import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder } from "#utils/common"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/items/thick-club.test.ts b/test/items/thick-club.test.ts index d14b4f955e7..6ec73c67f53 100644 --- a/test/items/thick-club.test.ts +++ b/test/items/thick-club.test.ts @@ -2,9 +2,9 @@ import { modifierTypes } from "#data/data-lists"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#modifiers/modifier"; -import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; import { NumberHolder, randInt } from "#utils/common"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/items/toxic-orb.test.ts b/test/items/toxic-orb.test.ts index 664ddd437e4..be6632e771f 100644 --- a/test/items/toxic-orb.test.ts +++ b/test/items/toxic-orb.test.ts @@ -2,8 +2,8 @@ import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; -import i18next from "#plugins/i18n"; import { GameManager } from "#test/test-utils/game-manager"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/test/setup/vitest.setup.ts b/test/setup/vitest.setup.ts index 4bd194c7be4..c38eab48ff7 100644 --- a/test/setup/vitest.setup.ts +++ b/test/setup/vitest.setup.ts @@ -1,4 +1,6 @@ import "vitest-canvas-mock"; +import "#plugins/i18n"; // tests don't go through `main.ts`, requiring this to be imported here as well + import { MockConsole } from "#test/test-utils/mocks/mock-console/mock-console"; import { logTestEnd, logTestStart } from "#test/test-utils/setup/test-end-log"; import { initTests } from "#test/test-utils/test-file-initialization"; diff --git a/test/test-utils/test-file-initialization.ts b/test/test-utils/test-file-initialization.ts index 6a765dfa1f2..a24c9a23673 100644 --- a/test/test-utils/test-file-initialization.ts +++ b/test/test-utils/test-file-initialization.ts @@ -1,6 +1,5 @@ import { SESSION_ID_COOKIE_NAME } from "#app/constants"; import { initializeGame } from "#app/init/init"; -import { initI18n } from "#plugins/i18n"; import { blobToString } from "#test/test-utils/game-manager-utils"; import { manageListeners } from "#test/test-utils/listeners-manager"; import { MockConsole } from "#test/test-utils/mocks/mock-console/mock-console"; @@ -15,26 +14,18 @@ import InputText from "phaser3-rex-plugins/plugins/inputtext"; let wasInitialized = false; /** - * Run initialization code upon starting a new file, both per-suite and per-instance oncess. + * Run initialization code upon starting a new file, both per-suite and per-instance ones. */ export function initTests(): void { setupStubs(); if (!wasInitialized) { - initTestFile(); + initializeGame(); wasInitialized = true; } manageListeners(); } -/** - * Initialize various values at the beginning of each testing instance. - */ -function initTestFile(): void { - initI18n(); - initializeGame(); -} - /** * Setup various stubs for testing. * @todo Move this into a dedicated stub file instead of running it once per test instance From b381d196cf9815658f2ee6870763c36e05028f62 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:57:34 -0500 Subject: [PATCH 070/101] [Test] Improve error message + typing on `toHaveUsedMove` (#6681) * [Test] Improve error message on `toHaveUsedMove` * Fixed typing on test stuff + added caching on `toHaveArenaTagOptions` * Fixed matcher breaking with single move arguments * Fixed typing errors in `vitest.d.ts` * Fixed typing importing from the wrong file * Fixed wish test type errors * Reverted type changes to battler tag matchers by request --- test/@types/test-helpers.ts | 8 ++-- test/@types/vitest.d.ts | 6 +-- test/moves/sleep-talk.test.ts | 2 +- test/moves/wish.test.ts | 2 +- test/test-utils/matchers/to-have-used-move.ts | 47 +++++++++++++------ 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/test/@types/test-helpers.ts b/test/@types/test-helpers.ts index b867eb32570..7e0c3b910fe 100644 --- a/test/@types/test-helpers.ts +++ b/test/@types/test-helpers.ts @@ -1,4 +1,4 @@ -import type { AtLeastOne, NonFunctionPropertiesRecursive as nonFunc } from "#types/type-helpers"; +import type { AtLeastOne, NonFunctionProperties } from "#types/type-helpers"; /** * Helper type to admit an object containing the given properties @@ -22,6 +22,6 @@ import type { AtLeastOne, NonFunctionPropertiesRecursive as nonFunc } from "#typ * @typeParam O - The object to source keys from * @typeParam K - One or more of O's keys to render mandatory */ -export type OneOther = AtLeastOne, K>> & { - [key in K]: O[K]; -}; +// NB: no need to recursively exclude non function properties +// TODO: Figure out how to force K to not be a method property +export type OneOther = Pick & AtLeastOne, K>>; diff --git a/test/@types/vitest.d.ts b/test/@types/vitest.d.ts index 43e9df190aa..0be04a05b2a 100644 --- a/test/@types/vitest.d.ts +++ b/test/@types/vitest.d.ts @@ -3,6 +3,7 @@ import "vitest"; import type Overrides from "#app/overrides"; import type { Phase } from "#app/phase"; import type { ArenaTag } from "#data/arena-tag"; +import type { PositionalTag } from "#data/positional-tags/positional-tag"; import type { TerrainType } from "#data/terrain"; import type { AbilityId } from "#enums/ability-id"; import type { ArenaTagSide } from "#enums/arena-tag-side"; @@ -10,11 +11,11 @@ import type { ArenaTagType } from "#enums/arena-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type"; import type { MoveId } from "#enums/move-id"; import type { PokemonType } from "#enums/pokemon-type"; -import type { PositionalTag } from "#data/positional-tags/positional-tag"; import type { PositionalTagType } from "#enums/positional-tag-type"; import type { BattleStat, EffectiveStat } from "#enums/stat"; import type { WeatherType } from "#enums/weather-type"; import type { Pokemon } from "#field/pokemon"; +import type { OneOther } from "#test/@types/test-helpers"; import type { GameManager } from "#test/test-utils/game-manager"; import type { toHaveArenaTagOptions } from "#test/test-utils/matchers/to-have-arena-tag"; import type { toHaveBattlerTagOptions } from "#test/test-utils/matchers/to-have-battler-tag"; @@ -24,7 +25,6 @@ import type { expectedStatusType } from "#test/test-utils/matchers/to-have-statu import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types"; import type { PhaseString } from "#types/phase-types"; import type { TurnMove } from "#types/turn-move"; -import type { AtLeastOne } from "#types/type-helpers"; import type { toDmgValue } from "#utils/common"; import type { expect } from "vitest"; @@ -154,7 +154,7 @@ interface PokemonMatchers { * @param index - The index of the move history entry to check, in order from most recent to least recent; default `0` * @see {@linkcode Pokemon.getLastXMoves} */ - toHaveUsedMove(expectedMove: MoveId | AtLeastOne, index?: number): void; + toHaveUsedMove(expectedMove: MoveId | OneOther, index?: number): void; /** * Check whether a {@linkcode Pokemon}'s effective stat is as expected diff --git a/test/moves/sleep-talk.test.ts b/test/moves/sleep-talk.test.ts index ee639aaf1e8..a2f40e407bb 100644 --- a/test/moves/sleep-talk.test.ts +++ b/test/moves/sleep-talk.test.ts @@ -79,7 +79,7 @@ describe("Moves - Sleep Talk", () => { game.move.select(MoveId.SLEEP_TALK); await game.toNextTurn(); - expect(feebas).toHaveUsedMove({ result: MoveResult.FAIL }); + expect(feebas).toHaveUsedMove({ move: MoveId.SLEEP_TALK, result: MoveResult.FAIL }); }); it("should fail if the user has no valid moves", async () => { diff --git a/test/moves/wish.test.ts b/test/moves/wish.test.ts index b64a15ac654..72d0d30abd0 100644 --- a/test/moves/wish.test.ts +++ b/test/moves/wish.test.ts @@ -79,7 +79,7 @@ describe("Move - Wish", () => { await game.toEndOfTurn(); expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1); - expect(alomomola).toHaveUsedMove({ result: MoveResult.FAIL }); + expect(alomomola).toHaveUsedMove({ move: MoveId.WISH, result: MoveResult.FAIL }); }); it("should function independently of Future Sight", async () => { diff --git a/test/test-utils/matchers/to-have-used-move.ts b/test/test-utils/matchers/to-have-used-move.ts index 206557b96e3..a19b0414868 100644 --- a/test/test-utils/matchers/to-have-used-move.ts +++ b/test/test-utils/matchers/to-have-used-move.ts @@ -1,10 +1,10 @@ import { getPokemonNameWithAffix } from "#app/messages"; -import type { MoveId } from "#enums/move-id"; +import { MoveId } from "#enums/move-id"; import type { Pokemon } from "#field/pokemon"; -import { getOnelineDiffStr, getOrdinal } from "#test/test-utils/string-utils"; +import type { OneOther } from "#test/@types/test-helpers"; +import { getEnumStr, getOnelineDiffStr, getOrdinal } from "#test/test-utils/string-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { TurnMove } from "#types/turn-move"; -import type { AtLeastOne } from "#types/type-helpers"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; /** @@ -12,14 +12,14 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; * @param received - The actual value received. Should be a {@linkcode Pokemon} * @param expectedMove - The {@linkcode MoveId} the Pokemon is expected to have used, * or a partially filled {@linkcode TurnMove} containing the desired properties to check - * @param index - The index of the move history entry to check, in order from most recent to least recent. - * Default `0` (last used move) + * @param index - The index of the move history entry to check, in order from most recent to least recent; + * default `0` (last used move) * @returns Whether the matcher passed */ export function toHaveUsedMove( this: MatcherState, received: unknown, - expectedMove: MoveId | AtLeastOne, + expectedMove: MoveId | OneOther, index = 0, ): SyncExpectationResult { if (!isPokemonInstance(received)) { @@ -29,10 +29,10 @@ export function toHaveUsedMove( }; } - const move: TurnMove | undefined = received.getLastXMoves(-1)[index]; + const historyMove: TurnMove | undefined = received.getLastXMoves(-1)[index]; const pkmName = getPokemonNameWithAffix(received); - if (move === undefined) { + if (historyMove === undefined) { return { pass: this.isNot, message: () => `Expected ${pkmName} to have used ${index + 1} moves, but it didn't!`, @@ -40,20 +40,37 @@ export function toHaveUsedMove( }; } - // Coerce to a `TurnMove` - if (typeof expectedMove === "number") { - expectedMove = { move: expectedMove }; - } - const moveIndexStr = index === 0 ? "last move" : `${getOrdinal(index)} most recent move`; - const pass = this.equals(move, expectedMove, [ + // Break out early if a move-only comparison was done or if the move ID did not match + const expectedId = typeof expectedMove === "number" ? expectedMove : expectedMove.move; + const actualId = historyMove.move; + const sameId = this.equals(actualId, expectedId, this.customTesters); + + if (typeof expectedMove === "number" || !sameId) { + const expectedIdStr = getEnumStr(MoveId, expectedId); + const actualIdStr = getEnumStr(MoveId, actualId); + return { + pass: sameId, + // Expected Magikarp' 5th most recent move to be PHOTON_GEYSER, but got METRONOME instead! + message: () => + sameId + ? `Expected ${pkmName}'s ${moveIndexStr} to NOT be ${expectedIdStr}, but it was!` + : `Expected ${pkmName}'s ${moveIndexStr} to be ${expectedIdStr}, but got ${actualIdStr} instead!`, + expected: expectedMove, + actual: historyMove, + }; + } + + // Compare equality with the provided object + const pass = this.equals(historyMove, expectedMove, [ ...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality, ]); const expectedStr = getOnelineDiffStr.call(this, expectedMove); + return { pass, message: () => @@ -61,6 +78,6 @@ export function toHaveUsedMove( ? `Expected ${pkmName}'s ${moveIndexStr} to NOT match ${expectedStr}, but it did!` : `Expected ${pkmName}'s ${moveIndexStr} to match ${expectedStr}, but it didn't!`, expected: expectedMove, - actual: move, + actual: historyMove, }; } From e6de0fb95dcace6397e8ac98b469e0c40b74c203 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:49:21 -0600 Subject: [PATCH 071/101] [UI/UX] Split login and register into separate menus (#6851) * [UI/UX] Split login and register into separate menus * Resize the container for the "Login" and "Register" buttons * Make container width dynamic Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> * Make logo position dynamic * Apply suggestions - Consolidate code in `LoginPhase` - Use `truncateString` utility function in `form-modal-ui-handler.ts` - Move login form to match location of register form - Swap `x` values of username and download buttons * Apply suggestions --------- Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> --- src/enums/ui-mode.ts | 1 + src/phases/login-phase.ts | 185 +++++----- src/ui/handlers/form-modal-ui-handler.ts | 68 ++-- src/ui/handlers/login-form-ui-handler.ts | 340 ++---------------- .../handlers/login-or-register-ui-handler.ts | 72 ++++ ...ogin-register-info-container-ui-handler.ts | 233 ++++++++++++ src/ui/handlers/oauth-providers-ui-handler.ts | 107 ++++++ .../handlers/registration-form-ui-handler.ts | 131 +++---- src/ui/ui.ts | 2 + 9 files changed, 641 insertions(+), 498 deletions(-) create mode 100644 src/ui/handlers/login-or-register-ui-handler.ts create mode 100644 src/ui/handlers/login-register-info-container-ui-handler.ts create mode 100644 src/ui/handlers/oauth-providers-ui-handler.ts diff --git a/src/enums/ui-mode.ts b/src/enums/ui-mode.ts index 75c07a5f63c..d4f27c66255 100644 --- a/src/enums/ui-mode.ts +++ b/src/enums/ui-mode.ts @@ -31,6 +31,7 @@ export enum UiMode { POKEDEX, POKEDEX_SCAN, POKEDEX_PAGE, + LOGIN_OR_REGISTER, LOGIN_FORM, REGISTRATION_FORM, LOADING, diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index 0aaf97e234b..aebdcdae152 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -10,7 +10,13 @@ import i18next, { t } from "i18next"; export class LoginPhase extends Phase { public readonly phaseName = "LoginPhase"; - private showText: boolean; + + /** + * Whether to load the "login or register" text. + * Only `true` the first time the phase runs, the text stays on screen after that. + * @defaultValue `true` + */ + private readonly showText: boolean; constructor(showText = true) { super(); @@ -18,103 +24,114 @@ export class LoginPhase extends Phase { this.showText = showText; } - start(): void { + public override async start(): Promise { + const { gameData, ui } = globalScene; + super.start(); const hasSession = !!getCookie(sessionIdKey); - globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); - executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { - const success = response ? response[0] : false; - const statusCode = response ? response[1] : null; - if (!success) { - if (!statusCode || statusCode === 400) { - if (this.showText) { - globalScene.ui.showText(i18next.t("menu:logInOrCreateAccount")); - } + ui.setMode(UiMode.LOADING, { buttonActions: [] }); - globalScene.playSound("ui/menu_open"); + const response = await executeIf(bypassLogin || hasSession, updateUserInfo); + const success = response?.[0] ?? false; + const statusCode = response ? response[1] : null; - const loadData = () => { - updateUserInfo().then(success => { - if (!success[0]) { - removeCookie(sessionIdKey); - globalScene.reset(true, true); - return; - } - globalScene.gameData.loadSystem().then(() => this.end()); - }); - }; + if (!success) { + this.checkStatus(statusCode); + return; + } - globalScene.ui.setMode(UiMode.LOGIN_FORM, { - buttonActions: [ - () => { - globalScene.ui.playSelect(); - loadData(); - }, - () => { - globalScene.playSound("ui/menu_open"); - globalScene.ui.setMode(UiMode.REGISTRATION_FORM, { - buttonActions: [ - () => { - globalScene.ui.playSelect(); - updateUserInfo().then(success => { - if (!success[0]) { - removeCookie(sessionIdKey); - globalScene.reset(true, true); - return; - } - this.end(); - }); - }, - () => { - globalScene.phaseManager.unshiftNew("LoginPhase", false); - this.end(); - }, - ], - }); - }, - () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; - window.open(discordUrl, "_self"); - }, - () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; - window.open(googleUrl, "_self"); - }, - ], - }); - } else if (statusCode === 401) { - removeCookie(sessionIdKey); - globalScene.reset(true, true); - } else { - globalScene.phaseManager.unshiftNew("UnavailablePhase"); - super.end(); - } - return null; - } - globalScene.gameData.loadSystem().then(success => { - if (success || bypassLogin) { - this.end(); - } else { - globalScene.ui.setMode(UiMode.MESSAGE); - globalScene.ui.showText(t("menu:failedToLoadSaveData")); - } - }); - }); + await gameData.loadSystem(); + if (success || bypassLogin) { + await this.end(); + return; + } + ui.setMode(UiMode.MESSAGE); + ui.showText(t("menu:failedToLoadSaveData")); } - end(): void { + public override async end(): Promise { globalScene.ui.setMode(UiMode.MESSAGE); if (!globalScene.gameData.gender) { globalScene.phaseManager.unshiftNew("SelectGenderPhase"); } - handleTutorial(Tutorial.Intro).then(() => super.end()); + await handleTutorial(Tutorial.Intro); + super.end(); + } + + private checkStatus(statusCode: number | null): void { + if (!statusCode || statusCode === 400) { + this.showLoginRegister(); + return; + } + + if (statusCode === 401) { + removeCookie(sessionIdKey); + globalScene.reset(true, true); + return; + } + + globalScene.phaseManager.unshiftNew("UnavailablePhase"); + super.end(); + } + + private showLoginRegister(): void { + const { gameData, phaseManager, ui } = globalScene; + + const backButton = () => { + phaseManager.unshiftNew("LoginPhase", false); + this.end(); + }; + + const checkUserInfo = async (): Promise => { + ui.playSelect(); + const success = await updateUserInfo(); + if (!success[0]) { + removeCookie(sessionIdKey); + globalScene.reset(true, true); + return false; + } + return true; + }; + + const loginButton = async () => { + const success = await checkUserInfo(); + if (!success) { + return; + } + await gameData.loadSystem(); + this.end(); + }; + + const registerButton = async () => { + const success = await checkUserInfo(); + if (!success) { + return; + } + this.end(); + }; + + const goToLoginButton = () => { + globalScene.playSound("ui/menu_open"); + + ui.setMode(UiMode.LOGIN_FORM, { buttonActions: [loginButton, backButton] }); + }; + + const goToRegistrationButton = () => { + globalScene.playSound("ui/menu_open"); + + ui.setMode(UiMode.REGISTRATION_FORM, { buttonActions: [registerButton, backButton] }); + }; + + if (this.showText) { + ui.showText(i18next.t("menu:logInOrCreateAccount")); + } + + globalScene.playSound("ui/menu_open"); + + ui.setMode(UiMode.LOGIN_OR_REGISTER, { buttonActions: [goToLoginButton, goToRegistrationButton] }); } } diff --git a/src/ui/handlers/form-modal-ui-handler.ts b/src/ui/handlers/form-modal-ui-handler.ts index a4771721995..b6db5d41ce8 100644 --- a/src/ui/handlers/form-modal-ui-handler.ts +++ b/src/ui/handlers/form-modal-ui-handler.ts @@ -5,7 +5,8 @@ import type { ModalConfig } from "#ui/modal-ui-handler"; import { ModalUiHandler } from "#ui/modal-ui-handler"; import { addTextInputObject, addTextObject, getTextColor } from "#ui/text"; import { addWindow, WindowVariant } from "#ui/ui-theme"; -import { fixedInt } from "#utils/common"; +import { fixedInt, truncateString } from "#utils/common"; +import type Phaser from "phaser"; import type InputText from "phaser3-rex-plugins/plugins/inputtext"; export interface FormModalConfig extends ModalConfig { @@ -24,30 +25,35 @@ export abstract class FormModalUiHandler extends ModalUiHandler { /** * Get configuration for all fields that should be part of the modal + * @remarks * Gets used by {@linkcode updateFields} to add the proper text inputs and labels to the view * @returns array of {@linkcode InputFieldConfig} */ abstract getInputFieldConfigs(): InputFieldConfig[]; - getHeight(config?: ModalConfig): number { + public override getHeight(config?: FormModalConfig): number { return ( 20 * this.getInputFieldConfigs().length + (this.getModalTitle() ? 26 : 0) - + ((config as FormModalConfig)?.errorMessage ? 12 : 0) + + (config?.errorMessage ? 12 : 0) + this.getButtonTopMargin() + 28 ); } - getReadableErrorMessage(error: string): string { - if (error?.indexOf("connection refused") > -1) { + public getReadableErrorMessage(error: string): string { + if (!error) { + return ""; + } + + if (error.includes("connection refused")) { return "Could not connect to the server"; } return error; } - setup(): void { + public override setup(): void { super.setup(); const config = this.getInputFieldConfigs(); @@ -58,16 +64,9 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.updateFields(config, hasTitle); } - this.errorMessage = addTextObject( - 10, - (hasTitle ? 31 : 5) + 20 * (config.length - 1) + 16 + this.getButtonTopMargin(), - "", - TextStyle.TOOLTIP_CONTENT, - { - fontSize: "42px", - wordWrap: { width: 850 }, - }, - ) + const errorMessageY = (hasTitle ? 31 : 5) + 20 * (config.length - 1) + 16 + this.getButtonTopMargin(); + const errorMessageOptions: Phaser.Types.GameObjects.Text.TextStyle = { fontSize: "42px", wordWrap: { width: 850 } }; + this.errorMessage = addTextObject(10, errorMessageY, "", TextStyle.TOOLTIP_CONTENT, errorMessageOptions) .setColor(getTextColor(TextStyle.SUMMARY_PINK)) .setShadowColor(getTextColor(TextStyle.SUMMARY_PINK, true)) .setVisible(false); @@ -75,21 +74,18 @@ export abstract class FormModalUiHandler extends ModalUiHandler { } protected updateFields(fieldsConfig: InputFieldConfig[], hasTitle: boolean) { - const inputContainers = (this.inputContainers = new Array(fieldsConfig.length)); - const inputs = (this.inputs = new Array(fieldsConfig.length)); - const formLabels = (this.formLabels = new Array(fieldsConfig.length)); + this.inputContainers = new Array(fieldsConfig.length); + this.inputs = new Array(fieldsConfig.length); + this.formLabels = new Array(fieldsConfig.length); for (const [f, config] of fieldsConfig.entries()) { + const labelY = (hasTitle ? 31 : 5) + 20 * f; // The Pokédex Scan Window uses width `300` instead of `160` like the other forms // Therefore, the label does not need to be shortened - const label = addTextObject( - 10, - (hasTitle ? 31 : 5) + 20 * f, - config.label.length > 25 && this.getWidth() < 200 ? config.label.slice(0, 20) + "..." : config.label, - TextStyle.TOOLTIP_CONTENT, - ); + const labelContent = this.getWidth() < 200 ? truncateString(config.label, 25) : config.label; + const label = addTextObject(10, labelY, labelContent, TextStyle.TOOLTIP_CONTENT); label.name = "formLabel" + f; - formLabels[f] = label; + this.formLabels[f] = label; this.modalContainer.add(label); const inputWidth = label.width < 320 ? 80 : 80 - (label.width - 320) / 5.5; @@ -109,14 +105,14 @@ export abstract class FormModalUiHandler extends ModalUiHandler { inputContainer.add([inputBg, input]); - inputContainers[f] = inputContainer; + this.inputContainers[f] = inputContainer; this.modalContainer.add(inputContainer); - inputs[f] = input; + this.inputs[f] = input; } } - override show(args: any[]): boolean { + public override show(args: any[]): boolean { if (super.show(args)) { for (const ic of this.inputContainers) { ic.setActive(true).setVisible(true); @@ -129,7 +125,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { // Auto focus the first input field after a short delay, to prevent accidental inputs setTimeout(() => { - this.inputs[0].setFocus(); + this.inputs[0]?.setFocus(); }, 50); // #region: Override button pointerDown @@ -170,7 +166,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { return false; } - processInput(button: Button): boolean { + public override processInput(button: Button): boolean { if (button === Button.SUBMIT && this.submitAction) { this.submitAction(); return true; @@ -179,13 +175,13 @@ export abstract class FormModalUiHandler extends ModalUiHandler { return false; } - sanitizeInputs(): void { + public sanitizeInputs(): void { for (const input of this.inputs) { input.text = input.text.trim(); } } - updateContainer(config?: ModalConfig): void { + public override updateContainer(config?: ModalConfig): void { super.updateContainer(config); this.errorMessage @@ -193,21 +189,21 @@ export abstract class FormModalUiHandler extends ModalUiHandler { .setVisible(!!this.errorMessage.text); } - hide(): void { + public hide(): void { this.modalContainer.setVisible(false).setActive(false); for (const ic of this.inputContainers) { ic.setVisible(false).setActive(false); } } - unhide(): void { + public unhide(): void { this.modalContainer.setActive(true).setVisible(true); for (const ic of this.inputContainers) { ic.setActive(true).setVisible(true); } } - clear(): void { + public override clear(): void { super.clear(); this.modalContainer.setVisible(false); diff --git a/src/ui/handlers/login-form-ui-handler.ts b/src/ui/handlers/login-form-ui-handler.ts index 6c4a7cfeaa6..98358c26ba4 100644 --- a/src/ui/handlers/login-form-ui-handler.ts +++ b/src/ui/handlers/login-form-ui-handler.ts @@ -1,152 +1,60 @@ import { pokerogueApi } from "#api/pokerogue-api"; import { globalScene } from "#app/global-scene"; -import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; -import { languageOptions } from "#system/settings-language"; -import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; -import { addTextObject } from "#ui/text"; -import { addWindow } from "#ui/ui-theme"; -import { fixedInt } from "#utils/common"; +import { OAuthProvidersUiHandler } from "#ui/oauth-providers-ui-handler"; import i18next from "i18next"; -import JSZip from "jszip"; -interface BuildInteractableImageOpts { - scale?: number; - x?: number; - y?: number; - origin?: { x: number; y: number }; -} +const ERR_USERNAME: string = "invalid username"; +const ERR_PASSWORD: string = "invalid password"; +const ERR_ACCOUNT_EXIST: string = "account doesn't exist"; +const ERR_PASSWORD_MATCH: string = "password doesn't match"; -/** - * The maximum number of saves that are allowed to show up in the username panel pefore - * the `P02: Too many saves` popup is displayed. - * - * @privateRemarks - * This limitation is in place to allow for the password reset helpers to get - * enough information in one screenshot. If the user has too many saves, this - * complicates the interaction as it would require scrolling, which will - * make tickets take longer to resolve. - */ -const MAX_SAVES_FOR_USERNAME_PANEL = 7; - -export class LoginFormUiHandler extends FormModalUiHandler { - private readonly ERR_USERNAME: string = "invalid username"; - private readonly ERR_PASSWORD: string = "invalid password"; - private readonly ERR_ACCOUNT_EXIST: string = "account doesn't exist"; - private readonly ERR_PASSWORD_MATCH: string = "password doesn't match"; - private readonly ERR_NO_SAVES: string = "No save files found"; - private readonly ERR_TOO_MANY_SAVES: string = "Too many save files found"; - - private googleImage: Phaser.GameObjects.Image; - private discordImage: Phaser.GameObjects.Image; - private usernameInfoImage: Phaser.GameObjects.Image; - private saveDownloadImage: Phaser.GameObjects.Image; - private changeLanguageImage: Phaser.GameObjects.Image; - private externalPartyContainer: Phaser.GameObjects.Container; - private infoContainer: Phaser.GameObjects.Container; - private externalPartyBg: Phaser.GameObjects.NineSlice; - private externalPartyTitle: Phaser.GameObjects.Text; +export class LoginFormUiHandler extends OAuthProvidersUiHandler { constructor(mode: UiMode | null = null) { super(mode); } - setup(): void { - super.setup(); - - this.buildExternalPartyContainer(); - this.buildInfoContainer(); - } - - private buildExternalPartyContainer() { - this.externalPartyContainer = globalScene.add.container(0, 0); - this.externalPartyContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.scaledCanvas.width / 2, globalScene.scaledCanvas.height / 2), - Phaser.Geom.Rectangle.Contains, - ); - this.externalPartyTitle = addTextObject(0, 4, "", TextStyle.SETTINGS_LABEL).setOrigin(0.5, 0); - this.externalPartyBg = addWindow(0, 0, 0, 0); - - this.googleImage = this.buildInteractableImage("google", "google-icon"); - this.discordImage = this.buildInteractableImage("discord", "discord-icon"); - - this.externalPartyContainer - .add([this.externalPartyBg, this.externalPartyTitle, this.googleImage, this.discordImage]) - .setVisible(false); - this.getUi().add(this.externalPartyContainer); - } - - private buildInfoContainer() { - this.infoContainer = globalScene.add.container(0, 0); - - this.usernameInfoImage = this.buildInteractableImage("settings_icon", "username-info-icon", { - x: 20, - scale: 0.5, - }); - - this.saveDownloadImage = this.buildInteractableImage("saving_icon", "save-download-icon", { - x: 0, - scale: 0.75, - }); - - this.changeLanguageImage = this.buildInteractableImage("language_icon", "change-language-icon", { - x: 40, - scale: 0.5, - }); - - this.infoContainer - .add([this.usernameInfoImage, this.saveDownloadImage, this.changeLanguageImage]) - .setVisible(false) - .disableInteractive(); - this.getUi().add(this.infoContainer); - } - - override getModalTitle(_config?: ModalConfig): string { - let key = "menu:login"; + public override getModalTitle(): string { if (import.meta.env.VITE_SERVER_URL === "https://apibeta.pokerogue.net") { - key = "menu:loginBeta"; + return i18next.t("menu:loginBeta"); } - return i18next.t(key); + return i18next.t("menu:login"); } - override getWidth(_config?: ModalConfig): number { + public override getWidth(): number { return 160; } - override getMargin(_config?: ModalConfig): [number, number, number, number] { - return [0, 0, 48, 0]; + public override getMargin(): [number, number, number, number] { + return [0, 20, 48, 0]; } - override getButtonLabels(_config?: ModalConfig): string[] { - return [i18next.t("menu:login"), i18next.t("menu:register")]; + public override getButtonLabels(): string[] { + return [i18next.t("menu:login"), i18next.t("menu:goBack")]; } - override getReadableErrorMessage(error: string): string { - const colonIndex = error?.indexOf(":"); - if (colonIndex > 0) { - error = error.slice(0, colonIndex); + public override getReadableErrorMessage(error: string): string { + if (!error) { + return ""; } + switch (error) { - case this.ERR_USERNAME: + case ERR_USERNAME: return i18next.t("menu:invalidLoginUsername"); - case this.ERR_PASSWORD: + case ERR_PASSWORD: return i18next.t("menu:invalidLoginPassword"); - case this.ERR_ACCOUNT_EXIST: + case ERR_ACCOUNT_EXIST: return i18next.t("menu:accountNonExistent"); - case this.ERR_PASSWORD_MATCH: + case ERR_PASSWORD_MATCH: return i18next.t("menu:unmatchingPassword"); - case this.ERR_NO_SAVES: - return "P01: " + i18next.t("menu:noSaves"); - case this.ERR_TOO_MANY_SAVES: - return "P02: " + i18next.t("menu:tooManySaves"); } return super.getReadableErrorMessage(error); } - override getInputFieldConfigs(): InputFieldConfig[] { + public override getInputFieldConfigs(): InputFieldConfig[] { const inputFieldConfigs: InputFieldConfig[] = []; inputFieldConfigs.push( { label: i18next.t("menu:username") }, @@ -158,12 +66,13 @@ export class LoginFormUiHandler extends FormModalUiHandler { return inputFieldConfigs; } - override show(args: any[]): boolean { + public override show(args: any[]): boolean { if (!super.show(args)) { return false; } const config = args[0] as ModalConfig; - this.processExternalProvider(config); + this.processExternalProvider(); + this.showInfoContainer(config); const originalLoginAction = this.submitAction; this.submitAction = () => { if (globalScene.tweens.getTweensOf(this.modalContainer).length > 0) { @@ -173,7 +82,7 @@ export class LoginFormUiHandler extends FormModalUiHandler { this.submitAction = originalLoginAction; this.sanitizeInputs(); globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); - const onFail = error => { + const onFail = (error: string | null) => { globalScene.ui.setMode(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); globalScene.ui.playError(); }; @@ -199,199 +108,4 @@ export class LoginFormUiHandler extends FormModalUiHandler { return true; } - - override clear() { - super.clear(); - this.externalPartyContainer.setVisible(false).setActive(false); - this.infoContainer.setVisible(false).setActive(false); - this.setMouseCursorStyle("default"); //reset cursor - - [ - this.discordImage, - this.googleImage, - this.usernameInfoImage, - this.saveDownloadImage, - this.changeLanguageImage, - ].forEach(img => { - img.off("pointerdown"); - }); - } - - override destroy() { - super.destroy(); - this.externalPartyContainer.destroy(); - this.infoContainer.destroy(); - } - - /** - * Show a panel with all usernames found in localStorage - * - * @remarks - * Up to {@linkcode MAX_SAVES_FOR_USERNAME_PANEL} usernames are shown, otherwise P02 is triggered - * @param onFail - Callback function for failure - */ - private showUsernames(config: ModalConfig) { - if (globalScene.tweens.getTweensOf(this.infoContainer).length === 0) { - const localStorageKeys = Object.keys(localStorage); // this gets the keys for localStorage - const keyToFind = "data_"; - const dataKeys = localStorageKeys.filter(ls => ls.indexOf(keyToFind) >= 0); - if (dataKeys.length === 0) { - this.onFail(this.ERR_NO_SAVES, config); - return; - } - if (dataKeys.length > MAX_SAVES_FOR_USERNAME_PANEL) { - this.onFail(this.ERR_TOO_MANY_SAVES, config); - return; - } - const options: OptionSelectItem[] = []; - const handler = () => { - globalScene.ui.revertMode(); - this.infoContainer.disableInteractive(); - return true; - }; - for (const key of dataKeys) { - options.push({ - label: key.replace(keyToFind, ""), - handler, - }); - } - globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { - options, - delay: 1000, - }); - this.infoContainer.setInteractive( - new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width, globalScene.game.canvas.height), - Phaser.Geom.Rectangle.Contains, - ); - } - } - - /** - * - */ - private onFail(error: string, config: ModalConfig) { - const ui = globalScene.ui; - ui.setMode(UiMode.LOADING, { buttonActions: [] }); - ui.setModeForceTransition(UiMode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); - ui.playError(); - } - - /** - * Collect the user's save files from localStorage and download them as a zip file - * - * @remarks - * Used as the `pointerDown` callback for the save download image - * @param config - The modal configuration - */ - private async downloadSaves(config: ModalConfig): Promise { - // find all data_ and sessionData keys, put them in a .txt file and download everything in a single zip - const localStorageKeys = Object.keys(localStorage); // this gets the keys for localStorage - const keyToFind = "data_"; - const sessionKeyToFind = "sessionData"; - const dataKeys = localStorageKeys.filter(ls => ls.indexOf(keyToFind) >= 0); - const sessionKeys = localStorageKeys.filter(ls => ls.indexOf(sessionKeyToFind) >= 0); - if (dataKeys.length <= 0 && sessionKeys.length <= 0) { - this.onFail(this.ERR_NO_SAVES, config); - return; - } - const zip = new JSZip(); - // Bang is safe here because of the filter above - for (const dataKey of dataKeys) { - zip.file(dataKey + ".prsv", localStorage.getItem(dataKey)!); - } - for (const sessionKey of sessionKeys) { - zip.file(sessionKey + ".prsv", localStorage.getItem(sessionKey)!); - } - const content = await zip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(content); - const a = document.createElement("a"); - a.href = url; - a.download = "pokerogue_saves.zip"; - a.click(); - URL.revokeObjectURL(url); - } - - private processExternalProvider(config: ModalConfig): void { - this.externalPartyTitle - .setText(i18next.t("menu:orUse") ?? "") - .setX(20 + this.externalPartyTitle.text.length) - .setVisible(true); - - const externalPartyContainer = this.externalPartyContainer - .setPositionRelative(this.modalContainer, 175, 0) - .setVisible(true); - - const externalPartyBg = this.externalPartyBg.setSize(this.externalPartyTitle.text.length + 50, this.modalBg.height); - this.getUi().moveTo(externalPartyContainer, this.getUi().length - 1); - - const externalPartyIconWidth = externalPartyBg.width / 3.1; - this.discordImage; - const infoContainer = this.infoContainer.setPosition(5, -76).setVisible(true); - this.getUi().moveTo(infoContainer, this.getUi().length - 1); - - this.discordImage // formatting - .setPosition(externalPartyIconWidth, externalPartyBg.height - 40) - .on("pointerdown", () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; - window.open(discordUrl, "_self"); - }); - - this.googleImage // formatting - .setPosition(externalPartyIconWidth, externalPartyBg.height - 60) - .on("pointerdown", () => { - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; - window.open(googleUrl, "_self"); - }); - - this.usernameInfoImage // formatting - .setPositionRelative(infoContainer, 0, 0) - .on("pointerdown", () => this.showUsernames(config)); - - this.saveDownloadImage // formatting - .setPositionRelative(infoContainer, 20, 0) - .on("pointerdown", () => this.downloadSaves(config)); - - this.changeLanguageImage.setPositionRelative(infoContainer, 40, 0).on("pointerdown", () => { - globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { - options: languageOptions, - maxOptions: 7, - delay: 1000, - }); - }); - - this.externalPartyContainer.setAlpha(0); - globalScene.tweens.add({ - targets: this.externalPartyContainer, - duration: fixedInt(1000), - ease: "Sine.easeInOut", - y: "-=24", - alpha: 1, - }); - - this.infoContainer.setAlpha(0); - globalScene.tweens.add({ - targets: this.infoContainer, - duration: fixedInt(1000), - ease: "Sine.easeInOut", - y: "-=24", - alpha: 1, - }); - } - - private buildInteractableImage(texture: string, name: string, opts: BuildInteractableImageOpts = {}) { - const { scale = 0.07, x = 0, y = 0, origin = { x: 0, y: 0 } } = opts; - const img = globalScene.add - .image(x, y, texture) - .setName(name) - .setOrigin(origin.x, origin.y) - .setScale(scale) - .setInteractive(); - this.addInteractionHoverEffect(img); - - return img; - } } diff --git a/src/ui/handlers/login-or-register-ui-handler.ts b/src/ui/handlers/login-or-register-ui-handler.ts new file mode 100644 index 00000000000..b5733a9eaee --- /dev/null +++ b/src/ui/handlers/login-or-register-ui-handler.ts @@ -0,0 +1,72 @@ +import { globalScene } from "#app/global-scene"; +import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; +import { LoginRegisterInfoContainerUiHandler } from "#ui/login-register-info-container-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; +import i18next from "i18next"; +import type Phaser from "phaser"; + +export class LoginOrRegisterUiHandler extends LoginRegisterInfoContainerUiHandler { + private logo: Phaser.GameObjects.Image; + + public override getModalTitle(): string { + return ""; + } + + public override getWidth(): number { + const buttonWidth = this.buttonLabels.reduce((sum, label) => sum + label.width, 0) / 6; + return buttonWidth + 50; + } + + public override getHeight(): number { + return 32; + } + + public override getMargin(): [number, number, number, number] { + return [0, 0, 30, 0]; + } + + public override getButtonLabels(): string[] { + return [i18next.t("menu:login"), i18next.t("menu:register")]; + } + + // TODO: use mixins so it's not necessary to inherit from `FormModalUiHandler` + public override getInputFieldConfigs(): InputFieldConfig[] { + return []; + } + + public override setup(): void { + super.setup(); + + // logo width is 150 + this.logo = globalScene.add // + .image(-((150 - this.getWidth()) / 2), -52, "logo") + .setOrigin(0); + + this.modalContainer.add(this.logo); + } + + public override show(args: [ModalConfig, ...any[]]): boolean { + this.logo // + .setVisible(true) + .setActive(true); + + const config = args[0]; + this.showInfoContainer(config); + + return super.show(args); + } + + public override clear(): void { + super.clear(); + + this.logo // + .setVisible(false) + .setActive(false); + } + + public override destroy(): void { + super.destroy(); + + this.logo.destroy(); + } +} diff --git a/src/ui/handlers/login-register-info-container-ui-handler.ts b/src/ui/handlers/login-register-info-container-ui-handler.ts new file mode 100644 index 00000000000..556ab94e15f --- /dev/null +++ b/src/ui/handlers/login-register-info-container-ui-handler.ts @@ -0,0 +1,233 @@ +import { globalScene } from "#app/global-scene"; +import { UiMode } from "#enums/ui-mode"; +import { languageOptions } from "#system/settings-language"; +import type { OptionSelectItem } from "#ui/abstract-option-select-ui-handler"; +import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; +import type { ModalConfig } from "#ui/modal-ui-handler"; +import { fixedInt } from "#utils/common"; +import i18next from "i18next"; +import JSZip from "jszip"; + +interface BuildInteractableImageOpts { + scale?: number; + x?: number; + y?: number; + origin?: { x: number; y: number }; +} + +/** + * The maximum number of saves that are allowed to show up in the username panel pefore + * the `P02: Too many saves` popup is displayed. + * + * @privateRemarks + * This limitation is in place to allow for the password reset helpers to get + * enough information in one screenshot. If the user has too many saves, this + * complicates the interaction as it would require scrolling, which will + * make tickets take longer to resolve. + */ +const MAX_SAVES_FOR_USERNAME_PANEL = 7; + +const ERR_NO_SAVES: string = "No save files found"; +const ERR_TOO_MANY_SAVES: string = "Too many save files found"; + +// TODO: use mixins +export abstract class LoginRegisterInfoContainerUiHandler extends FormModalUiHandler { + private usernameInfoImage: Phaser.GameObjects.Image; + private saveDownloadImage: Phaser.GameObjects.Image; + private changeLanguageImage: Phaser.GameObjects.Image; + private infoContainer: Phaser.GameObjects.Container; + + public override getReadableErrorMessage(error: string): string { + if (!error) { + return ""; + } + + const colonIndex = error.indexOf(":"); + if (colonIndex > 0) { + error = error.slice(0, colonIndex); + } + + switch (error) { + case ERR_NO_SAVES: + return "P01: " + i18next.t("menu:noSaves"); + case ERR_TOO_MANY_SAVES: + return "P02: " + i18next.t("menu:tooManySaves"); + } + + return super.getReadableErrorMessage(error); + } + + public override setup(): void { + super.setup(); + this.buildInfoContainer(); + } + + public override clear(): void { + super.clear(); + this.infoContainer // + .setVisible(false) + .setActive(false); + this.setMouseCursorStyle("default"); // reset cursor + } + + public override destroy(): void { + super.destroy(); + this.infoContainer.destroy(); + } + + private buildInfoContainer() { + this.usernameInfoImage = this.buildInteractableImage("settings_icon", "username-info-icon", { x: 0, scale: 0.5 }); + this.saveDownloadImage = this.buildInteractableImage("saving_icon", "save-download-icon", { x: 20, scale: 0.75 }); + this.changeLanguageImage = this.buildInteractableImage("language_icon", "change-language-icon", { + x: 40, + scale: 0.5, + }); + + this.infoContainer = globalScene.add + .container(0, 0) + .add([this.usernameInfoImage, this.saveDownloadImage, this.changeLanguageImage]) + .setVisible(false) + .disableInteractive(); + + this.getUi().add(this.infoContainer); + } + + protected showInfoContainer(config: ModalConfig) { + this.infoContainer // + .setPosition(5, -76) + .setVisible(true); + this.getUi().moveTo(this.infoContainer, this.getUi().length - 1); + + this.usernameInfoImage // + .setPositionRelative(this.infoContainer, 0, 0) + .on("pointerdown", () => this.showUsernames(config)); + + this.saveDownloadImage // + .setPositionRelative(this.infoContainer, 20, 0) + .on("pointerdown", () => this.downloadSaves(config)); + + this.changeLanguageImage // + .setPositionRelative(this.infoContainer, 40, 0) + .on("pointerdown", () => { + globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { + options: languageOptions, + maxOptions: 7, + delay: 1000, + }); + }); + + this.infoContainer.setAlpha(0); + globalScene.tweens.add({ + targets: this.infoContainer, + duration: fixedInt(1000), + ease: "Sine.easeInOut", + y: "-=24", + alpha: 1, + }); + } + + /** + * Show a panel with all usernames found in localStorage + * @remarks + * Up to {@linkcode MAX_SAVES_FOR_USERNAME_PANEL} usernames are shown, otherwise P02 is triggered + * @param config - The modal configuration + */ + private showUsernames(config: ModalConfig): void { + if (globalScene.tweens.getTweensOf(this.infoContainer).length > 0) { + return; + } + + const localStorageKeys = Object.keys(localStorage); + const keyToFind = "data_"; + const dataKeys = localStorageKeys.filter(ls => ls.includes(keyToFind)); + + if (dataKeys.length === 0) { + this.onFail(ERR_NO_SAVES, config); + return; + } + if (dataKeys.length > MAX_SAVES_FOR_USERNAME_PANEL) { + this.onFail(ERR_TOO_MANY_SAVES, config); + return; + } + + const options: OptionSelectItem[] = []; + const handler = () => { + globalScene.ui.revertMode(); + this.infoContainer.disableInteractive(); + return true; + }; + + for (const key of dataKeys) { + options.push({ label: key.replace(keyToFind, ""), handler }); + } + + globalScene.ui.setOverlayMode(UiMode.OPTION_SELECT, { options, delay: 1000 }); + this.infoContainer.setInteractive( + new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width, globalScene.game.canvas.height), + Phaser.Geom.Rectangle.Contains, + ); + } + + /** + * Collect the user's save files from localStorage and download them as a zip file + * @remarks + * Used as the `pointerDown` callback for the save download image + * @param config - The modal configuration + */ + private async downloadSaves(config: ModalConfig): Promise { + // find all data_ and sessionData keys, put them in a .txt file and download everything in a single zip + const localStorageKeys = Object.keys(localStorage); + const keyToFind = "data_"; + const sessionKeyToFind = "sessionData"; + const dataKeys = localStorageKeys.filter(ls => ls.includes(keyToFind)); + const sessionKeys = localStorageKeys.filter(ls => ls.includes(sessionKeyToFind)); + + if (dataKeys.length <= 0 && sessionKeys.length <= 0) { + this.onFail(ERR_NO_SAVES, config); + return; + } + + const zip = new JSZip(); + // Bang is safe here because of the filter above + for (const dataKey of dataKeys) { + zip.file(dataKey + ".prsv", localStorage.getItem(dataKey)!); + } + for (const sessionKey of sessionKeys) { + zip.file(sessionKey + ".prsv", localStorage.getItem(sessionKey)!); + } + + const content = await zip.generateAsync({ type: "blob" }); + const url = URL.createObjectURL(content); + const a = document.createElement("a"); + + a.href = url; + a.download = "pokerogue_saves.zip"; + a.click(); + + URL.revokeObjectURL(url); + } + + private onFail(error: string, config: ModalConfig): void { + const ui = globalScene.ui; + ui.setMode(UiMode.LOADING, { buttonActions: [] }); + ui.setModeForceTransition(UiMode.LOGIN_OR_REGISTER, Object.assign(config, { errorMessage: error?.trim() })); + ui.playError(); + } + + protected buildInteractableImage( + texture: string, + name: string, + opts: BuildInteractableImageOpts = {}, + ): Phaser.GameObjects.Image { + const { scale = 0.07, x = 0, y = 0, origin = { x: 0, y: 0 } } = opts; + const img = globalScene.add + .image(x, y, texture) + .setName(name) + .setOrigin(origin.x, origin.y) + .setScale(scale) + .setInteractive(); + this.addInteractionHoverEffect(img); + + return img; + } +} diff --git a/src/ui/handlers/oauth-providers-ui-handler.ts b/src/ui/handlers/oauth-providers-ui-handler.ts new file mode 100644 index 00000000000..8839f6880a1 --- /dev/null +++ b/src/ui/handlers/oauth-providers-ui-handler.ts @@ -0,0 +1,107 @@ +import { globalScene } from "#app/global-scene"; +import { TextStyle } from "#enums/text-style"; +import { LoginRegisterInfoContainerUiHandler } from "#ui/login-register-info-container-ui-handler"; +import { addTextObject } from "#ui/text"; +import { addWindow } from "#ui/ui-theme"; +import { fixedInt } from "#utils/common"; +import i18next from "i18next"; + +// TODO: use mixins +export abstract class OAuthProvidersUiHandler extends LoginRegisterInfoContainerUiHandler { + private discordImage: Phaser.GameObjects.Image; + private googleImage: Phaser.GameObjects.Image; + + private externalPartyContainer: Phaser.GameObjects.Container; + private externalPartyBg: Phaser.GameObjects.NineSlice; + private externalPartyTitle: Phaser.GameObjects.Text; + + public override setup(): void { + super.setup(); + this.buildExternalPartyContainer(); + } + + public override clear(): void { + super.clear(); + + this.externalPartyContainer // + .setVisible(false) + .setActive(false); + + [this.discordImage, this.googleImage].forEach(img => { + img.off("pointerdown"); + }); + } + + public override destroy(): void { + super.destroy(); + this.externalPartyContainer.destroy(); + } + + private buildExternalPartyContainer(): void { + const { height, width } = globalScene.scaledCanvas; + + this.externalPartyContainer = globalScene.add + .container(0, 0) + .setInteractive(new Phaser.Geom.Rectangle(0, 0, width / 2, height / 2), Phaser.Geom.Rectangle.Contains); + this.externalPartyTitle = addTextObject(0, 4, "", TextStyle.SETTINGS_LABEL) // + .setOrigin(0.5, 0); + this.externalPartyBg = addWindow(0, 0, 0, 0); + + this.discordImage = this.buildInteractableImage("discord", "discord-icon"); + this.googleImage = this.buildInteractableImage("google", "google-icon"); + + this.externalPartyContainer + .add([this.externalPartyBg, this.externalPartyTitle, this.discordImage, this.googleImage]) + .setVisible(false); + this.getUi().add(this.externalPartyContainer); + } + + protected processExternalProvider(): void { + const titleX = 22; + this.externalPartyTitle + .setText(i18next.t("menu:orUse")) + .setX(titleX + this.externalPartyTitle.text.length) + .setVisible(true); + + this.externalPartyContainer // + .setPositionRelative(this.modalContainer, 175, 0) + .setVisible(true); + + const bgWidth = this.externalPartyTitle.text.length + 50; + this.externalPartyBg.setSize(bgWidth, this.modalBg.height); + this.getUi().moveTo(this.externalPartyContainer, this.getUi().length - 1); + + const externalPartyIconWidth = this.externalPartyBg.width / 3.1; + + const getRedirectUri = (service: string): string => { + return encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/${service}/callback`); + }; + + this.discordImage // + .setPosition(externalPartyIconWidth, this.externalPartyBg.height - 40) + .on("pointerdown", () => { + const redirectUri = getRedirectUri("discord"); + const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; + const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&prompt=none`; + window.open(discordUrl, "_self"); + }); + + this.googleImage // + .setPosition(externalPartyIconWidth, this.externalPartyBg.height - 60) + .on("pointerdown", () => { + const redirectUri = getRedirectUri("google"); + const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; + const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&redirect_uri=${redirectUri}&response_type=code&scope=openid`; + window.open(googleUrl, "_self"); + }); + + this.externalPartyContainer.setAlpha(0); + globalScene.tweens.add({ + targets: this.externalPartyContainer, + duration: fixedInt(1000), + ease: "Sine.easeInOut", + y: "-=24", + alpha: 1, + }); + } +} diff --git a/src/ui/handlers/registration-form-ui-handler.ts b/src/ui/handlers/registration-form-ui-handler.ts index e5220351497..250a243e494 100644 --- a/src/ui/handlers/registration-form-ui-handler.ts +++ b/src/ui/handlers/registration-form-ui-handler.ts @@ -3,33 +3,33 @@ import { globalScene } from "#app/global-scene"; import { TextStyle } from "#enums/text-style"; import { UiMode } from "#enums/ui-mode"; import type { InputFieldConfig } from "#ui/form-modal-ui-handler"; -import { FormModalUiHandler } from "#ui/form-modal-ui-handler"; import type { ModalConfig } from "#ui/modal-ui-handler"; import { addTextObject } from "#ui/text"; import i18next from "i18next"; +import { LoginRegisterInfoContainerUiHandler } from "./login-register-info-container-ui-handler"; -export class RegistrationFormUiHandler extends FormModalUiHandler { - getModalTitle(_config?: ModalConfig): string { +export class RegistrationFormUiHandler extends LoginRegisterInfoContainerUiHandler { + public override getModalTitle(): string { return i18next.t("menu:register"); } - getWidth(_config?: ModalConfig): number { + public override getWidth(): number { return 160; } - getMargin(_config?: ModalConfig): [number, number, number, number] { - return [0, 0, 48, 0]; + public override getMargin(): [number, number, number, number] { + return [0, 20, 48, 0]; } - getButtonTopMargin(): number { + public override getButtonTopMargin(): number { return 12; } - getButtonLabels(_config?: ModalConfig): string[] { - return [i18next.t("menu:register"), i18next.t("menu:backToLogin")]; + public override getButtonLabels(): string[] { + return [i18next.t("menu:register"), i18next.t("menu:goBack")]; } - getReadableErrorMessage(error: string): string { + public override getReadableErrorMessage(error: string): string { const colonIndex = error?.indexOf(":"); if (colonIndex > 0) { error = error.slice(0, colonIndex); @@ -46,7 +46,7 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { return super.getReadableErrorMessage(error); } - override getInputFieldConfigs(): InputFieldConfig[] { + public override getInputFieldConfigs(): InputFieldConfig[] { const inputFieldConfigs: InputFieldConfig[] = []; inputFieldConfigs.push({ label: i18next.t("menu:username") }); inputFieldConfigs.push({ @@ -60,7 +60,7 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { return inputFieldConfigs; } - setup(): void { + public override setup(): void { super.setup(); const label = addTextObject(10, 87, i18next.t("menu:registrationAgeWarning"), TextStyle.TOOLTIP_CONTENT, { @@ -71,60 +71,61 @@ export class RegistrationFormUiHandler extends FormModalUiHandler { this.modalContainer.add(label); } - show(args: any[]): boolean { - if (super.show(args)) { - const config = args[0] as ModalConfig; - - const originalRegistrationAction = this.submitAction; - this.submitAction = () => { - if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { - // Prevent overlapping overrides on action modification - this.submitAction = originalRegistrationAction; - this.sanitizeInputs(); - globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); - const onFail = error => { - globalScene.ui.setMode(UiMode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() })); - globalScene.ui.playError(); - }; - if (!this.inputs[0].text) { - return onFail(i18next.t("menu:emptyUsername")); - } - if (!this.inputs[1].text) { - return onFail(this.getReadableErrorMessage("invalid password")); - } - if (this.inputs[1].text !== this.inputs[2].text) { - return onFail(i18next.t("menu:passwordNotMatchingConfirmPassword")); - } - const [usernameInput, passwordInput] = this.inputs; - pokerogueApi.account - .register({ - username: usernameInput.text, - password: passwordInput.text, - }) - .then(registerError => { - if (!registerError) { - pokerogueApi.account - .login({ - username: usernameInput.text, - password: passwordInput.text, - }) - .then(loginError => { - if (!loginError) { - originalRegistrationAction?.(); - } else { - onFail(loginError); - } - }); - } else { - onFail(registerError); - } - }); - } - }; - - return true; + public override show(args: [ModalConfig, ...any[]]): boolean { + if (!super.show(args)) { + return false; } - return false; + const config = args[0]; + this.showInfoContainer(config); + + const originalRegistrationAction = this.submitAction; + this.submitAction = () => { + if (globalScene.tweens.getTweensOf(this.modalContainer).length === 0) { + // Prevent overlapping overrides on action modification + this.submitAction = originalRegistrationAction; + this.sanitizeInputs(); + globalScene.ui.setMode(UiMode.LOADING, { buttonActions: [] }); + const onFail = error => { + globalScene.ui.setMode(UiMode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.playError(); + }; + if (!this.inputs[0].text) { + return onFail(i18next.t("menu:emptyUsername")); + } + if (!this.inputs[1].text) { + return onFail(this.getReadableErrorMessage("invalid password")); + } + if (this.inputs[1].text !== this.inputs[2].text) { + return onFail(i18next.t("menu:passwordNotMatchingConfirmPassword")); + } + const [usernameInput, passwordInput] = this.inputs; + pokerogueApi.account + .register({ + username: usernameInput.text, + password: passwordInput.text, + }) + .then(registerError => { + if (registerError) { + onFail(registerError); + } else { + pokerogueApi.account + .login({ + username: usernameInput.text, + password: passwordInput.text, + }) + .then(loginError => { + if (loginError) { + onFail(loginError); + } else { + originalRegistrationAction?.(); + } + }); + } + }); + } + }; + + return true; } } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 29a3acd983e..4ba03d564e8 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -26,6 +26,7 @@ import { GamepadBindingUiHandler } from "#ui/gamepad-binding-ui-handler"; import { KeyboardBindingUiHandler } from "#ui/keyboard-binding-ui-handler"; import { LoadingModalUiHandler } from "#ui/loading-modal-ui-handler"; import { LoginFormUiHandler } from "#ui/login-form-ui-handler"; +import { LoginOrRegisterUiHandler } from "#ui/login-or-register-ui-handler"; import { MenuUiHandler } from "#ui/menu-ui-handler"; import { MessageUiHandler } from "#ui/message-ui-handler"; import { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler"; @@ -163,6 +164,7 @@ export class UI extends Phaser.GameObjects.Container { new PokedexUiHandler(), new PokedexScanUiHandler(UiMode.TEST_DIALOGUE), new PokedexPageUiHandler(), + new LoginOrRegisterUiHandler(), new LoginFormUiHandler(), new RegistrationFormUiHandler(), new LoadingModalUiHandler(), From 4545f9aae3132b8307a3b2f0d09a5d1fbf337970 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:54:38 -0600 Subject: [PATCH 072/101] [Misc] Remove `| null` from `user` param of `PokemonAttackCondition` https://github.com/pagefaultgames/pokerogue/pull/6859 --- src/@types/ability-types.ts | 2 +- src/data/abilities/ability.ts | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/@types/ability-types.ts b/src/@types/ability-types.ts index dc3b348c507..7b9c4ba1e1e 100644 --- a/src/@types/ability-types.ts +++ b/src/@types/ability-types.ts @@ -8,7 +8,7 @@ import type { Move } from "#moves/move"; export type * from "#abilities/ability"; export type AbAttrCondition = (pokemon: Pokemon) => boolean; -export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; +export type PokemonAttackCondition = (user: Pokemon, target: Pokemon | null, move: Move) => boolean; export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; export type PokemonStatStageChangeCondition = ( target: Pokemon, diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index f258314157b..7adce995dd8 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2352,7 +2352,7 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { override canApply(params: PostMoveInteractionAbAttrParams): boolean { const { pokemon, move, opponent } = params; - /**Battler tags inflicted by abilities post attacking are also considered additional effects.*/ + // Battler tags inflicted by abilities post attacking are also considered additional effects. return ( super.canApply(params) && !opponent.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") @@ -5792,10 +5792,10 @@ export interface MoveAbilityBypassAbAttrParams extends AbAttrBaseParams { export class MoveAbilityBypassAbAttr extends AbAttr { private readonly moveIgnoreFunc: (pokemon: Pokemon, move: Move) => boolean; - constructor(moveIgnoreFunc?: (pokemon: Pokemon, move: Move) => boolean) { + constructor(moveIgnoreFunc: (pokemon: Pokemon, move: Move) => boolean = () => true) { super(false); - this.moveIgnoreFunc = moveIgnoreFunc || ((_pokemon, _move) => true); + this.moveIgnoreFunc = moveIgnoreFunc; } override canApply({ pokemon, move, cancelled }: MoveAbilityBypassAbAttrParams): boolean { @@ -7133,8 +7133,8 @@ export function initAbilities() { .ignorable() .build(), new AbBuilder(AbilityId.RIVALRY, 4) - .attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25) - .attr(MovePowerBoostAbAttr, (user, target, _move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75) + .attr(MovePowerBoostAbAttr, (user, target, _move) => user.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user.gender === target?.gender, 1.25) + .attr(MovePowerBoostAbAttr, (user, target, _move) => user.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user.gender !== target?.gender, 0.75) .build(), new AbBuilder(AbilityId.STEADFAST, 4) .attr(FlinchStatStageChangeAbAttr, [ Stat.SPD ], 1) @@ -7367,10 +7367,10 @@ export function initAbilities() { .ignorable() .build(), new AbBuilder(AbilityId.TOXIC_BOOST, 5) - .attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.PHYSICAL && (user?.status?.effect === StatusEffect.POISON || user?.status?.effect === StatusEffect.TOXIC), 1.5) + .attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.PHYSICAL && (user.status?.effect === StatusEffect.POISON || user.status?.effect === StatusEffect.TOXIC), 1.5) .build(), new AbBuilder(AbilityId.FLARE_BOOST, 5) - .attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.SPECIAL && user?.status?.effect === StatusEffect.BURN, 1.5) + .attr(MovePowerBoostAbAttr, (user, _target, move) => move.category === MoveCategory.SPECIAL && user.status?.effect === StatusEffect.BURN, 1.5) .build(), new AbBuilder(AbilityId.HARVEST, 5) .attr( @@ -7414,7 +7414,7 @@ export function initAbilities() { new AbBuilder(AbilityId.ANALYTIC, 5) .attr(MovePowerBoostAbAttr, (user) => // Boost power if all other Pokemon have already moved (no other moves are slated to execute) - !globalScene.phaseManager.hasPhaseOfType("MovePhase", phase => phase.pokemon.id !== user?.id), + !globalScene.phaseManager.hasPhaseOfType("MovePhase", phase => phase.pokemon.id !== user.id), 1.3) .build(), new AbBuilder(AbilityId.ILLUSION, 5) @@ -7596,8 +7596,7 @@ export function initAbilities() { .attr(AddSecondStrikeAbAttr) // Only multiply damage on the last strike of multi-strike moves .attr(MoveDamageBoostAbAttr, 0.25, (user, target, move) => ( - !!user - && user.turnData.hitCount > 1 // move was originally multi hit + user.turnData.hitCount > 1 // move was originally multi hit && user.turnData.hitsLeft === 1 // move is on its final strike && move.canBeMultiStrikeEnhanced(user, true, target) ) @@ -7857,7 +7856,7 @@ export function initAbilities() { .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75) .build(), new AbBuilder(AbilityId.NEUROFORCE, 7) - .attr(MovePowerBoostAbAttr, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) >= 2, 1.25) + .attr(MovePowerBoostAbAttr, (user, target, move) => (target?.getMoveEffectiveness(user, move) ?? 1) >= 2, 1.25) .build(), new AbBuilder(AbilityId.INTREPID_SWORD, 8) .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true) From 49d89d469afb360fc5d0e907534888cd31a9a3d7 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:52:23 -0500 Subject: [PATCH 073/101] [Test] Cache arena tag matcher types; fix `BattlerTag` matcher distributiveness (#6821) --- test/test-utils/matchers/to-have-arena-tag.ts | 35 +++++++++++++------ .../matchers/to-have-battler-tag.ts | 17 +++++---- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/test/test-utils/matchers/to-have-arena-tag.ts b/test/test-utils/matchers/to-have-arena-tag.ts index f54a4365912..dbf74b27242 100644 --- a/test/test-utils/matchers/to-have-arena-tag.ts +++ b/test/test-utils/matchers/to-have-arena-tag.ts @@ -9,22 +9,37 @@ import type { ArenaTagDataMap, SerializableArenaTagType } from "#types/arena-tag import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; /** - * Options type for {@linkcode toHaveArenaTag}. - * @typeParam A - The {@linkcode ArenaTagType} being checked - * @remarks - * If A corresponds to a serializable `ArenaTag`, only properties allowed to be serialized - * (i.e. can change across instances) will be present and able to be checked. + * Helper type for serializable arena tag options. + * Allows for caching to avoid repeated instantiation and faster typechecking. + * @internal */ -export type toHaveArenaTagOptions = OneOther< - A extends SerializableArenaTagType ? ArenaTagDataMap[A] : ArenaTagTypeMap[A], - "tagType" | "side" -> & { +type SerializableArenaTagOptions = OneOther & { tagType: A; }; +/** + * Helper type for non-serializable arena tag options. + * Allows for caching to avoid repeated instantiation and faster typechecking. + * @internal + */ +type NonSerializableArenaTagOptions = OneOther & { + tagType: A; +}; + +/** + * Options type for {@linkcode toHaveArenaTag}. + * @typeParam A - The {@linkcode ArenaTagType} being checked + * @remarks + * If `A` corresponds to a serializable `ArenaTag`, only properties allowed to be serialized + * (i.e. can change across instances) will be present and able to be checked. + */ +export type toHaveArenaTagOptions = [A] extends [SerializableArenaTagType] + ? SerializableArenaTagOptions + : NonSerializableArenaTagOptions; + /** * Matcher to check if the {@linkcode Arena} has a given {@linkcode ArenaTag} active. - * @param received - The object to check. Should be the current {@linkcode GameManager}. + * @param received - The object to check. Should be the current {@linkcode GameManager} * @param expectedTag - The `ArenaTagType` of the desired tag, or a partially-filled object * containing the desired properties * @param side - The {@linkcode ArenaTagSide | side of the field} the tag should affect, or diff --git a/test/test-utils/matchers/to-have-battler-tag.ts b/test/test-utils/matchers/to-have-battler-tag.ts index 5f014247806..c8a8e39d2bd 100644 --- a/test/test-utils/matchers/to-have-battler-tag.ts +++ b/test/test-utils/matchers/to-have-battler-tag.ts @@ -8,22 +8,21 @@ import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import type { BattlerTagDataMap, SerializableBattlerTagType } from "#types/battler-tags"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; -// intersection required to preserve T for inferences - /** - * Helper type for serializable battler tag options. Allows for caching of the type to avoid - * instantiation each time typescript encounters the type. (dramatically speeds up typechecking) + * Helper type for serializable battler tag options. + * Allows for caching to avoid repeated instantiation and faster typechecking. * @internal */ -type SerializableTagOptions = OneOther & { +type SerializableBattlerTagOptions = OneOther & { tagType: B; }; /** * Helper type for non-serializable battler tag options. + * Allows for caching to avoid repeated instantiation and faster typechecking. * @internal */ -type NonSerializableTagOptions = OneOther & { +type NonSerializableBattlerTagOptions = OneOther & { tagType: B; }; @@ -34,9 +33,9 @@ type NonSerializableTagOptions = OneOther = B extends SerializableBattlerTagType - ? SerializableTagOptions - : NonSerializableTagOptions; +export type toHaveBattlerTagOptions = [B] extends [SerializableBattlerTagType] + ? SerializableBattlerTagOptions + : NonSerializableBattlerTagOptions; /** * Matcher that checks if a {@linkcode Pokemon} has a specific {@linkcode BattlerTag}. From a47633a779e00560375292d75a1198e553f84a8a Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:54:23 -0800 Subject: [PATCH 074/101] [Misc] Update submodules --- assets | 2 +- locales | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index 5481af203f1..a724d2fec98 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 5481af203f1de7da88d803f68b01e9ff776cd851 +Subproject commit a724d2fec98c412df28a635c1d90fe2f790cb5dc diff --git a/locales b/locales index 3a9418f4083..8f30c25c114 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit 3a9418f40832eaf86c6b4161dfc60562ae9bf756 +Subproject commit 8f30c25c114a8cfc8648000f5c19e07661a44474 From a6554acfe3032951e5bebcde3f08695ef53e91bf Mon Sep 17 00:00:00 2001 From: Lugiad Date: Fri, 19 Dec 2025 03:57:37 +0100 Subject: [PATCH 075/101] [i18n] Indonesian & Hindi addition (#6850) --- src/plugins/i18n.ts | 6 ++++-- src/system/settings/settings-language.ts | 8 ++++++++ src/ui/handlers/game-stats-ui-handler.ts | 2 +- src/ui/handlers/pokedex-page-ui-handler.ts | 8 ++++++++ src/ui/handlers/starter-select-ui-handler.ts | 8 ++++++++ src/ui/settings/settings-display-ui-handler.ts | 12 ++++++++++++ src/utils/common.ts | 2 ++ 7 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index fadfaa38168..e84f0dfd566 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -80,13 +80,13 @@ const fonts: LoadingFontFaceProperty[] = [ face: new FontFace("emerald", "url(./fonts/pokemon-bw.ttf)", { unicodeRange: rangesByLanguage.japanese, }), - only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "tl"], + only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "id", "hi", "tl"], }, { face: new FontFace("pkmnems", "url(./fonts/pokemon-bw.ttf)", { unicodeRange: rangesByLanguage.japanese, }), - only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "tl"], + only: ["en", "es", "fr", "it", "de", "pt", "ko", "ja", "ca", "da", "tr", "ro", "ru", "id", "hi", "tl"], }, // devanagari { @@ -196,6 +196,8 @@ await i18next "tr", "ro", "ru", + "id", + "hi", "tl", "nb-NO", ], diff --git a/src/system/settings/settings-language.ts b/src/system/settings/settings-language.ts index 5235defab22..b1df2444507 100644 --- a/src/system/settings/settings-language.ts +++ b/src/system/settings/settings-language.ts @@ -82,6 +82,14 @@ export const languageOptions = [ label: "Русский (Needs Help)", handler: () => changeLocaleHandler("ru"), }, + { + label: "Bahasa Indonesia (Needs Help)", + handler: () => changeLocaleHandler("id"), + }, + { + label: "हिन्दी (Needs Help)", + handler: () => changeLocaleHandler("hi"), + }, { label: "Dansk (Needs Help)", handler: () => changeLocaleHandler("da"), diff --git a/src/ui/handlers/game-stats-ui-handler.ts b/src/ui/handlers/game-stats-ui-handler.ts index 802bd982813..4dd9e163961 100644 --- a/src/ui/handlers/game-stats-ui-handler.ts +++ b/src/ui/handlers/game-stats-ui-handler.ts @@ -250,7 +250,7 @@ export class GameStatsUiHandler extends UiHandler { const resolvedLang = i18next.resolvedLanguage ?? "en"; // NOTE TO TRANSLATION TEAM: Add more languages that want to display // in a single-column inside of the `[]` (e.g. `["ru", "fr"]`) - return ["fr", "es-ES", "es-419", "it", "ja", "pt-BR", "ru", "tr"].includes(resolvedLang); + return ["fr", "es-ES", "es-419", "it", "ja", "pt-BR", "ru", "id", "tr"].includes(resolvedLang); } /** The number of columns used by this menu in the resolved language */ private get columnCount(): 1 | 2 { diff --git a/src/ui/handlers/pokedex-page-ui-handler.ts b/src/ui/handlers/pokedex-page-ui-handler.ts index 5e87872f7c2..c43911be1f1 100644 --- a/src/ui/handlers/pokedex-page-ui-handler.ts +++ b/src/ui/handlers/pokedex-page-ui-handler.ts @@ -147,6 +147,14 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 26, }, + id: { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + hi: { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, "nb-NO": { starterInfoTextSize: "56px", instructionTextSize: "38px", diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index c487c0dcfea..e647dd7958f 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -168,6 +168,14 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoYOffset: 0.5, starterInfoXPos: 26, }, + id: { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, + hi: { + starterInfoTextSize: "56px", + instructionTextSize: "38px", + }, tl: { starterInfoTextSize: "56px", instructionTextSize: "38px", diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index df39e4a6d0c..bbaa4a9f4be 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -105,6 +105,18 @@ export class SettingsDisplayUiHandler extends AbstractSettingsUiHandler { label: "Русский (Needs Help)", }; break; + case "id": + this.settings[languageIndex].options[0] = { + value: "Bahasa Indonesia", + label: "Bahasa Indonesia (Needs Help)", + }; + break; + case "hi": + this.settings[languageIndex].options[0] = { + value: "हिन्दी", + label: "हिन्दी (Needs Help)", + }; + break; case "da": this.settings[languageIndex].options[0] = { value: "Dansk", diff --git a/src/utils/common.ts b/src/utils/common.ts index ee93b484625..e5c8d54e362 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -413,6 +413,8 @@ export function hasAllLocalizedSprites(lang?: string): boolean { case "ja": case "ca": case "ru": + case "id": + case "hi": case "tl": case "nb-NO": return true; From c7bdfe7ed8e7a4f3dc8244dd9a68af4ee91f7ee0 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 18 Dec 2025 22:07:48 -0500 Subject: [PATCH 076/101] [Bug] Reset hit-related turn data inside `MoveEndPhase` (#6637) * Reset hit-related turn data inside `MoveEndPhase` and remove `extraTurns` field * Fixed FS edge case * Fixed test hit count checks going past move end phase * fixed PB tests * Put `default` switch case last again --- src/data/abilities/ability.ts | 1 - src/data/moves/move.ts | 17 ++++---- src/data/pokemon/pokemon-data.ts | 29 +++++++------ src/data/positional-tags/positional-tag.ts | 6 +-- src/phases/move-effect-phase.ts | 9 +---- src/phases/move-end-phase.ts | 8 ++++ src/phases/move-phase.ts | 7 ---- test/abilities/parental-bond.test.ts | 47 ++++++++-------------- test/abilities/wimp-out.test.ts | 9 +++-- test/items/multi-lens.test.ts | 8 ++-- test/moves/beak-blast.test.ts | 13 ------ test/moves/electro-shot.test.ts | 2 +- test/moves/protect.test.ts | 3 +- 13 files changed, 67 insertions(+), 92 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 7adce995dd8..348764d4659 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -5100,7 +5100,6 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { */ override apply({ source, pokemon, move, targets, simulated }: PostMoveUsedAbAttrParams): void { if (!simulated) { - pokemon.turnData.extraTurns++; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) { const target = this.getTarget(pokemon, source, targets); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 1073473b48f..3376dc26e0a 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1860,7 +1860,9 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { super(0); } - apply(user: Pokemon, target: Pokemon, _move: Move, args: any[]): boolean { + apply(user: Pokemon, target: Pokemon, _move: Move, args: [NumberHolder, ...any[]]): boolean { + const [dmg] = args; + // first, determine if the hit is coming from multi lens or not const lensCount = user @@ -1869,23 +1871,23 @@ export class TargetHalfHpDamageAttr extends FixedDamageAttr { ?.getStackCount() ?? 0; if (lensCount <= 0) { // no multi lenses; we can just halve the target's hp and call it a day - (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); + dmg.value = toDmgValue(target.hp / 2); return true; } // figure out what hit # we're on switch (user.turnData.hitCount - user.turnData.hitsLeft) { case lensCount + 1: - // parental bond added hit; calc damage as normal - (args[0] as NumberHolder).value = toDmgValue(target.hp / 2); + // parental bond added hit; halve target's hp as normal + dmg.value = toDmgValue(target.hp / 2); return true; - // biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional? + // biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional case 0: - // first hit of move; update initialHp tracker + // first hit of move; update initialHp tracker for first hit this.initialHp = target.hp; default: // multi lens added hit; use initialHp tracker to ensure correct damage - (args[0] as NumberHolder).value = toDmgValue(this.initialHp / 2); + dmg.value = toDmgValue(this.initialHp / 2); return true; } } @@ -7861,7 +7863,6 @@ export class RepeatMoveAttr extends MoveEffectAttr { targetPokemonName: getPokemonNameWithAffix(target), }), ); - target.turnData.extraTurns++; globalScene.phaseManager.unshiftNew( "MovePhase", target, diff --git a/src/data/pokemon/pokemon-data.ts b/src/data/pokemon/pokemon-data.ts index f0a0fc88c59..40b03da12b6 100644 --- a/src/data/pokemon/pokemon-data.ts +++ b/src/data/pokemon/pokemon-data.ts @@ -124,12 +124,17 @@ export class PokemonSummonData { public stats: number[] = [0, 0, 0, 0, 0, 0]; public moveset: PokemonMove[] | null; - // If not initialized this value will not be populated from save data. public types: PokemonType[] = []; public addedType: PokemonType | null = null; - /** Data pertaining to this pokemon's illusion. */ + /** Data pertaining to this pokemon's Illusion, if it has one. */ public illusion: IllusionData | null = null; + /** + * Whether this Pokemon's illusion has been broken since switching out. + * @defaultValue `false` + */ + // TODO: Since Illusion applies on switch in, and this entire class is reset on switch-in, + // this may be replaceable with a check for `pokemon.summonData.illusionData !== null` public illusionBroken = false; /** Array containing all berries eaten in the last turn; used by {@linkcode AbilityId.CUD_CHEW} */ @@ -139,6 +144,7 @@ export class PokemonSummonData { * An array of all moves this pokemon has used since entering the battle. * Used for most moves and abilities that check prior move usage or copy already-used moves. */ + // TODO: Rework this into a sort of "global move history" that also allows checking execution order (for Fusion Bolt/Flare) public moveHistory: TurnMove[] = []; constructor(source?: PokemonSummonData | SerializedPokemonSummonData) { @@ -302,8 +308,10 @@ export class PokemonTurnData { /** How many times the current move should hit the target(s) */ public hitCount = 0; /** - * - `-1` = Calculate how many hits are left - * - `0` = Move is finished + * - `-1`: Calculate how many hits are left + * - `0`: Move is finished + * - `>0`: Move is in process of hitting targets + * @defaultValue `-1` */ public hitsLeft = -1; public totalDamageDealt = 0; @@ -320,20 +328,17 @@ export class PokemonTurnData { public summonedThisTurn = false; public failedRunAway = false; public joinedRound = false; - /** Tracker for a pending status effect + /** + * Tracker for a pending status effect. * * @remarks * Set whenever {@linkcode Pokemon#trySetStatus} succeeds in order to prevent subsequent status effects - * from being applied. Necessary because the status is not actually set until the {@linkcode ObtainStatusEffectPhase} runs, + * from being applied. \ + * Necessary because the status is not actually set until the {@linkcode ObtainStatusEffectPhase} runs, * which may not happen before another status effect is attempted to be applied. + * @defaultValue `StatusEffect.NONE` */ public pendingStatus: StatusEffect = StatusEffect.NONE; - /** - * The amount of times this Pokemon has acted again and used a move in the current turn. - * Used to make sure multi-hits occur properly when the user is - * forced to act again in the same turn, and **must be incremented** by any effects that grant extra actions. - */ - public extraTurns = 0; /** * All berries eaten by this pokemon in this turn. * Saved into {@linkcode PokemonSummonData | SummonData} by {@linkcode AbilityId.CUD_CHEW} on turn end. diff --git a/src/data/positional-tags/positional-tag.ts b/src/data/positional-tags/positional-tag.ts index e6d5cb245c5..f1fc29f59d4 100644 --- a/src/data/positional-tags/positional-tag.ts +++ b/src/data/positional-tags/positional-tag.ts @@ -99,10 +99,8 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs public override trigger(): void { // Bangs are justified as the `shouldTrigger` method will queue the tag for removal // if the source or target no longer exist - const source = globalScene.getPokemonById(this.sourceId)!; const target = this.getTarget()!; - source.turnData.extraTurns++; globalScene.phaseManager.queueMessage( i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(target), @@ -112,7 +110,9 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs globalScene.phaseManager.unshiftNew( "MoveEffectPhase", - this.sourceId, // TODO: Find an alternate method of passing the source pokemon without a source ID + // TODO: Find an alternate method of passing the (currently off-field) source pokemon + // instead of relying on pokemon getter jank + this.sourceId, [this.targetIndex], allMoves[this.sourceMove], MoveUseMode.DELAYED_ATTACK, diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 3209298a265..f73ee2ae0ec 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -133,6 +133,7 @@ export class MoveEffectPhase extends PokemonPhase { if (anySuccess) { this.moveHistoryEntry.result = MoveResult.SUCCESS; } else { + // If the move failed to impact all targets, disable all subsequent multi-hits user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; this.moveHistoryEntry.result = allMiss ? MoveResult.MISS : MoveResult.FAIL; @@ -258,14 +259,6 @@ export class MoveEffectPhase extends PokemonPhase { // Lapse `MOVE_EFFECT` effects (i.e. semi-invulnerability) when applicable user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); - // If the user is acting again (such as due to Instruct or Dancer), reset hitsLeft/hitCount and - // recalculate hit count for multi-hit moves. - if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) { - user.turnData.hitsLeft = -1; - user.turnData.hitCount = 0; - user.turnData.extraTurns--; - } - /** * If this phase is for the first hit of the invoked move, * resolve the move's total hit count. This block combines the diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index fd893c445ff..02e6750f1aa 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -22,6 +22,14 @@ export class MoveEndPhase extends PokemonPhase { super.start(); const pokemon = this.getPokemon(); + + // Reset hit-related temporary data. + // TODO: These properties should be stored inside a "move in flight" object, + // which this Phase would promptly destroy + if (pokemon) { + pokemon.turnData.hitsLeft = -1; + } + if (!this.wasFollowUp && pokemon?.isActive(true)) { pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 50dd16a4d3f..fa4bf293ba4 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -422,13 +422,6 @@ export class MovePhase extends PokemonPhase { this.doThawCheck(); } - // Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) - if (isVirtual(useMode)) { - const turnData = user.turnData; - turnData.hitsLeft = -1; - turnData.hitCount = 0; - } - const pokemonMove = this.move; // Check move to see if arena.ignoreAbilities should be true. diff --git a/test/abilities/parental-bond.test.ts b/test/abilities/parental-bond.test.ts index 95f0e8d4159..0fd199c0075 100644 --- a/test/abilities/parental-bond.test.ts +++ b/test/abilities/parental-bond.test.ts @@ -37,6 +37,8 @@ describe("Abilities - Parental Bond", () => { .enemyLevel(100); }); + // TODO: Review how many of these tests are duplicated in other files + // and/or in Multi Lens' suite it("should add second strike to attack move", async () => { game.override.moveset([MoveId.TACKLE]); @@ -53,7 +55,7 @@ describe("Abilities - Parental Bond", () => { const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp; enemyStartingHp = enemyPokemon.hp; - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); const secondStrikeDamage = enemyStartingHp - enemyPokemon.hp; @@ -70,7 +72,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(MoveId.POWER_UP_PUNCH); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(leadPokemon.turnData.hitCount).toBe(2); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2); @@ -85,7 +87,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(MoveId.BABY_DOLL_EYES); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); }); @@ -100,7 +102,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(MoveId.DOUBLE_HIT); await game.move.forceHit(); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(leadPokemon.turnData.hitCount).toBe(2); }); @@ -142,7 +144,7 @@ describe("Abilities - Parental Bond", () => { const enemyPokemon = game.field.getEnemyPokemon(); game.move.select(MoveId.DRAGON_RAGE); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - 80); }); @@ -156,11 +158,11 @@ describe("Abilities - Parental Bond", () => { const enemyPokemon = game.field.getEnemyPokemon(); game.move.select(MoveId.COUNTER); - await game.phaseInterceptor.to("DamageAnimPhase"); + await game.phaseInterceptor.to("MoveEndPhase"); const playerDamage = leadPokemon.getMaxHp() - leadPokemon.hp; - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - 4 * playerDamage); }); @@ -168,16 +170,13 @@ describe("Abilities - Parental Bond", () => { it("should not apply to multi-target moves", async () => { game.override.battleStyle("double").moveset([MoveId.EARTHQUAKE]).passiveAbility(AbilityId.LEVITATE); - await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); - - const playerPokemon = game.scene.getPlayerField(); + await game.classicMode.startBattle([SpeciesId.MAGIKARP]); game.move.select(MoveId.EARTHQUAKE); - game.move.select(MoveId.EARTHQUAKE, 1); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); - playerPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1)); + expect(game.field.getPlayerPokemon().turnData.hitCount).toBe(1); }); it("should apply to multi-target moves when hitting only one target", async () => { @@ -208,7 +207,7 @@ describe("Abilities - Parental Bond", () => { expect(leadPokemon.turnData.hitCount).toBe(2); // This test will time out if the user faints - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(leadPokemon.hp).toBe(Math.ceil(leadPokemon.getMaxHp() / 2)); }); @@ -229,7 +228,7 @@ describe("Abilities - Parental Bond", () => { expect(enemyPokemon.hp).toBeGreaterThan(0); expect(leadPokemon.isOfType(PokemonType.FIRE)).toBe(true); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(leadPokemon.isOfType(PokemonType.FIRE)).toBe(false); }); @@ -332,25 +331,11 @@ describe("Abilities - Parental Bond", () => { expect(leadPokemon.turnData.hitCount).toBe(2); expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.status?.effect).toBeUndefined(); }); - it("should not cause user to hit into King's Shield more than once", async () => { - game.override.moveset([MoveId.TACKLE]).enemyMoveset([MoveId.KINGS_SHIELD]); - - await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - - const leadPokemon = game.field.getPlayerPokemon(); - - game.move.select(MoveId.TACKLE); - - await game.phaseInterceptor.to("BerryPhase", false); - - expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1); - }); - it("should not cause user to hit into Storm Drain more than once", async () => { game.override.moveset([MoveId.WATER_GUN]).enemyAbility(AbilityId.STORM_DRAIN); @@ -360,7 +345,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(MoveId.WATER_GUN); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1); }); diff --git a/test/abilities/wimp-out.test.ts b/test/abilities/wimp-out.test.ts index 1caf9bf7701..75c5edf5566 100644 --- a/test/abilities/wimp-out.test.ts +++ b/test/abilities/wimp-out.test.ts @@ -417,7 +417,8 @@ describe("Abilities - Wimp Out", () => { game.move.select(MoveId.ENDURE); game.doSelectPartyPokemon(1); - await game.phaseInterceptor.to("TurnEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); const enemyPokemon = game.field.getEnemyPokemon(); expect(enemyPokemon.turnData.hitsLeft).toBe(0); @@ -433,7 +434,8 @@ describe("Abilities - Wimp Out", () => { game.move.select(MoveId.ENDURE); game.doSelectPartyPokemon(1); - await game.phaseInterceptor.to("TurnEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); const enemyPokemon = game.field.getEnemyPokemon(); expect(enemyPokemon.turnData.hitsLeft).toBe(0); @@ -448,7 +450,8 @@ describe("Abilities - Wimp Out", () => { game.move.select(MoveId.ENDURE); game.doSelectPartyPokemon(1); - await game.phaseInterceptor.to("TurnEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); const enemyPokemon = game.field.getEnemyPokemon(); expect(enemyPokemon.turnData.hitsLeft).toBe(0); diff --git a/test/items/multi-lens.test.ts b/test/items/multi-lens.test.ts index aff9cc148f6..ee2f7629ceb 100644 --- a/test/items/multi-lens.test.ts +++ b/test/items/multi-lens.test.ts @@ -55,7 +55,7 @@ describe("Items - Multi Lens", () => { game.move.select(MoveId.TACKLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); const damageResults = spy.mock.results.map(result => result.value?.damage); expect(damageResults).toHaveLength(1 + stackCount); @@ -74,7 +74,7 @@ describe("Items - Multi Lens", () => { game.move.select(MoveId.TACKLE); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(playerPokemon.turnData.hitCount).toBe(3); }); @@ -112,7 +112,7 @@ describe("Items - Multi Lens", () => { await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); - await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(magikarp.turnData.hitCount).toBe(2); }); @@ -129,7 +129,7 @@ describe("Items - Multi Lens", () => { game.move.select(MoveId.SEISMIC_TOSS); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); const damageResults = spy.mock.results.map(result => result.value?.damage); expect(damageResults).toHaveLength(2); diff --git a/test/moves/beak-blast.test.ts b/test/moves/beak-blast.test.ts index 4d28e7fd0ab..374c983ed9f 100644 --- a/test/moves/beak-blast.test.ts +++ b/test/moves/beak-blast.test.ts @@ -86,19 +86,6 @@ describe("Moves - Beak Blast", () => { expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.BURN); }); - it("should only hit twice with Multi-Lens", async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); - - await game.classicMode.startBattle([SpeciesId.BLASTOISE]); - - const leadPokemon = game.field.getPlayerPokemon(); - - game.move.select(MoveId.BEAK_BLAST); - - await game.phaseInterceptor.to(BerryPhase, false); - expect(leadPokemon.turnData.hitCount).toBe(2); - }); - it("should be blocked by Protect", async () => { game.override.enemyMoveset([MoveId.PROTECT]); diff --git a/test/moves/electro-shot.test.ts b/test/moves/electro-shot.test.ts index 4b1303fc930..edaaf3e365c 100644 --- a/test/moves/electro-shot.test.ts +++ b/test/moves/electro-shot.test.ts @@ -95,7 +95,7 @@ describe("Moves - Electro Shot", () => { game.move.select(MoveId.ELECTRO_SHOT); - await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(playerPokemon.turnData.hitCount).toBe(1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); }); diff --git a/test/moves/protect.test.ts b/test/moves/protect.test.ts index 7fe29cd7568..1c9fdb87558 100644 --- a/test/moves/protect.test.ts +++ b/test/moves/protect.test.ts @@ -179,7 +179,8 @@ describe("Moves - Protect", () => { const enemyPokemon = game.field.getEnemyPokemon(); game.move.select(MoveId.PROTECT); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("MoveEndPhase"); + await game.phaseInterceptor.to("MoveEndPhase", false); expect(charizard.hp).toBe(charizard.getMaxHp()); expect(enemyPokemon.turnData.hitCount).toBe(1); From 8f4243853d962e535be23efa7369eca0b63e39e9 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 18 Dec 2025 22:11:11 -0500 Subject: [PATCH 077/101] [Refactor] Make `phaseManager#unshiftPhase` and `#pushPhase` variadic https://github.com/pagefaultgames/pokerogue/pull/6776 --- src/phase-manager.ts | 32 +++++++++++++------ .../a-trainers-test-encounter.test.ts | 6 ++-- .../dancing-lessons-encounter.test.ts | 12 +++---- .../uncommon-breed-encounter.test.ts | 6 ++-- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 927c343c9f2..5f1637d1378 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -113,6 +113,7 @@ import { UnlockPhase } from "#phases/unlock-phase"; import { VictoryPhase } from "#phases/victory-phase"; import { WeatherEffectPhase } from "#phases/weather-effect-phase"; import type { PhaseConditionFunc, PhaseMap, PhaseString } from "#types/phase-types"; +import type { NonEmptyTuple } from "type-fest"; /** * Object that holds all of the phase constructors. @@ -275,21 +276,34 @@ export class PhaseManager { } /** - * Adds a phase to the end of the queue - * @param phase - The {@linkcode Phase} to add + * Add one or more Phases to the end of the queue. + * They will run once all phases already in the queue have ended. + * @param phases - One or more {@linkcode Phase}s to add */ - public pushPhase(phase: Phase): void { - this.phaseQueue.pushPhase(this.checkDynamic(phase)); + public pushPhase(...phases: NonEmptyTuple): void { + for (const phase of phases) { + this.phaseQueue.pushPhase(this.checkDynamic(phase)); + } } /** - * Queue a phase to be run immediately after the current phase finishes. \ + * Queue one or more phases to be run immediately after the current phase finishes. \ * Unshifted phases are run in FIFO order if multiple are queued during a single phase's execution. - * @param phase - The {@linkcode Phase} to add + * @param phases - One or more {@linkcode Phase}s to add + * @privateRemarks + * Any newly-unshifted `MovePhase`s will be queued after the next `MoveEndPhase`. */ - public unshiftPhase(phase: Phase): void { - const toAdd = this.checkDynamic(phase); - phase.is("MovePhase") ? this.phaseQueue.addAfter(toAdd, "MoveEndPhase") : this.phaseQueue.addPhase(toAdd); + // NB: I'd like to restrict this to only allow passing 1 `MovePhase` at a time, but this causes TS to + // flip the hell out with `Parameters`... + public unshiftPhase(...phases: NonEmptyTuple): void { + for (const phase of phases) { + const toAdd = this.checkDynamic(phase); + if (phase.is("MovePhase")) { + this.phaseQueue.addAfter(toAdd, "MoveEndPhase"); + } else { + this.phaseQueue.addPhase(toAdd); + } + } } /** diff --git a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index 8dc4348adae..55ffa65d8f0 100644 --- a/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -9,7 +9,7 @@ import { ATrainersTestEncounter } from "#mystery-encounters/a-trainers-test-enco import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#mystery-encounters/mystery-encounters"; -import { PartyHealPhase } from "#phases/party-heal-phase"; +import type { PartyHealPhase } from "#phases/party-heal-phase"; import { SelectModifierPhase } from "#phases/select-modifier-phase"; import { runMysteryEncounterToEnd, @@ -165,8 +165,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.A_TRAINERS_TEST, defaultParty); await runMysteryEncounterToEnd(game, 2); - const partyHealPhases = phaseSpy.mock.calls.filter(p => p[0] instanceof PartyHealPhase).map(p => p[0]); - expect(partyHealPhases.length).toBe(1); + const partyHealPhases = phaseSpy.mock.calls.flat().filter((p): p is PartyHealPhase => p.is("PartyHealPhase")); + expect(partyHealPhases).toHaveLength(1); }); it("Should reward the player with a Rare egg", async () => { diff --git a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 81a2fc7463c..714c7706e41 100644 --- a/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -9,8 +9,8 @@ import { UiMode } from "#enums/ui-mode"; import { DancingLessonsEncounter } from "#mystery-encounters/dancing-lessons-encounter"; import * as EncounterPhaseUtils from "#mystery-encounters/encounter-phase-utils"; import * as MysteryEncounters from "#mystery-encounters/mystery-encounters"; -import { LearnMovePhase } from "#phases/learn-move-phase"; -import { MovePhase } from "#phases/move-phase"; +import type { LearnMovePhase } from "#phases/learn-move-phase"; +import type { MovePhase } from "#phases/move-phase"; import { MysteryEncounterPhase } from "#phases/mystery-encounter-phases"; import { runMysteryEncounterToEnd, @@ -110,9 +110,9 @@ describe("Dancing Lessons - Mystery Encounter", () => { const moveset = enemyField[0].moveset.map(m => m.moveId); expect(moveset.some(m => m === MoveId.REVELATION_DANCE)).toBeTruthy(); - const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); - expect(movePhases.length).toBe(1); - expect(movePhases.filter(p => (p as MovePhase).move.moveId === MoveId.REVELATION_DANCE).length).toBe(1); // Revelation Dance used before battle + const movePhases = phaseSpy.mock.calls.flat().filter((p): p is MovePhase => p.is("MovePhase")); + expect(movePhases).toHaveLength(1); + expect(movePhases[0].move.moveId).toBe(MoveId.REVELATION_DANCE); // Revelation Dance used before battle }); it("should have a Baton in the rewards after battle", async () => { @@ -159,7 +159,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { game.field.getPlayerPokemon().moveset = []; await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 }); - const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof LearnMovePhase).map(p => p[0]); + const movePhases = phaseSpy.mock.calls.filter(p => p.some(i => i.is("LearnMovePhase"))).map(p => p[0]); expect(movePhases.length).toBe(1); expect(movePhases.filter(p => (p as LearnMovePhase)["moveId"] === MoveId.REVELATION_DANCE).length).toBe(1); // Revelation Dance taught to pokemon }); diff --git a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 021aa336205..cbce233f90b 100644 --- a/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -123,8 +123,10 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); - const statStagePhases = unshiftPhaseSpy.mock.calls.find(p => p[0] instanceof StatStageChangePhase)?.[0] as any; - expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); + const statStagePhase = unshiftPhaseSpy.mock.calls + .flat() + .find((p): p is StatStageChangePhase => p.is("StatStageChangePhase")); + expect(statStagePhase?.["stats"]).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); // Should have used its egg move pre-battle const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); From e8b1d0fd7120a7f6412910de6069df8e00dc6823 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:41:32 -0600 Subject: [PATCH 078/101] [Bug] Fix evil team admin randomization (#6830) --- src/utils/random.ts | 21 ++++++---------- test/utils/random.test.ts | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 test/utils/random.test.ts diff --git a/src/utils/random.ts b/src/utils/random.ts index 530ee394c70..3a3d3ad0280 100644 --- a/src/utils/random.ts +++ b/src/utils/random.ts @@ -12,7 +12,7 @@ import { globalScene } from "#app/global-scene"; import type { Mutable } from "#types/type-helpers"; -import { randSeedItem } from "#utils/common"; +import { randSeedItem, randSeedShuffle } from "#utils/common"; /** * Select a random element using an offset such that the chosen element is @@ -21,9 +21,10 @@ import { randSeedItem } from "#utils/common"; * @remarks * If the seed offset is greater than the number of choices, this will just choose a random element * - * @param arr - The array of items to choose from + * @param choices - The array of items to choose from + * @param seedOffset - The offset into the array * @param scene - (default {@linkcode globalScene}); The scene to use for random seeding - * @returns A random item from the array that is guaranteed to be different from the + * @returns A random item from the array that is guaranteed to be different from the previous result * @typeParam T - The type of items in the array * * @example @@ -38,8 +39,7 @@ import { randSeedItem } from "#utils/common"; * ``` */ export function randSeedUniqueItem(choices: readonly T[], seedOffset: number, scene = globalScene): T { - if (seedOffset === 0 || choices.length <= seedOffset) { - // cast to mutable is safe because randSeedItem does not actually modify the array + if (choices.length <= seedOffset) { return randSeedItem(choices as Mutable); } @@ -47,14 +47,7 @@ export function randSeedUniqueItem(choices: readonly T[], seedOffset: number, let choice: T; scene.executeWithSeedOffset(() => { - const curChoices = choices.slice(); - for (let i = 0; i < seedOffset; i++) { - const previousChoice = randSeedItem(curChoices); - curChoices.splice(curChoices.indexOf(previousChoice), 1); - } - choice = randSeedItem(curChoices); - }, seedOffset); - - // Bang is safe since there are at least `seedOffset` choices, so the method above is guaranteed to set `choice` + choice = randSeedShuffle(choices.slice())[seedOffset]; + }, 0); return choice!; } diff --git a/test/utils/random.test.ts b/test/utils/random.test.ts new file mode 100644 index 00000000000..62ddeadf4f8 --- /dev/null +++ b/test/utils/random.test.ts @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Pagefault Games + * SPDX-FileContributor: SirzBenjie + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { GameManager } from "#test/test-utils/game-manager"; +import { randSeedUniqueItem } from "#utils/random"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Utils - Random", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + describe("randSeedUniqueItem", () => { + // TODO: Remove `initialization of game` once `randSeedUniqueItem` stops using `executeWithSeedOffset` + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + }); + + it("should prevent duplicates when provided with different offsets", async () => { + const choices = ["a", "b", "c", "d"]; + const choice1 = randSeedUniqueItem(choices, 0); + const choice2 = randSeedUniqueItem(choices, 1); + const choice3 = randSeedUniqueItem(choices, 2); + expect(choice2).not.toEqual(choice1); + expect(choice2).not.toEqual(choice3); + expect(choice1).not.toEqual(choice3); + }); + + it("should gracefully handle an offset larger than the choices", () => { + const choices = ["a", "b", "c"]; + // 1) the function must not throw + // 2) The output must be one of the choices + const choice = randSeedUniqueItem(choices, 5); + expect(choices).toContain(choice); + }); + }); +}); From e888cbd6fba58f90319547563dccd711741bc6d6 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:04:45 -0800 Subject: [PATCH 079/101] [Misc] Add missing word in TSDoc --- src/utils/random.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/random.ts b/src/utils/random.ts index 3a3d3ad0280..de2f3609a8c 100644 --- a/src/utils/random.ts +++ b/src/utils/random.ts @@ -22,7 +22,7 @@ import { randSeedItem, randSeedShuffle } from "#utils/common"; * If the seed offset is greater than the number of choices, this will just choose a random element * * @param choices - The array of items to choose from - * @param seedOffset - The offset into the array + * @param seedOffset - The offset into the shuffled array * @param scene - (default {@linkcode globalScene}); The scene to use for random seeding * @returns A random item from the array that is guaranteed to be different from the previous result * @typeParam T - The type of items in the array From a1a1796f64da23bc481c8a775a41a5a2ea6f510b Mon Sep 17 00:00:00 2001 From: Austin Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:28:06 -0500 Subject: [PATCH 080/101] [Event] Add data for Winter Holiday 2025 event (#6860) * Add data for Winter 2025 event * Update timed-event-manager.ts --------- Co-authored-by: damocleas --- .../encounters/delibirdy-encounter.ts | 1 + src/timed-event-manager.ts | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index aede4c1568a..7f99bfe106d 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -80,6 +80,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with MysteryEncounterType.DELIBIRDY, ) .withEncounterTier(MysteryEncounterTier.GREAT) + .withMaxAllowedEncounters(4) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least .withPrimaryPokemonRequirement( diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index efc05cd78d3..d326988050a 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -81,6 +81,59 @@ interface TimedEvent extends EventBanner { } const timedEvents: readonly TimedEvent[] = [ + { + name: "Winter 25", + eventType: EventType.SHINY, + startDate: new Date(Date.UTC(2025, 11, 19)), + endDate: new Date(Date.UTC(2026, 0, 5)), + bannerKey: "winter2025", + scale: 0.19, + availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans", "zh-Hant", "da", "ru"], + shinyEncounterMultiplier: 2, + shinyCatchMultiplier: 3, + upgradeUnlockedVouchers: true, + eventEncounters: [ + { species: SpeciesId.CYNDAQUIL }, + { species: SpeciesId.SENTRET }, + { species: SpeciesId.DELIBIRD }, + { species: SpeciesId.STANTLER }, + { species: SpeciesId.SMOOCHUM }, + { species: SpeciesId.BALTOY }, + { species: SpeciesId.MAKUHITA }, + { species: SpeciesId.PIPLUP }, + { species: SpeciesId.CHINGLING }, + { species: SpeciesId.LITWICK }, + { species: SpeciesId.CHESPIN }, + { species: SpeciesId.AMAURA }, + { species: SpeciesId.COMFEY }, + { species: SpeciesId.DHELMISE }, + { species: SpeciesId.ROLYCOLY }, + { species: SpeciesId.SMOLIV }, + { species: SpeciesId.GIMMIGHOUL, blockEvolution: true }, + { species: SpeciesId.IRON_BUNDLE }, + { species: SpeciesId.ALOLA_VULPIX }, + { species: SpeciesId.GALAR_DARUMAKA }, + ], + classicWaveRewards: [ + { wave: 8, type: "SHINY_CHARM" }, + { wave: 8, type: "ABILITY_CHARM" }, + { wave: 8, type: "CATCHING_CHARM" }, + { wave: 25, type: "SHINY_CHARM" }, + ], + delibirdyBuff: ["CATCHING_CHARM", "SHINY_CHARM", "ABILITY_CHARM", "EXP_CHARM", "SUPER_EXP_CHARM", "HEALING_CHARM"], + mysteryEncounterTierChanges: [ + { + mysteryEncounter: MysteryEncounterType.DELIBIRDY, + tier: MysteryEncounterTier.COMMON, + }, + { mysteryEncounter: MysteryEncounterType.PART_TIMER, disable: true }, + { + mysteryEncounter: MysteryEncounterType.DEPARTMENT_STORE_SALE, + disable: true, + }, + ], + dailyRunStartingItems: ["ABILITY_CHARM", "SHINY_CHARM"], + }, { name: "Winter Holiday Update", eventType: EventType.SHINY, From 6bcb8bc38a7b903738416c9911b6916848f0dde0 Mon Sep 17 00:00:00 2001 From: damocleas Date: Fri, 19 Dec 2025 12:26:19 -0500 Subject: [PATCH 081/101] Update assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a724d2fec98..9805d99d659 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a724d2fec98c412df28a635c1d90fe2f790cb5dc +Subproject commit 9805d99d659f047e89d61f4e917b15a79e0e82ff From e501a29942a25262366ad3f6f37c7e8edcb83eb4 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:24:18 -0800 Subject: [PATCH 082/101] [Misc] Update submodules --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 9805d99d659..1982bfe6e5a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9805d99d659f047e89d61f4e917b15a79e0e82ff +Subproject commit 1982bfe6e5a6d98f10eb748affb519c7bda08d8e From 0cc3d2c566e5e6cc632f8e51024338d2ee414c2b Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:26:21 +0100 Subject: [PATCH 083/101] [Bug] Charging moves no longer fail on last PP (#6867) --- src/phases/move-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index fa4bf293ba4..d8abc884047 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -906,7 +906,7 @@ export class MovePhase extends PokemonPhase { this.pokemon.getBattlerIndex(), this.targets[0], this.move, - this.useMode, + this.useMode === MoveUseMode.NORMAL ? MoveUseMode.IGNORE_PP : this.useMode, ); } From 8efc05904e9f434b13ba316ff0a3f7623b0e1a12 Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:55:57 -0600 Subject: [PATCH 084/101] [Audio] Add winter title BGM loop point & play during winter season (#6864) Add winter title BGM loop point & play during winter season The winter title BGM will play during the entirety of December and the first half of January, regardless of if an event is happening or not. --- src/battle-scene.ts | 2 ++ src/phases/title-phase.ts | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c537728442c..8a999665919 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2376,6 +2376,8 @@ export class BattleScene extends SceneBase { switch (bgmName) { case "title": //Firel PokéRogue Title return 46.5; + case "winter_title": //Andr06 Winter Title + return 20.57; case "battle_kanto_champion": //B2W2 Kanto Champion Battle return 13.95; case "battle_johto_champion": //B2W2 Johto Champion Battle diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index ceac7ed1958..f942925c935 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -37,7 +37,12 @@ export class TitlePhase extends Phase { globalScene.ui.clearText(); globalScene.ui.fadeIn(250); - globalScene.playBgm("title", true); + const now = new Date(); + if (now.getMonth() === 11 || (now.getMonth() === 0 && now.getDate() <= 15)) { + globalScene.playBgm("winter_title", true); + } else { + globalScene.playBgm("title", true); + } globalScene.gameData .getSession(loggedInUser?.lastSessionSlot ?? -1) From 18dcde63d77831dcdb7095c204c0cd39203428ac Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:58:28 -0800 Subject: [PATCH 085/101] [Test] Disable flaky Delibirdy ME test temporarily --- test/mystery-encounter/encounters/delibirdy-encounter.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index fe17f091d0e..0c35cd837ae 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -357,7 +357,8 @@ describe("Delibird-y - Mystery Encounter", () => { }); }); - it("Should decrease held item stacks and give the player a Healing Charm", async () => { + // TODO: fix mocking of events (and remove `BattleScene#eventManager`) + it.skip("Should decrease held item stacks and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 2 Soul Dew on party lead From 407ad49a72898bf81c2fc4d629ead5446cd46f19 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:18:47 -0800 Subject: [PATCH 086/101] [i18n] Update locales submodule --- locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales b/locales index 8f30c25c114..99f2e966b39 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit 8f30c25c114a8cfc8648000f5c19e07661a44474 +Subproject commit 99f2e966b3973dc203266b8c047a574b9656e815 From 36b138333d1bbbe75be180aa80626123c4fbeb48 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:50:01 -0800 Subject: [PATCH 087/101] [Test] Disable other flaky Delibirdy ME test --- test/mystery-encounter/encounters/delibirdy-encounter.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 0c35cd837ae..e844949a5bd 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -379,7 +379,8 @@ describe("Delibird-y - Mystery Encounter", () => { expect(healingCharmAfter?.stackCount).toBe(1); }); - it("Should remove held item and give the player a Healing Charm", async () => { + // TODO: fix mocking of events (and remove `BattleScene#eventManager`) + it.todo("Should remove held item and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Soul Dew on party lead From a519e5392574e54185a387015526a9fcc297addb Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Sat, 20 Dec 2025 07:14:29 -0600 Subject: [PATCH 088/101] [P3 Bug] Fix naming of banner (#6870) --- src/timed-event-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index d326988050a..632e2b336d8 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -86,7 +86,7 @@ const timedEvents: readonly TimedEvent[] = [ eventType: EventType.SHINY, startDate: new Date(Date.UTC(2025, 11, 19)), endDate: new Date(Date.UTC(2026, 0, 5)), - bannerKey: "winter2025", + bannerKey: "winter_holidays2025-event", scale: 0.19, availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-419", "pt-BR", "zh-Hans", "zh-Hant", "da", "ru"], shinyEncounterMultiplier: 2, From 72329f003cce00ea28489528d2081ff499587984 Mon Sep 17 00:00:00 2001 From: Lugiad Date: Sat, 20 Dec 2025 15:07:07 +0100 Subject: [PATCH 089/101] [UI/UX] Indonesian text position adjustments (#6875) * Hindi Status icon placeholder typo fix * Indonesian Ui adjustment --- src/ui/handlers/starter-select-ui-handler.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ui/handlers/starter-select-ui-handler.ts b/src/ui/handlers/starter-select-ui-handler.ts index e647dd7958f..139d7844229 100644 --- a/src/ui/handlers/starter-select-ui-handler.ts +++ b/src/ui/handlers/starter-select-ui-handler.ts @@ -169,8 +169,10 @@ const languageSettings: { [key: string]: LanguageSetting } = { starterInfoXPos: 26, }, id: { - starterInfoTextSize: "56px", - instructionTextSize: "38px", + starterInfoTextSize: "48px", + instructionTextSize: "42px", + starterInfoYOffset: 0.5, + starterInfoXPos: 37, }, hi: { starterInfoTextSize: "56px", From 75aa179503b9df258143f5fd5cd29e8a5c76785e Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 20 Dec 2025 14:39:07 -0500 Subject: [PATCH 090/101] update submodules --- assets | 2 +- locales | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index 1982bfe6e5a..3587f12fac1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 1982bfe6e5a6d98f10eb748affb519c7bda08d8e +Subproject commit 3587f12fac13284d0846e6ea10e1203552dff1ce diff --git a/locales b/locales index 99f2e966b39..6b5e2130256 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit 99f2e966b3973dc203266b8c047a574b9656e815 +Subproject commit 6b5e2130256dd521908f15a485d045fb36baca41 From b409dda6958380a0f34bb52893a429ff0617294a Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 20 Dec 2025 14:51:03 -0500 Subject: [PATCH 091/101] [Sprite] Adjust Bug Type Superfan and Shady Vitamin Dealer visual presentation With the changes to their sprites, both have had their visual presentation adjusted. --- .../encounters/bug-type-superfan-encounter.ts | 67 ++++++------------- .../shady-vitamin-dealer-encounter.ts | 11 +-- 2 files changed, 26 insertions(+), 52 deletions(-) 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 a05da4893c6..cc7b839588c 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -34,7 +34,6 @@ import { setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#mystery-encounters/encounter-phase-utils"; -import { getSpriteKeysFromSpecies } from "#mystery-encounters/encounter-pokemon-utils"; import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option"; @@ -177,7 +176,26 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(3, 6) .withMaxAllowedEncounters(1) - .withIntroSpriteConfigs([]) // These are set in onInit() + .withIntroSpriteConfigs([ + { + species: SpeciesId.VESPIQUEN, + spriteKey: "", + fileRoot: "", + hasShadow: true, + repeat: true, + x: 35, + y: -2, + yShadow: -2, + }, + { + spriteKey: "bug_type_superfan", + fileRoot: "trainer", + hasShadow: true, + x: -20, + y: 5, + yShadow: 5, + }, + ]) .withAutoHideIntroVisuals(false) .withIntroDialogue([ { @@ -194,56 +212,11 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde // Bug type superfan trainer config const config = getTrainerConfigForWave(globalScene.currentBattle.waveIndex); - const spriteKey = config.getSpriteKey(); encounter.enemyPartyConfigs.push({ trainerConfig: config, female: true, }); - let beedrillKeys: { spriteKey: string; fileRoot: string }; - let butterfreeKeys: { spriteKey: string; fileRoot: string }; - if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) { - beedrillKeys = getSpriteKeysFromSpecies(SpeciesId.BEEDRILL, false); - butterfreeKeys = getSpriteKeysFromSpecies(SpeciesId.BUTTERFREE, false); - } else { - // Mega Beedrill/Gmax Butterfree - beedrillKeys = getSpriteKeysFromSpecies(SpeciesId.BEEDRILL, false, 1); - butterfreeKeys = getSpriteKeysFromSpecies(SpeciesId.BUTTERFREE, false, 1); - } - - encounter.spriteConfigs = [ - { - spriteKey: beedrillKeys.spriteKey, - fileRoot: beedrillKeys.fileRoot, - hasShadow: true, - repeat: true, - isPokemon: true, - x: -30, - tint: 0.15, - y: -4, - yShadow: -4, - }, - { - spriteKey: butterfreeKeys.spriteKey, - fileRoot: butterfreeKeys.fileRoot, - hasShadow: true, - repeat: true, - isPokemon: true, - x: 30, - tint: 0.15, - y: -4, - yShadow: -4, - }, - { - spriteKey, - fileRoot: "trainer", - hasShadow: true, - x: 4, - y: 7, - yShadow: 7, - }, - ]; - const requiredItems = [ generateModifierType(modifierTypes.QUICK_CLAW), generateModifierType(modifierTypes.GRIP_CLAW), 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 fa65164ecfd..f2eaaa6df35 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -48,13 +48,14 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui .withPrimaryPokemonHealthRatioRequirement([0.51, 1]) // At least 1 Pokemon must have above half HP .withIntroSpriteConfigs([ { - spriteKey: SpeciesId.KROOKODILE.toString(), + spriteKey: SpeciesId.KROKOROK.toString(), fileRoot: "pokemon", hasShadow: true, - repeat: true, - x: 12, - y: -5, - yShadow: -5, + repeat: false, + scale: 1.1, + x: 24, + y: 0, + yShadow: 0, }, { spriteKey: "shady_vitamin_dealer", From 8ae898ec30cd7d78bfa751c1c5fabb01adba81e0 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:26:51 -0500 Subject: [PATCH 092/101] [Move] Update documentation for `AddSubstituteAttr`; fix Shed Tail incorrect error message (#6873) * [Move] Update documentation for attribute; fix Shed Tail incorrect error message * Add another test --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> --- docs/comments.md | 40 ++++++++++++++------------ src/data/moves/move.ts | 53 ++++++++++++++++++----------------- test/moves/shed-tail.test.ts | 14 +++++++++ test/moves/substitute.test.ts | 14 +++++++++ 4 files changed, 77 insertions(+), 44 deletions(-) diff --git a/docs/comments.md b/docs/comments.md index fa94d0cf4bd..438b6a1c895 100644 --- a/docs/comments.md +++ b/docs/comments.md @@ -38,28 +38,37 @@ For an example of how TSDoc comments work, here are some TSDoc comments taken fr * Attribute to put in a {@link https://bulbapedia.bulbagarden.net/wiki/Substitute_(doll) | Substitute Doll} for the user. */ export class AddSubstituteAttr extends MoveEffectAttr { - /** The ratio of the user's max HP that is required to apply this effect */ - private hpCost: number; - /** Whether the damage taken should be rounded up (Shed Tail rounds up) */ - private roundUp: boolean; + /** The percentage of the user's maximum HP that is required to apply this effect. */ + private readonly hpCost: number; + /** Whether the damage taken should be rounded up (Shed Tail rounds up). */ + private readonly roundUp: boolean; constructor(hpCost: number, roundUp: boolean) { // code removed } /** - * Removes 1/4 of the user's maximum HP (rounded down) to create a substitute for the user - * @param user - The {@linkcode Pokemon} that used the move. - * @param target - n/a - * @param move - The {@linkcode Move} with this attribute. - * @param args - n/a - * @returns `true` if the attribute successfully applies, `false` otherwise + * Helper function to compute the amount of HP required to create a substitute. + * @param user - The {@linkcode Pokemon} using the move + * @returns The amount of HP that required to create a substitute. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + private getHpCost(user: Pokemon): number { // code removed } - getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + /** + * Remove a fraction of the user's maximum HP to create a 25% HP substitute doll. + * @param user - The {@linkcode Pokemon} using the move + * @param target - n/a + * @param move - The {@linkcode Move} being used + * @param args - n/a + * @returns Whether the attribute successfully applied. + */ + public override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + // code removed + } + + public override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { // code removed } @@ -67,12 +76,7 @@ export class AddSubstituteAttr extends MoveEffectAttr { // code removed } - /** - * Get the substitute-specific failure message if one should be displayed. - * @param user - The pokemon using the move. - * @returns The substitute-specific failure message if the conditions apply, otherwise `undefined` - */ - getFailedText(user: Pokemon, _target: Pokemon, _move: Move): string | undefined { + public override getFailedText(user: Pokemon): string | undefined { // code removed } } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 3376dc26e0a..69cce126558 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2227,11 +2227,13 @@ export class HalfSacrificialAttr extends MoveEffectAttr { /** * Attribute to put in a {@link https://bulbapedia.bulbagarden.net/wiki/Substitute_(doll) | Substitute Doll} for the user. + * + * Used for {@linkcode MoveId.SUBSTITUTE} and {@linkcode MoveId.SHED_TAIL}. */ export class AddSubstituteAttr extends MoveEffectAttr { - /** The ratio of the user's max HP that is required to apply this effect */ + /** The percentage of the user's maximum HP that is required to apply this effect. */ private readonly hpCost: number; - /** Whether the damage taken should be rounded up (Shed Tail rounds up) */ + /** Whether the damage taken should be rounded up (Shed Tail rounds up). */ private readonly roundUp: boolean; constructor(hpCost: number, roundUp: boolean) { @@ -2242,50 +2244,49 @@ export class AddSubstituteAttr extends MoveEffectAttr { } /** - * Removes 1/4 of the user's maximum HP (rounded down) to create a substitute for the user - * @param user - The {@linkcode Pokemon} that used the move. - * @param target - n/a - * @param move - The {@linkcode Move} with this attribute. - * @param args - n/a - * @returns `true` if the attribute successfully applies, `false` otherwise + * Helper function to compute the amount of HP required to create a substitute. + * @param user - The {@linkcode Pokemon} using the move + * @returns The amount of HP that is required to create a substitute. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + private getHpCost(user: Pokemon): number { + return (this.roundUp ? Math.ceil : toDmgValue)(user.getMaxHp() * this.hpCost); + } + + /** + * Remove a fraction of the user's maximum HP to create a 25% HP substitute doll. + * @param user - The {@linkcode Pokemon} using the move + * @param target - n/a + * @param move - The {@linkcode Move} being used + * @param args - n/a + * @returns Whether the attribute successfully applied + */ + public override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) { return false; } - const damageTaken = this.roundUp - ? Math.ceil(user.getMaxHp() * this.hpCost) - : Math.floor(user.getMaxHp() * this.hpCost); - user.damageAndUpdate(damageTaken, { result: HitResult.INDIRECT, ignoreSegments: true, ignoreFaintPhase: true }); + const dmgTaken = this.getHpCost(user); + user.damageAndUpdate(dmgTaken, { result: HitResult.INDIRECT, ignoreSegments: true, ignoreFaintPhase: true }); user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id); return true; } - getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { + public override getUserBenefitScore(user: Pokemon, _target: Pokemon, _move: Move): number { if (user.isBoss()) { return -10; } return 5; } - getCondition(): MoveConditionFunc { - return (user, _target, _move) => - !user.getTag(SubstituteTag) - && user.hp > (this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost)) - && user.getMaxHp() > 1; + public override getCondition(): MoveConditionFunc { + return user => !user.getTag(SubstituteTag) && user.hp > this.getHpCost(user); } - /** - * Get the substitute-specific failure message if one should be displayed. - * @param user - The pokemon using the move. - * @returns The substitute-specific failure message if the conditions apply, otherwise `undefined` - */ - getFailedText(user: Pokemon, _target: Pokemon, _move: Move): string | undefined { + public override getFailedText(user: Pokemon): string | undefined { if (user.getTag(SubstituteTag)) { return i18next.t("moveTriggers:substituteOnOverlap", { pokemonName: getPokemonNameWithAffix(user) }); } - if (user.hp <= Math.floor(user.getMaxHp() / 4) || user.getMaxHp() === 1) { + if (user.hp <= this.getHpCost(user)) { return i18next.t("moveTriggers:substituteNotEnoughHp"); } } diff --git a/test/moves/shed-tail.test.ts b/test/moves/shed-tail.test.ts index b53af269875..a23025992d3 100644 --- a/test/moves/shed-tail.test.ts +++ b/test/moves/shed-tail.test.ts @@ -4,6 +4,7 @@ import { MoveId } from "#enums/move-id"; import { MoveResult } from "#enums/move-result"; import { SpeciesId } from "#enums/species-id"; import { GameManager } from "#test/test-utils/game-manager"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -65,4 +66,17 @@ describe("Moves - Shed Tail", () => { expect(magikarp.isOnField()).toBeTruthy(); expect(magikarp.getLastXMoves()[0].result).toBe(MoveResult.FAIL); }); + + it("should show the correct failure message between 26-50% HP", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.ABRA]); + + const feebas = game.field.getPlayerPokemon(); + feebas.hp *= 0.4; + + game.move.use(MoveId.SHED_TAIL); + await game.toEndOfTurn(); + + expect(feebas).toHaveUsedMove({ move: MoveId.SHED_TAIL, result: MoveResult.FAIL }); + expect(game).toHaveShownMessage(i18next.t("moveTriggers:substituteNotEnoughHp")); + }); }); diff --git a/test/moves/substitute.test.ts b/test/moves/substitute.test.ts index 89018a8d592..708a9bda9ec 100644 --- a/test/moves/substitute.test.ts +++ b/test/moves/substitute.test.ts @@ -509,4 +509,18 @@ describe("Moves - Substitute", () => { expect(playerPokemon.getTag(BattlerTagType.SEEDED)).toBeUndefined(); }); + + it("should fail if the user has 1 max HP", async () => { + await game.classicMode.startBattle([SpeciesId.SHEDINJA]); + + const player = game.field.getPlayerPokemon(); + + game.move.use(MoveId.SUBSTITUTE); + await game.toEndOfTurn(); + + expect(player).toHaveUsedMove({ move: MoveId.SUBSTITUTE, result: MoveResult.FAIL }); + expect(player).not.toHaveBattlerTag(BattlerTagType.SUBSTITUTE); + expect(player).toHaveFullHp(); + expect(player).toHaveHp(1); + }); }); From 2256b57370cb3af84074701140f8ed9f3baecbcf Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:36:42 +0100 Subject: [PATCH 093/101] [UI] [Bug] Correctly update level after Weird Dream ME (#6876) --- src/ui/battle-info/player-battle-info.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts index ec6332c54f3..bc9806bef92 100644 --- a/src/ui/battle-info/player-battle-info.ts +++ b/src/ui/battle-info/player-battle-info.ts @@ -207,6 +207,7 @@ export class PlayerBattleInfo extends BattleInfo { this.lastLevelCapped = isLevelCapped; if (this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level) { + this.lastLevel = Math.min(this.lastLevel, pokemon.level); const durationMultiplier = Math.max( Phaser.Tweens.Builders.GetEaseFunction("Cubic.easeIn")(1 - Math.min(pokemon.level - this.lastLevel, 10) / 10), 0.1, From fc42530ca3d2b5742d51e9ba62009223322c70e6 Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Sat, 20 Dec 2025 14:39:54 -0600 Subject: [PATCH 094/101] [Sprite] Add snow to main menu during winter season (#6877) --- src/loading-scene.ts | 1 + src/ui/handlers/title-ui-handler.ts | 32 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/loading-scene.ts b/src/loading-scene.ts index d87faa322a4..02afc302bce 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -29,6 +29,7 @@ export class LoadingScene extends SceneBase { this.loadImage("loading_bg", "arenas"); this.loadImage("logo", ""); this.loadImage("logo_fake", ""); + this.loadImage("snow", ""); // Load menu images this.loadAtlas("bg", "ui"); diff --git a/src/ui/handlers/title-ui-handler.ts b/src/ui/handlers/title-ui-handler.ts index 80c7180f6c5..de3bbecb0ce 100644 --- a/src/ui/handlers/title-ui-handler.ts +++ b/src/ui/handlers/title-ui-handler.ts @@ -203,6 +203,11 @@ export class TitleUiHandler extends OptionSelectUiHandler { this.eventDisplay.show(); } + const now = new Date(); + if (now.getMonth() === 11 || (now.getMonth() === 0 && now.getDate() <= 15)) { + this.getSnow(); + } + this.randomPokemon(); this.genderSplash(); @@ -249,4 +254,31 @@ export class TitleUiHandler extends OptionSelectUiHandler { const aprilFools = timedEventManager.isAprilFoolsActive(); return aprilFools === !!randInt(FAKE_TITLE_LOGO_CHANCE) ? "logo_fake" : "logo"; } + + private snow: Phaser.GameObjects.TileSprite; + + /** Adds a snow effect on the title screen during the winter season. */ + private getSnow(): void { + const width = globalScene.scaledCanvas.width; + const height = globalScene.scaledCanvas.height; + this.snow = globalScene.add.tileSprite(width, height, width, height, "snow"); + this.snow.setOrigin(1, 1); + + globalScene.tweens.add({ + targets: this.snow, + tilePositionX: { from: 0, to: -512 }, + tilePositionY: { from: 0, to: -512 }, + duration: 100000, + repeat: -1, + yoyo: false, + ease: "Linear", + onUpdate: () => { + if (this.snow) { + this.snow.tilePositionX -= 0.5; + this.snow.tilePositionY -= 0.5; + } + }, + }); + this.titleContainer.addAt(this.snow, 0); + } } From b1f482e22cc0a6a93e69afa2e60b7442e9d8830a Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:49:00 +0100 Subject: [PATCH 095/101] [Refactor] Remove `BattleScene#eventManager` (#6868) * remove EventManager from battleScene * remove `MockEventManager` --- src/battle-scene.ts | 12 ++++-------- src/phases/title-phase.ts | 2 +- src/timed-event-manager.ts | 18 ++++++++++++++++++ .../encounters/delibirdy-encounter.test.ts | 6 ++---- test/test-utils/game-wrapper.ts | 4 ++-- .../mocks/mock-timed-event-manager.ts | 19 ------------------- 6 files changed, 27 insertions(+), 34 deletions(-) delete mode 100644 test/test-utils/mocks/mock-timed-event-manager.ts diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 8a999665919..fec0c2c6823 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -22,7 +22,6 @@ import { InvertPostFX } from "#app/pipelines/invert"; import { SpritePipeline } from "#app/pipelines/sprite"; import { SceneBase } from "#app/scene-base"; import { startingWave } from "#app/starting-wave"; -import { TimedEventManager } from "#app/timed-event-manager"; import { UiInputs } from "#app/ui-inputs"; import { pokemonPrevolutions } from "#balance/pokemon-evolutions"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#balance/starters"; @@ -310,7 +309,7 @@ export class BattleScene extends SceneBase { private bgm: AnySound; private bgmResumeTimer: Phaser.Time.TimerEvent | null; - private bgmCache: Set = new Set(); + private readonly bgmCache: Set = new Set(); private playTimeTimer: Phaser.Time.TimerEvent; public rngCounter = 0; @@ -318,9 +317,7 @@ export class BattleScene extends SceneBase { public rngOffset = 0; public inputMethod: string; - private infoToggles: InfoToggle[] = []; - - public eventManager: TimedEventManager; + private readonly infoToggles: InfoToggle[] = []; /** * Allows subscribers to listen for events @@ -336,7 +333,6 @@ export class BattleScene extends SceneBase { constructor() { super("battle"); this.phaseManager = new PhaseManager(); - this.eventManager = new TimedEventManager(); this.updateGameInfo(); initGlobalScene(this); } @@ -3651,7 +3647,7 @@ export class BattleScene extends SceneBase { let availableEncounters: MysteryEncounter[] = []; const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.at(-1)?.type ?? null; // TODO: This being `null` is a bit weird - const disabledEncounters = this.eventManager.getEventMysteryEncountersDisabled(); + const disabledEncounters = timedEventManager.getEventMysteryEncountersDisabled(); const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType)?.filter(enc => !disabledEncounters.includes(enc)) ?? []; // If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available @@ -3663,7 +3659,7 @@ export class BattleScene extends SceneBase { return false; } if ( - this.eventManager.getMysteryEncounterTierForEvent(encounterType, encounterCandidate.encounterTier) !== tier + timedEventManager.getMysteryEncounterTierForEvent(encounterType, encounterCandidate.encounterTier) !== tier ) { return false; } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index f942925c935..c2307c98965 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -214,7 +214,7 @@ export class TitlePhase extends Phase { const generateDaily = (seed: string) => { globalScene.gameMode = getGameMode(GameModes.DAILY); // Daily runs don't support all challenges yet (starter select restrictions aren't considered) - globalScene.eventManager.startEventChallenges(); + timedEventManager.startEventChallenges(); globalScene.setSeed(seed); globalScene.resetSeed(); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 632e2b336d8..4a811f45c41 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -491,7 +491,16 @@ const timedEvents: readonly TimedEvent[] = [ ]; export class TimedEventManager { + /** + * Whether the timed event manager is disabled. + * Used to disable events in testing. + */ + private disabled: boolean; + isActive(event: TimedEvent) { + if (this.disabled) { + return false; + } const now = new Date(); return event.startDate < now && now < event.endDate; } @@ -687,6 +696,15 @@ export class TimedEventManager { getEventDailyStartingItems(): readonly ModifierTypeKeys[] { return this.activeEvent()?.dailyRunStartingItems ?? []; } + + /** + * Disable the timed event manager. Used for testing. + */ + public disable(): void { + this.disabled = true; + } + + // todo: add option to enable to aloow for testing timed events } export class TimedEventDisplay extends Phaser.GameObjects.Container { diff --git a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index e844949a5bd..fe17f091d0e 100644 --- a/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -357,8 +357,7 @@ describe("Delibird-y - Mystery Encounter", () => { }); }); - // TODO: fix mocking of events (and remove `BattleScene#eventManager`) - it.skip("Should decrease held item stacks and give the player a Healing Charm", async () => { + it("Should decrease held item stacks and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 2 Soul Dew on party lead @@ -379,8 +378,7 @@ describe("Delibird-y - Mystery Encounter", () => { expect(healingCharmAfter?.stackCount).toBe(1); }); - // TODO: fix mocking of events (and remove `BattleScene#eventManager`) - it.todo("Should remove held item and give the player a Healing Charm", async () => { + it("Should remove held item and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Soul Dew on party lead diff --git a/test/test-utils/game-wrapper.ts b/test/test-utils/game-wrapper.ts index bfaa645c99b..fcb392caee0 100644 --- a/test/test-utils/game-wrapper.ts +++ b/test/test-utils/game-wrapper.ts @@ -1,6 +1,7 @@ // @ts-nocheck - TODO: remove this import { BattleScene } from "#app/battle-scene"; +import { timedEventManager } from "#app/global-event-manager"; // biome-ignore lint/performance/noNamespaceImport: Necessary in order to mock the var import * as appConstants from "#constants/app-constants"; import { MoveAnim } from "#data/battle-anims"; @@ -10,7 +11,6 @@ import { MockClock } from "#test/test-utils/mocks/mock-clock"; import { MockGameObjectCreator } from "#test/test-utils/mocks/mock-game-object-creator"; import { MockLoader } from "#test/test-utils/mocks/mock-loader"; import { MockTextureManager } from "#test/test-utils/mocks/mock-texture-manager"; -import { MockTimedEventManager } from "#test/test-utils/mocks/mock-timed-event-manager"; import { MockContainer } from "#test/test-utils/mocks/mocks-container/mock-container"; import { PokedexMonContainer } from "#ui/pokedex-mon-container"; import fs from "node:fs"; @@ -196,7 +196,7 @@ export class GameWrapper { this.scene.make = new MockGameObjectCreator(mockTextureManager); this.scene.time = new MockClock(this.scene); this.scene.remove = vi.fn(); // TODO: this should be stubbed differently - this.scene.eventManager = new MockTimedEventManager(); // Disable Timed Events + timedEventManager.disable(); } } diff --git a/test/test-utils/mocks/mock-timed-event-manager.ts b/test/test-utils/mocks/mock-timed-event-manager.ts deleted file mode 100644 index c1f9c34bec2..00000000000 --- a/test/test-utils/mocks/mock-timed-event-manager.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TimedEventManager } from "#app/timed-event-manager"; -import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "#balance/starters"; - -/** Mock TimedEventManager so that ongoing events don't impact tests */ -export class MockTimedEventManager extends TimedEventManager { - // biome-ignore lint/nursery/noUselessUndefined: Changes return type to void instead of undefined - override activeEvent(): undefined { - return; - } - override isEventActive(): boolean { - return false; - } - override getClassicFriendshipMultiplier(): number { - return CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; - } - override getShinyEncounterMultiplier(): number { - return 1; - } -} From 7f2f163a9a3b8f20974372e85f0edd849b4b79b1 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:18:27 -0500 Subject: [PATCH 096/101] [Docs] Fix up docs related to move phase (#6862) * [Docs] Fix up docs related to move phase * Fix `MoveUseMode` invalid TSDoc linkcodes * bulk suggestion Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/moves/move.ts | 15 ++++++++------- src/enums/move-use-mode.ts | 24 +++++++++++++----------- src/field/pokemon.ts | 16 ++++++++++------ 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 69cce126558..cc646539dba 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -871,14 +871,15 @@ export abstract class Move implements Localizable { } /** - * Applies each {@linkcode MoveCondition} function of this move to the params, determines if the move can be used prior to calling each attribute's apply() - * @param user - {@linkcode Pokemon} to apply conditions to - * @param target - {@linkcode Pokemon} to apply conditions to - * @param move - {@linkcode Move} to apply conditions to - * @param sequence - The sequence number where the condition check occurs, or `-1` to check all; defaults to 4. Pass -1 to check all - * @returns boolean: false if any of the apply()'s return false, else true + * Apply this move's conditions prior to move effect application. + * @param user - The {@linkcode Pokemon} using this move + * @param target - The {@linkcode Pokemon} targeted by this move + * @param sequence - (Default `4`) The sequence number where the condition check occurs, or `-1` to check all + * @returns Whether all conditions passed + * @remarks + * Only applies conditions intrinsic to the particular move being used. */ - applyConditions(user: Pokemon, target: Pokemon, sequence: -1 | 2 | 3 | 4 = 4): boolean { + public applyConditions(user: Pokemon, target: Pokemon, sequence: -1 | 2 | 3 | 4 = 4): boolean { let conditionsArray: MoveCondition[]; switch (sequence) { case -1: diff --git a/src/enums/move-use-mode.ts b/src/enums/move-use-mode.ts index b08977195f0..03ab9a019b3 100644 --- a/src/enums/move-use-mode.ts +++ b/src/enums/move-use-mode.ts @@ -59,6 +59,7 @@ export const MoveUseMode = { * **cannot be reflected by other reflecting effects**. */ REFLECTED: 5, + /** * This "move" was created by a transparent effect that **does not count as using a move**, * such as {@linkcode DelayedAttackAttr | Future Sight/Doom Desire}. @@ -77,9 +78,10 @@ export type MoveUseMode = ObjectValues; // Please update the markdown tables if any new `MoveUseMode`s get added. /** - * Check if a given {@linkcode MoveUseMode} is virtual (i.e. called by another move or effect). + * Check if a given `MoveUseMode` is virtual (i.e. called by another move or effect). * Virtual moves are ignored by most moveset-related effects due to not being executed directly. - * @returns Whether {@linkcode useMode} is virtual. + * @param useMode - The {@linkcode MoveUseMode} to check + * @returns Whether `useMode` is virtual. * @remarks * This function is equivalent to the following truth table: * @@ -97,10 +99,10 @@ export function isVirtual(useMode: MoveUseMode): boolean { } /** - * Check if a given {@linkcode MoveUseMode} should ignore pre-move cancellation checks + * Check if a given `MoveUseMode` should ignore pre-move cancellation checks * from {@linkcode StatusEffect.PARALYSIS} and {@linkcode BattlerTagLapseType.MOVE}-type effects. - * @param useMode - The {@linkcode MoveUseMode} to check. - * @returns Whether {@linkcode useMode} should ignore status and otehr cancellation checks. + * @param useMode - The {@linkcode MoveUseMode} to check + * @returns Whether `useMode` should ignore status and other cancellation checks. * @remarks * This function is equivalent to the following truth table: * @@ -118,10 +120,10 @@ export function isIgnoreStatus(useMode: MoveUseMode): boolean { } /** - * Check if a given {@linkcode MoveUseMode} should ignore PP. + * Check if a given `MoveUseMode` should ignore PP. * PP-ignoring moves will ignore normal PP consumption as well as associated failure checks. - * @param useMode - The {@linkcode MoveUseMode} to check. - * @returns Whether {@linkcode useMode} ignores PP. + * @param useMode - The {@linkcode MoveUseMode} to check + * @returns Whether `useMode` ignores PP consumption. * @remarks * This function is equivalent to the following truth table: * @@ -139,11 +141,11 @@ export function isIgnorePP(useMode: MoveUseMode): boolean { } /** - * Check if a given {@linkcode MoveUseMode} is reflected. + * Check if a given `MoveUseMode` is reflected. * Reflected moves cannot be reflected, copied, or cancelled by status effects, * nor will they trigger {@linkcode PostDancingMoveAbAttr | Dancer}. - * @param useMode - The {@linkcode MoveUseMode} to check. - * @returns Whether {@linkcode useMode} is reflected. + * @param useMode - The {@linkcode MoveUseMode} to check + * @returns Whether `useMode` is reflected. * @remarks * This function is equivalent to the following truth table: * diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index bc51389fdfc..6fdd9fc8b17 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5003,20 +5003,24 @@ export abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Queue the status cure message, reset the status, and update the info display + * Helper function for the Move phase that queues the status cure message, + * resets it, and updates the info display. * @param effect - The effect to cure. If this does not match the current status, nothing happens. - * @param msg - A custom message to display when curing the status effect (used for curing freeze due to move use) + * @param msg - If provided, will override the default message displayed when removing status. + * Used for moves that thaw the user out */ - public cureStatus(effect: StatusEffect, msg?: string): void { + // TODO: Distinguish this more from `resetStatus` + public cureStatus(effect: StatusEffect, msg = getStatusEffectHealText(effect, getPokemonNameWithAffix(this))): void { if (effect !== this.status?.effect) { return; } - // Freeze healed by move uses its own msg - globalScene.phaseManager.queueMessage(msg ?? getStatusEffectHealText(effect, getPokemonNameWithAffix(this))); - // cannot use `asPhase=true` as it will cause status to be reset _after_ this phase ends + + globalScene.phaseManager.queueMessage(msg); + // cannot use `asPhase=true` as it will cause status to be reset _after_ the move phase ends this.resetStatus(undefined, undefined, undefined, false); this.updateInfo(); } + /** * Reset this Pokémon's status * @param revive - Whether revive should be cured; default `true` From 95470b02c11dc8e8eedfbed203a8072558a6669f Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 20 Dec 2025 18:37:11 -0500 Subject: [PATCH 097/101] =?UTF-8?q?[Audio]=20Paradox=20Pok=C3=A9mon=20play?= =?UTF-8?q?=20theme=20when=20outside=20of=20End=20(#6621)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Override music in End biome for Classic, add music in battle.ts for Paradox Pokémon * Add additional property to if statement Paradox Pokemon will play their intended music in Endless now. * Don't change music for Paradox Pokemon in Endless * Change `if` instead of adding `break` --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Madmadness65 --- src/battle.ts | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index 09e6e7141b5..21f24aef29c 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -4,6 +4,7 @@ import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; import { BattleType } from "#enums/battle-type"; import { BattlerIndex } from "#enums/battler-index"; +import { BiomeId } from "#enums/biome-id"; import type { Command } from "#enums/command"; import type { MoveId } from "#enums/move-id"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; @@ -22,6 +23,7 @@ import { MusicPreference } from "#system/settings"; import { trainerConfigs } from "#trainers/trainer-config"; import type { TurnMove } from "#types/turn-move"; import { + isBetween, NumberHolder, randInt, randomString, @@ -249,8 +251,13 @@ export class Battle { } return this.trainer?.getMixedBattleBgm() ?? null; } - if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) { - return "end_summit"; + if (this.gameMode.isClassic) { + if (isBetween(this.waveIndex, 191, 194)) { + return "end"; + } + if (isBetween(this.waveIndex, 196, 199)) { + return "end_summit"; + } } const wildOpponents = globalScene.getEnemyParty(); for (const pokemon of wildOpponents) { @@ -260,7 +267,12 @@ export class Battle { } return "battle_final_encounter"; } - if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) { + if ( + pokemon.species.legendary + || pokemon.species.subLegendary + || pokemon.species.mythical + || (pokemon.species.category.startsWith("Paradox") && globalScene.arena.biomeType !== BiomeId.END) + ) { if (globalScene.musicPreference === MusicPreference.GENFIVE) { switch (pokemon.species.speciesId) { case SpeciesId.REGIROCK: @@ -401,6 +413,26 @@ export class Battle { case SpeciesId.TING_LU: case SpeciesId.CHI_YU: return "battle_legendary_ruinous"; + case SpeciesId.GREAT_TUSK: + case SpeciesId.SCREAM_TAIL: + case SpeciesId.BRUTE_BONNET: + case SpeciesId.FLUTTER_MANE: + case SpeciesId.SLITHER_WING: + case SpeciesId.SANDY_SHOCKS: + case SpeciesId.IRON_TREADS: + case SpeciesId.IRON_BUNDLE: + case SpeciesId.IRON_HANDS: + case SpeciesId.IRON_JUGULIS: + case SpeciesId.IRON_MOTH: + case SpeciesId.IRON_THORNS: + case SpeciesId.ROARING_MOON: + case SpeciesId.IRON_VALIANT: + case SpeciesId.WALKING_WAKE: + case SpeciesId.IRON_LEAVES: + case SpeciesId.GOUGING_FIRE: + case SpeciesId.RAGING_BOLT: + case SpeciesId.IRON_BOULDER: + case SpeciesId.IRON_CROWN: case SpeciesId.KORAIDON: case SpeciesId.MIRAIDON: return "battle_legendary_kor_mir"; From 29d88d558c8abf6d39fe06bbd071f17acc295d8c Mon Sep 17 00:00:00 2001 From: Fabi <192151969+fabske0@users.noreply.github.com> Date: Sun, 21 Dec 2025 00:46:13 +0100 Subject: [PATCH 098/101] [Balance] increase ai friendship scaling (#6879) --- src/field/pokemon.ts | 4 ++-- test/field/pokemon.test.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6fdd9fc8b17..35ca5c6633f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -173,7 +173,7 @@ import { calculateBossSegmentDamage } from "#utils/damage"; import { getEnumValues } from "#utils/enums"; import { getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils"; import { inSpeedOrder } from "#utils/speed-order-generator"; -import { argbFromRgba, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities"; +import { argbFromRgba, clampInt, QuantizerCelebi, rgbaFromArgb } from "@material/material-color-utilities"; import i18next from "i18next"; import Phaser from "phaser"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; @@ -6405,7 +6405,7 @@ export class EnemyPokemon extends Pokemon { ivs.push(randSeedIntRange(Math.floor(waveIndex / 10), 31)); } this.ivs = ivs; - this.friendship = Math.round(255 * (waveIndex / 200)); + this.friendship = clampInt(50, 255, Math.round(255 * (waveIndex / 145))); } } diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index 075ae36672d..aaae19d7193 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -215,12 +215,12 @@ describe("Spec - Pokemon", () => { }); it.each([ - { wave: 5, friendship: 6 }, - { wave: 25, friendship: 32 }, - { wave: 55, friendship: 70 }, - { wave: 95, friendship: 121 }, - { wave: 145, friendship: 185 }, - { wave: 195, friendship: 249 }, + { wave: 5, friendship: 50 }, + { wave: 25, friendship: 50 }, + { wave: 55, friendship: 97 }, + { wave: 95, friendship: 167 }, + { wave: 145, friendship: 255 }, + { wave: 195, friendship: 255 }, ])("should set friendship for enemy trainer pokemon based on wave ($wave)", async ({ wave, friendship }) => { game.override.startingWave(wave); await game.classicMode.runToSummon([SpeciesId.FEEBAS]); From 8e38d880461de8f169862e5c9fa127d507610102 Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 20 Dec 2025 18:54:09 -0500 Subject: [PATCH 099/101] assets and version bump --- assets | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets b/assets index 3587f12fac1..a36741a2112 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 3587f12fac13284d0846e6ea10e1203552dff1ce +Subproject commit a36741a2112217eaf067248d7d1917266339a56d diff --git a/package.json b/package.json index c1fdf94cda9..348fe750905 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.11.3", + "version": "1.11.4", "type": "module", "scripts": { "start:prod": "vite --mode production", From f317ec9a263b30a6ea0a6f4c21318f59ad0c4500 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:55:23 -0500 Subject: [PATCH 100/101] [Bug] Avoid pre-emptively leaving the field when forcibly switching out https://github.com/pagefaultgames/pokerogue/pull/6874 * [Bug] Avoid pre-emptively leaving the field when forcibly switching out * Fix `SwitchPhase` off-by-one error * Add check for pokemon fainting in test * add test for destiny bond crash * Fix `queueDeferred` not respecting pending `FaintPhase`s TL;DR we would defer the faint phases to run after the switch sequences, which is wrong - leaving the field has to be the LAST thing that happens in a given turn (or else shit breaks big-time). * Update test/moves/u-turn.test.ts --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Fabi <192151969+fabske0@users.noreply.github.com> --- src/data/abilities/ability.ts | 2 -- src/data/battle-anims.ts | 2 ++ src/data/moves/move.ts | 3 --- src/phase-manager.ts | 2 +- src/phase-tree.ts | 9 ------- src/phases/switch-phase.ts | 18 ++++++-------- src/phases/switch-summon-phase.ts | 12 +++++++++ test/moves/u-turn.test.ts | 41 ++++++++++++++++++++++++++++++- 8 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 348764d4659..681238f7b2b 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -6309,7 +6309,6 @@ class ForceSwitchOutHelper { } if (switchOutTarget.hp > 0) { - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); globalScene.phaseManager.queueDeferred( "SwitchPhase", this.switchType, @@ -6328,7 +6327,6 @@ class ForceSwitchOutHelper { return false; } if (switchOutTarget.hp > 0) { - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); const summonIndex = globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0; diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index fb9082d6ef6..e6f73eb0ce6 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -14,6 +14,8 @@ import { getEnumKeys, getEnumValues } from "#utils/enums"; import { toKebabCase } from "#utils/strings"; import Phaser from "phaser"; +// TODO: Split up this entire file - it has way WAY too much stuff for its own good. +// (Also happens to be positively spaghetti, but that's besides the point) export class AnimConfig { public id: number; public graphic: string; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index cc646539dba..042ff9af44c 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -6982,7 +6982,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (switchOutTarget.hp > 0) { if (this.switchType === SwitchType.FORCE_SWITCH) { - switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; globalScene.phaseManager.queueDeferred( "SwitchSummonPhase", @@ -7027,7 +7026,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (switchOutTarget.hp > 0) { if (this.switchType === SwitchType.FORCE_SWITCH) { - switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randBattleSeedInt(eligibleNewIndices.length)]; globalScene.phaseManager.queueDeferred( "SwitchSummonPhase", @@ -7038,7 +7036,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { false, ); } else { - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); globalScene.phaseManager.queueDeferred( "SwitchSummonPhase", this.switchType, diff --git a/src/phase-manager.ts b/src/phase-manager.ts index 5f1637d1378..776aebef763 100644 --- a/src/phase-manager.ts +++ b/src/phase-manager.ts @@ -536,7 +536,7 @@ export class PhaseManager { phase: T, ...args: ConstructorParameters ): void { - this.phaseQueue.unshiftToCurrent(this.create(phase, ...args)); + this.phaseQueue.addPhase(this.create(phase, ...args), true); } /** diff --git a/src/phase-tree.ts b/src/phase-tree.ts index f348f89818c..02db3d5bd1d 100644 --- a/src/phase-tree.ts +++ b/src/phase-tree.ts @@ -78,15 +78,6 @@ export class PhaseTree { this.addPhase(phase); } - /** - * Unshifts a {@linkcode Phase} to the current level. - * This is effectively the same as if the phase were added immediately after the currently-running phase, before it started. - * @param phase - The {@linkcode Phase} to be added - */ - public unshiftToCurrent(phase: Phase): void { - this.levels[this.currentLevel].unshift(phase); - } - /** * Pushes a {@linkcode Phase} to the last level of the queue. It will run only after all previously queued phases have been executed. * @param phase - The {@linkcode Phase} to be added diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index 9ab06ec827c..fe856e56f4a 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -22,7 +22,8 @@ export class SwitchPhase extends BattlePhase { * @param isModal Indicates if the switch should be forced (true) or is * optional (false). * @param doReturn Indicates if the party member on the field should be - * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. + * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}, + * and is (ostensibly) only set to `false` from `FaintPhase`. */ constructor(switchType: SwitchType, fieldIndex: number, isModal: boolean, doReturn: boolean) { super(); @@ -36,11 +37,8 @@ export class SwitchPhase extends BattlePhase { start() { super.start(); - // Skip modal switch if impossible (no remaining party members that aren't in battle) - if ( - this.isModal - && globalScene.getPlayerParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length === 0 - ) { + // Skip modal switch if impossible (no remaining party members that aren't already in battle) + if (this.isModal && globalScene.getPokemonAllowedInBattle().every(p => p.isOnField())) { return super.end(); } @@ -51,16 +49,14 @@ export class SwitchPhase extends BattlePhase { * if the mon should have already been returned but is still alive and well * on the field. see also; battle.test.ts */ + // TODO: If a Phasing move kills its own user, when does said user appear on field? + // Is it after the user faints if (this.isModal && !this.doReturn && !globalScene.getPlayerParty()[this.fieldIndex].isFainted()) { return super.end(); } // Check if there is any space still in field - if ( - this.isModal - && globalScene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length - >= globalScene.currentBattle.getBattlerCount() - ) { + if (this.isModal && globalScene.getPlayerField(true).length > globalScene.currentBattle.getBattlerCount()) { return super.end(); } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 9ccc3cc573e..e9834351015 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -126,6 +126,18 @@ export class SwitchSummonPhase extends SummonPhase { switchedInPokemon.resetSummonData(); switchedInPokemon.loadAssets(true); + // Even more defensive programming: Some callers will or will not make their users leave the field + // before this phase starts. + // To account for this (and avoid crashes by leaving the field during move processing), + // forcibly ensure the the victim is off of the field if they have not already done so. + // TODO: This means the switch out will occur immediately from U-turn's effect if the U-Turn user faints + // (instead of happening at end of turn from an empty slot). + // That being said, this blemish becomes completely irrelevant + // once #6611 burns the entire system to the ground. + if (this.lastPokemon.isOnField()) { + this.lastPokemon.leaveField(this.switchType === SwitchType.SWITCH); + } + applyAbAttrs("PreSummonAbAttr", { pokemon: switchedInPokemon }); applyAbAttrs("PreSwitchOutAbAttr", { pokemon: this.lastPokemon }); if (!switchedInPokemon) { diff --git a/test/moves/u-turn.test.ts b/test/moves/u-turn.test.ts index 25c333e58e1..96f683d0cb4 100644 --- a/test/moves/u-turn.test.ts +++ b/test/moves/u-turn.test.ts @@ -1,4 +1,5 @@ import { AbilityId } from "#enums/ability-id"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; @@ -24,7 +25,7 @@ describe("Moves - U-turn", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") - .enemySpecies(SpeciesId.GENGAR) + .enemySpecies(SpeciesId.MAGIKARP) .startingLevel(90) .startingWave(97) .moveset([MoveId.U_TURN]) @@ -103,4 +104,42 @@ describe("Moves - U-turn", () => { expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.SHUCKLE); }); + + it("should not crash when KOing the user from a reactive effect", async () => { + game.override.enemyAbility(AbilityId.ROUGH_SKIN); + await game.classicMode.startBattle([SpeciesId.SHEDINJA, SpeciesId.FEEBAS]); + + const player1 = game.field.getPlayerPokemon(); + + game.move.use(MoveId.U_TURN); + game.doSelectPartyPokemon(1); + await game.toEndOfTurn(); + + expect(game.field.getPlayerPokemon().species.speciesId).toBe(SpeciesId.FEEBAS); + expect(player1).toHaveFainted(); + }); + + it("should not crash when KOing the user via Destiny Bond", async () => { + await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MILOTIC]); + + const feebas = game.field.getPlayerPokemon(); + const karp = game.field.getEnemyPokemon(); + karp.hp = 1; + + game.move.use(MoveId.U_TURN); + game.doSelectPartyPokemon(1); + await game.move.forceEnemyMove(MoveId.DESTINY_BOND); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.toEndOfTurn(); + + expect(karp).toHaveFainted(); + expect(feebas).toHaveFainted(); + expect(feebas.isOnField()).toBe(false); + + // Make sure feebas' faint phase runs before being switched out (since that was the root cause of the crash) + const logs = game.phaseInterceptor.log; + expect(logs).toContain("SwitchSummonPhase"); + expect(logs).toContain("FaintPhase"); + expect(logs.indexOf("SwitchSummonPhase")).toBeGreaterThan(logs.indexOf("FaintPhase")); + }); }); From 2e04e6296584e9d7db2f6da60cec6a154b63b1d9 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:56:15 -0600 Subject: [PATCH 101/101] [Balance] Misc TM Changes + Removals (#6815) * Ice Types now share Hail + Snowscape * comment out cut and frustration * Remove Veluza, add Hisui Avalugg to Hail * Gen 1 Indigo Disk update and Politoed I guess * More TM additions * More changes, fix Malamar's name * Update tm-species-map.ts * Remove Cut and Frustration --- src/data/balance/tm-species-map.ts | 1692 ++++++---------------------- src/data/balance/tms.ts | 2 - 2 files changed, 354 insertions(+), 1340 deletions(-) diff --git a/src/data/balance/tm-species-map.ts b/src/data/balance/tm-species-map.ts index 80d3a9db124..d4fcb371758 100644 --- a/src/data/balance/tm-species-map.ts +++ b/src/data/balance/tm-species-map.ts @@ -1205,284 +1205,6 @@ export const tmSpecies = { SpeciesId.HISUI_DECIDUEYE, SpeciesId.BLOODMOON_URSALUNA, ], - [MoveId.CUT]: [ - SpeciesId.BULBASAUR, - SpeciesId.IVYSAUR, - SpeciesId.VENUSAUR, - SpeciesId.CHARMANDER, - SpeciesId.CHARMELEON, - SpeciesId.CHARIZARD, - SpeciesId.BEEDRILL, - SpeciesId.RATTATA, - SpeciesId.RATICATE, - SpeciesId.SANDSHREW, - SpeciesId.SANDSLASH, - SpeciesId.NIDORAN_F, - SpeciesId.NIDORINA, - SpeciesId.NIDOQUEEN, - SpeciesId.NIDORAN_M, - SpeciesId.NIDORINO, - SpeciesId.NIDOKING, - SpeciesId.ODDISH, - SpeciesId.GLOOM, - SpeciesId.VILEPLUME, - SpeciesId.PARAS, - SpeciesId.PARASECT, - SpeciesId.DIGLETT, - SpeciesId.DUGTRIO, - SpeciesId.MEOWTH, - SpeciesId.PERSIAN, - SpeciesId.BELLSPROUT, - SpeciesId.WEEPINBELL, - SpeciesId.VICTREEBEL, - SpeciesId.TENTACOOL, - SpeciesId.TENTACRUEL, - SpeciesId.FARFETCHD, - SpeciesId.KRABBY, - SpeciesId.KINGLER, - SpeciesId.LICKITUNG, - SpeciesId.RHYDON, - SpeciesId.TANGELA, - SpeciesId.KANGASKHAN, - SpeciesId.SCYTHER, - SpeciesId.PINSIR, - SpeciesId.KABUTOPS, - SpeciesId.DRAGONITE, - SpeciesId.MEW, - SpeciesId.CHIKORITA, - SpeciesId.BAYLEEF, - SpeciesId.MEGANIUM, - SpeciesId.CYNDAQUIL, - SpeciesId.QUILAVA, - SpeciesId.TYPHLOSION, - SpeciesId.TOTODILE, - SpeciesId.CROCONAW, - SpeciesId.FERALIGATR, - SpeciesId.SENTRET, - SpeciesId.FURRET, - SpeciesId.BELLOSSOM, - SpeciesId.AIPOM, - SpeciesId.SUNKERN, - SpeciesId.SUNFLORA, - SpeciesId.ESPEON, - SpeciesId.UMBREON, - SpeciesId.GLIGAR, - SpeciesId.STEELIX, - SpeciesId.SCIZOR, - SpeciesId.HERACROSS, - SpeciesId.SNEASEL, - SpeciesId.TEDDIURSA, - SpeciesId.URSARING, - SpeciesId.SKARMORY, - SpeciesId.RAIKOU, - SpeciesId.ENTEI, - SpeciesId.SUICUNE, - SpeciesId.TYRANITAR, - SpeciesId.CELEBI, - SpeciesId.TREECKO, - SpeciesId.GROVYLE, - SpeciesId.SCEPTILE, - SpeciesId.TORCHIC, - SpeciesId.COMBUSKEN, - SpeciesId.BLAZIKEN, - SpeciesId.ZIGZAGOON, - SpeciesId.LINOONE, - SpeciesId.NUZLEAF, - SpeciesId.SHIFTRY, - SpeciesId.BRELOOM, - SpeciesId.SLAKOTH, - SpeciesId.VIGOROTH, - SpeciesId.SLAKING, - SpeciesId.NINCADA, - SpeciesId.NINJASK, - SpeciesId.SHEDINJA, - SpeciesId.SABLEYE, - SpeciesId.ARON, - SpeciesId.LAIRON, - SpeciesId.AGGRON, - SpeciesId.ROSELIA, - SpeciesId.CACNEA, - SpeciesId.CACTURNE, - SpeciesId.CORPHISH, - SpeciesId.CRAWDAUNT, - SpeciesId.ANORITH, - SpeciesId.ARMALDO, - SpeciesId.KECLEON, - SpeciesId.TROPIUS, - SpeciesId.ABSOL, - SpeciesId.BAGON, - SpeciesId.SHELGON, - SpeciesId.SALAMENCE, - SpeciesId.METANG, - SpeciesId.METAGROSS, - SpeciesId.LATIAS, - SpeciesId.LATIOS, - SpeciesId.GROUDON, - SpeciesId.DEOXYS, - SpeciesId.TURTWIG, - SpeciesId.GROTLE, - SpeciesId.TORTERRA, - SpeciesId.CHIMCHAR, - SpeciesId.MONFERNO, - SpeciesId.INFERNAPE, - SpeciesId.PIPLUP, - SpeciesId.PRINPLUP, - SpeciesId.EMPOLEON, - SpeciesId.BIDOOF, - SpeciesId.BIBAREL, - SpeciesId.KRICKETUNE, - SpeciesId.BUDEW, - SpeciesId.ROSERADE, - SpeciesId.RAMPARDOS, - SpeciesId.VESPIQUEN, - SpeciesId.PACHIRISU, - SpeciesId.AMBIPOM, - SpeciesId.DRIFLOON, - SpeciesId.DRIFBLIM, - SpeciesId.BUNEARY, - SpeciesId.LOPUNNY, - SpeciesId.GLAMEOW, - SpeciesId.PURUGLY, - SpeciesId.STUNKY, - SpeciesId.SKUNTANK, - SpeciesId.GIBLE, - SpeciesId.GABITE, - SpeciesId.GARCHOMP, - SpeciesId.SKORUPI, - SpeciesId.DRAPION, - SpeciesId.TOXICROAK, - SpeciesId.CARNIVINE, - SpeciesId.WEAVILE, - SpeciesId.LICKILICKY, - SpeciesId.RHYPERIOR, - SpeciesId.TANGROWTH, - SpeciesId.GLISCOR, - SpeciesId.GALLADE, - SpeciesId.DIALGA, - SpeciesId.PALKIA, - SpeciesId.GIRATINA, - SpeciesId.DARKRAI, - SpeciesId.ARCEUS, - SpeciesId.SNIVY, - SpeciesId.SERVINE, - SpeciesId.SERPERIOR, - SpeciesId.OSHAWOTT, - SpeciesId.DEWOTT, - SpeciesId.SAMUROTT, - SpeciesId.PATRAT, - SpeciesId.WATCHOG, - SpeciesId.PURRLOIN, - SpeciesId.LIEPARD, - SpeciesId.PANSAGE, - SpeciesId.SIMISAGE, - SpeciesId.PANSEAR, - SpeciesId.SIMISEAR, - SpeciesId.PANPOUR, - SpeciesId.SIMIPOUR, - SpeciesId.DRILBUR, - SpeciesId.EXCADRILL, - SpeciesId.SEWADDLE, - SpeciesId.SWADLOON, - SpeciesId.LEAVANNY, - SpeciesId.SCOLIPEDE, - SpeciesId.PETILIL, - SpeciesId.LILLIGANT, - SpeciesId.BASCULIN, - SpeciesId.SANDILE, - SpeciesId.KROKOROK, - SpeciesId.KROOKODILE, - SpeciesId.DWEBBLE, - SpeciesId.CRUSTLE, - SpeciesId.ARCHEN, - SpeciesId.ARCHEOPS, - SpeciesId.ZORUA, - SpeciesId.ZOROARK, - SpeciesId.SAWSBUCK, - SpeciesId.EMOLGA, - SpeciesId.KARRABLAST, - SpeciesId.ESCAVALIER, - SpeciesId.JOLTIK, - SpeciesId.GALVANTULA, - SpeciesId.FERROTHORN, - SpeciesId.EELEKTROSS, - SpeciesId.AXEW, - SpeciesId.FRAXURE, - SpeciesId.HAXORUS, - SpeciesId.CUBCHOO, - SpeciesId.BEARTIC, - SpeciesId.DRUDDIGON, - SpeciesId.PAWNIARD, - SpeciesId.BISHARP, - SpeciesId.BOUFFALANT, - SpeciesId.RUFFLET, - SpeciesId.BRAVIARY, - SpeciesId.VULLABY, - SpeciesId.MANDIBUZZ, - SpeciesId.HEATMOR, - SpeciesId.DURANT, - SpeciesId.COBALION, - SpeciesId.TERRAKION, - SpeciesId.VIRIZION, - SpeciesId.RESHIRAM, - SpeciesId.ZEKROM, - SpeciesId.KYUREM, - SpeciesId.KELDEO, - SpeciesId.CHESPIN, - SpeciesId.QUILLADIN, - SpeciesId.CHESNAUGHT, - SpeciesId.FENNEKIN, - SpeciesId.BRAIXEN, - SpeciesId.DELPHOX, - SpeciesId.FROAKIE, - SpeciesId.FROGADIER, - SpeciesId.GRENINJA, - SpeciesId.BUNNELBY, - SpeciesId.DIGGERSBY, - SpeciesId.PANCHAM, - SpeciesId.PANGORO, - SpeciesId.ESPURR, - SpeciesId.MEOWSTIC, - SpeciesId.HONEDGE, - SpeciesId.DOUBLADE, - SpeciesId.AEGISLASH, - SpeciesId.INKAY, - SpeciesId.MALAMAR, - SpeciesId.BINACLE, - SpeciesId.BARBARACLE, - SpeciesId.CLAUNCHER, - SpeciesId.CLAWITZER, - SpeciesId.HELIOPTILE, - SpeciesId.HELIOLISK, - SpeciesId.SYLVEON, - SpeciesId.HAWLUCHA, - SpeciesId.DEDENNE, - SpeciesId.KLEFKI, - SpeciesId.PHANTUMP, - SpeciesId.TREVENANT, - SpeciesId.NOIBAT, - SpeciesId.NOIVERN, - SpeciesId.XERNEAS, - SpeciesId.YVELTAL, - SpeciesId.VOLCANION, - SpeciesId.KARTANA, - SpeciesId.OBSTAGOON, - SpeciesId.PERRSERKER, - SpeciesId.URSALUNA, - SpeciesId.BASCULEGION, - SpeciesId.KINGAMBIT, - SpeciesId.ALOLA_RATTATA, - SpeciesId.ALOLA_RATICATE, - SpeciesId.ALOLA_MEOWTH, - SpeciesId.ALOLA_PERSIAN, - SpeciesId.GALAR_MEOWTH, - SpeciesId.GALAR_ZIGZAGOON, - SpeciesId.GALAR_LINOONE, - SpeciesId.HISUI_TYPHLOSION, - SpeciesId.HISUI_SAMUROTT, - SpeciesId.HISUI_LILLIGANT, - SpeciesId.HISUI_BRAVIARY, - SpeciesId.BLOODMOON_URSALUNA, - ], [MoveId.FLY]: [ SpeciesId.CHARIZARD, SpeciesId.PIDGEY, @@ -1901,6 +1623,7 @@ export const tmSpecies = { SpeciesId.HYPNO, SpeciesId.KRABBY, SpeciesId.KINGLER, + SpeciesId.EXEGGUTOR, SpeciesId.CUBONE, SpeciesId.MAROWAK, SpeciesId.HITMONLEE, @@ -2127,6 +1850,8 @@ export const tmSpecies = { SpeciesId.EMPOLEON, SpeciesId.LUXRAY, SpeciesId.ROSERADE, + SpeciesId.CRANIDOS, + SpeciesId.RAMPARDOS, SpeciesId.SHIELDON, SpeciesId.BASTIODON, SpeciesId.FLOATZEL, @@ -2411,6 +2136,7 @@ export const tmSpecies = { SpeciesId.ALOLA_GOLEM, SpeciesId.ALOLA_GRIMER, SpeciesId.ALOLA_MUK, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.ALOLA_MAROWAK, SpeciesId.GALAR_MEOWTH, SpeciesId.GALAR_PONYTA, @@ -3227,7 +2953,6 @@ export const tmSpecies = { SpeciesId.KINGLER, SpeciesId.VOLTORB, SpeciesId.ELECTRODE, - SpeciesId.EXEGGCUTE, SpeciesId.EXEGGUTOR, SpeciesId.CUBONE, SpeciesId.MAROWAK, @@ -3807,6 +3532,7 @@ export const tmSpecies = { SpeciesId.RHYHORN, SpeciesId.RHYDON, SpeciesId.KANGASKHAN, + SpeciesId.MAGMAR, SpeciesId.GYARADOS, SpeciesId.LAPRAS, SpeciesId.EEVEE, @@ -3907,6 +3633,7 @@ export const tmSpecies = { SpeciesId.HIPPOWDON, SpeciesId.DRAPION, SpeciesId.RHYPERIOR, + SpeciesId.MAGMORTAR, SpeciesId.LEAFEON, SpeciesId.GLACEON, SpeciesId.MAMOSWINE, @@ -3921,6 +3648,7 @@ export const tmSpecies = { SpeciesId.LILLIPUP, SpeciesId.HERDIER, SpeciesId.STOUTLAND, + SpeciesId.ZEBSTRIKA, SpeciesId.SANDILE, SpeciesId.KROKOROK, SpeciesId.KROOKODILE, @@ -3944,6 +3672,9 @@ export const tmSpecies = { SpeciesId.COBALION, SpeciesId.TERRAKION, SpeciesId.VIRIZION, + SpeciesId.RESHIRAM, + SpeciesId.ZEKROM, + SpeciesId.KYUREM, SpeciesId.KELDEO, SpeciesId.CHESPIN, SpeciesId.QUILLADIN, @@ -3986,6 +3717,7 @@ export const tmSpecies = { SpeciesId.LUNALA, SpeciesId.YAMPER, SpeciesId.BOLTUND, + SpeciesId.DURALUDON, SpeciesId.ZAMAZENTA, SpeciesId.ZARUDE, SpeciesId.GLASTRIER, @@ -4267,6 +3999,8 @@ export const tmSpecies = { SpeciesId.TENTACRUEL, SpeciesId.SLOWPOKE, SpeciesId.SLOWBRO, + SpeciesId.SEEL, + SpeciesId.DEWGONG, SpeciesId.SHELLDER, SpeciesId.CLOYSTER, SpeciesId.KINGLER, @@ -5532,6 +5266,7 @@ export const tmSpecies = { SpeciesId.OCTILLERY, SpeciesId.DELIBIRD, SpeciesId.MANTINE, + SpeciesId.SKARMORY, SpeciesId.HOUNDOOM, SpeciesId.KINGDRA, SpeciesId.DONPHAN, @@ -5799,12 +5534,14 @@ export const tmSpecies = { SpeciesId.DECIDUEYE, SpeciesId.INCINEROAR, SpeciesId.PRIMARINA, + SpeciesId.TOUCANNON, SpeciesId.GUMSHOOS, SpeciesId.VIKAVOLT, SpeciesId.CRABOMINABLE, SpeciesId.RIBOMBEE, SpeciesId.TOXAPEX, SpeciesId.MUDSDALE, + SpeciesId.ARAQUANID, SpeciesId.LURANTIS, SpeciesId.SHIINOTIC, SpeciesId.SALAZZLE, @@ -6456,6 +6193,7 @@ export const tmSpecies = { SpeciesId.FINIZEN, SpeciesId.PALAFIN, SpeciesId.TATSUGIRI, + SpeciesId.ANNIHILAPE, SpeciesId.KORAIDON, SpeciesId.OKIDOGI, SpeciesId.OGERPON, @@ -7219,6 +6957,9 @@ export const tmSpecies = { SpeciesId.MAGMORTAR, SpeciesId.HEATRAN, SpeciesId.VICTINI, + SpeciesId.TEPIG, + SpeciesId.PIGNITE, + SpeciesId.EMBOAR, SpeciesId.PANSEAR, SpeciesId.SIMISEAR, SpeciesId.DARUMAKA, @@ -7230,6 +6971,7 @@ export const tmSpecies = { SpeciesId.HYDREIGON, SpeciesId.LARVESTA, SpeciesId.VOLCARONA, + SpeciesId.RESHIRAM, SpeciesId.FENNEKIN, SpeciesId.BRAIXEN, SpeciesId.DELPHOX, @@ -8110,6 +7852,7 @@ export const tmSpecies = { SpeciesId.SLOWPOKE, SpeciesId.SLOWBRO, SpeciesId.ONIX, + SpeciesId.EXEGGUTOR, SpeciesId.CUBONE, SpeciesId.MAROWAK, SpeciesId.HITMONLEE, @@ -8122,6 +7865,7 @@ export const tmSpecies = { SpeciesId.PINSIR, SpeciesId.TAUROS, SpeciesId.GYARADOS, + SpeciesId.LAPRAS, SpeciesId.AERODACTYL, SpeciesId.SNORLAX, SpeciesId.DRAGONITE, @@ -8625,6 +8369,9 @@ export const tmSpecies = { SpeciesId.GLISCOR, SpeciesId.MAMOSWINE, SpeciesId.HEATRAN, + SpeciesId.TEPIG, + SpeciesId.PIGNITE, + SpeciesId.EMBOAR, SpeciesId.OSHAWOTT, SpeciesId.DEWOTT, SpeciesId.SAMUROTT, @@ -10020,6 +9767,7 @@ export const tmSpecies = { SpeciesId.MINUN, SpeciesId.CARVANHA, SpeciesId.SHARPEDO, + SpeciesId.FLYGON, SpeciesId.SWABLU, SpeciesId.ALTARIA, SpeciesId.ZANGOOSE, @@ -10188,6 +9936,7 @@ export const tmSpecies = { SpeciesId.IRON_LEAVES, SpeciesId.FEZANDIPITI, SpeciesId.IRON_BOULDER, + SpeciesId.IRON_CROWN, [ SpeciesId.DEOXYS, "speed", @@ -17486,6 +17235,9 @@ export const tmSpecies = { SpeciesId.KABUTOPS, SpeciesId.AERODACTYL, SpeciesId.MEW, + SpeciesId.TOTODILE, + SpeciesId.CROCONAW, + SpeciesId.FERALIGATR, SpeciesId.SENTRET, SpeciesId.FURRET, SpeciesId.HOOTHOOT, @@ -17534,6 +17286,11 @@ export const tmSpecies = { SpeciesId.ELEKID, SpeciesId.MAGBY, SpeciesId.BLISSEY, + SpeciesId.TREECKO, + SpeciesId.GROVYLE, + SpeciesId.SCEPTILE, + SpeciesId.COMBUSKEN, + SpeciesId.BLAZIKEN, SpeciesId.POOCHYENA, SpeciesId.MIGHTYENA, SpeciesId.ZIGZAGOON, @@ -17735,6 +17492,7 @@ export const tmSpecies = { SpeciesId.NOIVERN, SpeciesId.YVELTAL, SpeciesId.HOOPA, + SpeciesId.INCINEROAR, SpeciesId.PIKIPEK, SpeciesId.TRUMBEAK, SpeciesId.TOUCANNON, @@ -19260,6 +19018,7 @@ export const tmSpecies = { SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_WEEZING, SpeciesId.GALAR_SLOWKING, + SpeciesId.GALAR_CORSOLA, SpeciesId.GALAR_YAMASK, SpeciesId.GALAR_STUNFISK, SpeciesId.HISUI_ELECTRODE, @@ -19343,6 +19102,7 @@ export const tmSpecies = { SpeciesId.SEVIPER, SpeciesId.MONFERNO, SpeciesId.INFERNAPE, + SpeciesId.BASTIODON, SpeciesId.VESPIQUEN, SpeciesId.LOPUNNY, SpeciesId.RIOLU, @@ -21695,6 +21455,7 @@ export const tmSpecies = { SpeciesId.GREEDENT, SpeciesId.ROLYCOLY, SpeciesId.CARKOL, + SpeciesId.COALOSSAL, SpeciesId.SILICOBRA, SpeciesId.SANDACONDA, SpeciesId.SPRIGATITO, @@ -22495,6 +22256,8 @@ export const tmSpecies = { SpeciesId.KLINKLANG, SpeciesId.ACCELGOR, SpeciesId.STUNFISK, + SpeciesId.GOLETT, + SpeciesId.GOLURK, SpeciesId.PAWNIARD, SpeciesId.BISHARP, SpeciesId.MANDIBUZZ, @@ -22535,6 +22298,7 @@ export const tmSpecies = { SpeciesId.HAKAMO_O, SpeciesId.KOMMO_O, SpeciesId.NIHILEGO, + SpeciesId.NECROZMA, SpeciesId.STAKATAKA, SpeciesId.DREDNAW, SpeciesId.ROLYCOLY, @@ -23315,6 +23079,12 @@ export const tmSpecies = { SpeciesId.SHAYMIN, SpeciesId.ARCEUS, SpeciesId.VICTINI, + SpeciesId.SNIVY, + SpeciesId.SERVINE, + SpeciesId.SERPERIOR, + SpeciesId.TEPIG, + SpeciesId.PIGNITE, + SpeciesId.EMBOAR, SpeciesId.OSHAWOTT, SpeciesId.DEWOTT, SpeciesId.SAMUROTT, @@ -23532,6 +23302,9 @@ export const tmSpecies = { SpeciesId.POPPLIO, SpeciesId.BRIONNE, SpeciesId.PRIMARINA, + SpeciesId.PIKIPEK, + SpeciesId.TRUMBEAK, + SpeciesId.TOUCANNON, SpeciesId.YUNGOOS, SpeciesId.GUMSHOOS, SpeciesId.GRUBBIN, @@ -23572,6 +23345,7 @@ export const tmSpecies = { SpeciesId.PYUKUMUKU, SpeciesId.TYPE_NULL, SpeciesId.SILVALLY, + SpeciesId.MINIOR, SpeciesId.KOMALA, SpeciesId.TURTONATOR, SpeciesId.TOGEDEMARU, @@ -28283,1065 +28057,6 @@ export const tmSpecies = { SpeciesId.PALDEA_WOOPER, SpeciesId.BLOODMOON_URSALUNA, ], - [MoveId.FRUSTRATION]: [ - SpeciesId.BULBASAUR, - SpeciesId.IVYSAUR, - SpeciesId.VENUSAUR, - SpeciesId.CHARMANDER, - SpeciesId.CHARMELEON, - SpeciesId.CHARIZARD, - SpeciesId.SQUIRTLE, - SpeciesId.WARTORTLE, - SpeciesId.BLASTOISE, - SpeciesId.BUTTERFREE, - SpeciesId.BEEDRILL, - SpeciesId.PIDGEY, - SpeciesId.PIDGEOTTO, - SpeciesId.PIDGEOT, - SpeciesId.RATTATA, - SpeciesId.RATICATE, - SpeciesId.SPEAROW, - SpeciesId.FEAROW, - SpeciesId.EKANS, - SpeciesId.ARBOK, - SpeciesId.PIKACHU, - SpeciesId.RAICHU, - SpeciesId.SANDSHREW, - SpeciesId.SANDSLASH, - SpeciesId.NIDORAN_F, - SpeciesId.NIDORINA, - SpeciesId.NIDOQUEEN, - SpeciesId.NIDORAN_M, - SpeciesId.NIDORINO, - SpeciesId.NIDOKING, - SpeciesId.CLEFAIRY, - SpeciesId.CLEFABLE, - SpeciesId.VULPIX, - SpeciesId.NINETALES, - SpeciesId.JIGGLYPUFF, - SpeciesId.WIGGLYTUFF, - SpeciesId.ZUBAT, - SpeciesId.GOLBAT, - SpeciesId.ODDISH, - SpeciesId.GLOOM, - SpeciesId.VILEPLUME, - SpeciesId.PARAS, - SpeciesId.PARASECT, - SpeciesId.VENONAT, - SpeciesId.VENOMOTH, - SpeciesId.DIGLETT, - SpeciesId.DUGTRIO, - SpeciesId.MEOWTH, - SpeciesId.PERSIAN, - SpeciesId.PSYDUCK, - SpeciesId.GOLDUCK, - SpeciesId.MANKEY, - SpeciesId.PRIMEAPE, - SpeciesId.GROWLITHE, - SpeciesId.ARCANINE, - SpeciesId.POLIWAG, - SpeciesId.POLIWHIRL, - SpeciesId.POLIWRATH, - SpeciesId.ABRA, - SpeciesId.KADABRA, - SpeciesId.ALAKAZAM, - SpeciesId.MACHOP, - SpeciesId.MACHOKE, - SpeciesId.MACHAMP, - SpeciesId.BELLSPROUT, - SpeciesId.WEEPINBELL, - SpeciesId.VICTREEBEL, - SpeciesId.TENTACOOL, - SpeciesId.TENTACRUEL, - SpeciesId.GEODUDE, - SpeciesId.GRAVELER, - SpeciesId.GOLEM, - SpeciesId.PONYTA, - SpeciesId.RAPIDASH, - SpeciesId.SLOWPOKE, - SpeciesId.SLOWBRO, - SpeciesId.MAGNEMITE, - SpeciesId.MAGNETON, - SpeciesId.FARFETCHD, - SpeciesId.DODUO, - SpeciesId.DODRIO, - SpeciesId.SEEL, - SpeciesId.DEWGONG, - SpeciesId.GRIMER, - SpeciesId.MUK, - SpeciesId.SHELLDER, - SpeciesId.CLOYSTER, - SpeciesId.GASTLY, - SpeciesId.HAUNTER, - SpeciesId.GENGAR, - SpeciesId.ONIX, - SpeciesId.DROWZEE, - SpeciesId.HYPNO, - SpeciesId.KRABBY, - SpeciesId.KINGLER, - SpeciesId.VOLTORB, - SpeciesId.ELECTRODE, - SpeciesId.EXEGGCUTE, - SpeciesId.EXEGGUTOR, - SpeciesId.CUBONE, - SpeciesId.MAROWAK, - SpeciesId.HITMONLEE, - SpeciesId.HITMONCHAN, - SpeciesId.LICKITUNG, - SpeciesId.KOFFING, - SpeciesId.WEEZING, - SpeciesId.RHYHORN, - SpeciesId.RHYDON, - SpeciesId.CHANSEY, - SpeciesId.TANGELA, - SpeciesId.KANGASKHAN, - SpeciesId.HORSEA, - SpeciesId.SEADRA, - SpeciesId.GOLDEEN, - SpeciesId.SEAKING, - SpeciesId.STARYU, - SpeciesId.STARMIE, - SpeciesId.MR_MIME, - SpeciesId.SCYTHER, - SpeciesId.JYNX, - SpeciesId.ELECTABUZZ, - SpeciesId.MAGMAR, - SpeciesId.PINSIR, - SpeciesId.TAUROS, - SpeciesId.GYARADOS, - SpeciesId.LAPRAS, - SpeciesId.EEVEE, - SpeciesId.VAPOREON, - SpeciesId.JOLTEON, - SpeciesId.FLAREON, - SpeciesId.PORYGON, - SpeciesId.OMANYTE, - SpeciesId.OMASTAR, - SpeciesId.KABUTO, - SpeciesId.KABUTOPS, - SpeciesId.AERODACTYL, - SpeciesId.SNORLAX, - SpeciesId.ARTICUNO, - SpeciesId.ZAPDOS, - SpeciesId.MOLTRES, - SpeciesId.DRATINI, - SpeciesId.DRAGONAIR, - SpeciesId.DRAGONITE, - SpeciesId.MEWTWO, - SpeciesId.MEW, - SpeciesId.CHIKORITA, - SpeciesId.BAYLEEF, - SpeciesId.MEGANIUM, - SpeciesId.CYNDAQUIL, - SpeciesId.QUILAVA, - SpeciesId.TYPHLOSION, - SpeciesId.TOTODILE, - SpeciesId.CROCONAW, - SpeciesId.FERALIGATR, - SpeciesId.SENTRET, - SpeciesId.FURRET, - SpeciesId.HOOTHOOT, - SpeciesId.NOCTOWL, - SpeciesId.LEDYBA, - SpeciesId.LEDIAN, - SpeciesId.SPINARAK, - SpeciesId.ARIADOS, - SpeciesId.CROBAT, - SpeciesId.CHINCHOU, - SpeciesId.LANTURN, - SpeciesId.PICHU, - SpeciesId.CLEFFA, - SpeciesId.IGGLYBUFF, - SpeciesId.TOGEPI, - SpeciesId.TOGETIC, - SpeciesId.NATU, - SpeciesId.XATU, - SpeciesId.MAREEP, - SpeciesId.FLAAFFY, - SpeciesId.AMPHAROS, - SpeciesId.BELLOSSOM, - SpeciesId.MARILL, - SpeciesId.AZUMARILL, - SpeciesId.SUDOWOODO, - SpeciesId.POLITOED, - SpeciesId.HOPPIP, - SpeciesId.SKIPLOOM, - SpeciesId.JUMPLUFF, - SpeciesId.AIPOM, - SpeciesId.SUNKERN, - SpeciesId.SUNFLORA, - SpeciesId.YANMA, - SpeciesId.WOOPER, - SpeciesId.QUAGSIRE, - SpeciesId.ESPEON, - SpeciesId.UMBREON, - SpeciesId.MURKROW, - SpeciesId.SLOWKING, - SpeciesId.MISDREAVUS, - SpeciesId.GIRAFARIG, - SpeciesId.PINECO, - SpeciesId.FORRETRESS, - SpeciesId.DUNSPARCE, - SpeciesId.GLIGAR, - SpeciesId.STEELIX, - SpeciesId.SNUBBULL, - SpeciesId.GRANBULL, - SpeciesId.QWILFISH, - SpeciesId.SCIZOR, - SpeciesId.SHUCKLE, - SpeciesId.HERACROSS, - SpeciesId.SNEASEL, - SpeciesId.TEDDIURSA, - SpeciesId.URSARING, - SpeciesId.SLUGMA, - SpeciesId.MAGCARGO, - SpeciesId.SWINUB, - SpeciesId.PILOSWINE, - SpeciesId.CORSOLA, - SpeciesId.REMORAID, - SpeciesId.OCTILLERY, - SpeciesId.DELIBIRD, - SpeciesId.MANTINE, - SpeciesId.SKARMORY, - SpeciesId.HOUNDOUR, - SpeciesId.HOUNDOOM, - SpeciesId.KINGDRA, - SpeciesId.PHANPY, - SpeciesId.DONPHAN, - SpeciesId.PORYGON2, - SpeciesId.STANTLER, - SpeciesId.TYROGUE, - SpeciesId.HITMONTOP, - SpeciesId.SMOOCHUM, - SpeciesId.ELEKID, - SpeciesId.MAGBY, - SpeciesId.MILTANK, - SpeciesId.BLISSEY, - SpeciesId.RAIKOU, - SpeciesId.ENTEI, - SpeciesId.SUICUNE, - SpeciesId.LARVITAR, - SpeciesId.PUPITAR, - SpeciesId.TYRANITAR, - SpeciesId.LUGIA, - SpeciesId.HO_OH, - SpeciesId.CELEBI, - SpeciesId.TREECKO, - SpeciesId.GROVYLE, - SpeciesId.SCEPTILE, - SpeciesId.TORCHIC, - SpeciesId.COMBUSKEN, - SpeciesId.BLAZIKEN, - SpeciesId.MUDKIP, - SpeciesId.MARSHTOMP, - SpeciesId.SWAMPERT, - SpeciesId.POOCHYENA, - SpeciesId.MIGHTYENA, - SpeciesId.ZIGZAGOON, - SpeciesId.LINOONE, - SpeciesId.BEAUTIFLY, - SpeciesId.DUSTOX, - SpeciesId.LOTAD, - SpeciesId.LOMBRE, - SpeciesId.LUDICOLO, - SpeciesId.SEEDOT, - SpeciesId.NUZLEAF, - SpeciesId.SHIFTRY, - SpeciesId.TAILLOW, - SpeciesId.SWELLOW, - SpeciesId.WINGULL, - SpeciesId.PELIPPER, - SpeciesId.RALTS, - SpeciesId.KIRLIA, - SpeciesId.GARDEVOIR, - SpeciesId.SURSKIT, - SpeciesId.MASQUERAIN, - SpeciesId.SHROOMISH, - SpeciesId.BRELOOM, - SpeciesId.SLAKOTH, - SpeciesId.VIGOROTH, - SpeciesId.SLAKING, - SpeciesId.NINCADA, - SpeciesId.NINJASK, - SpeciesId.SHEDINJA, - SpeciesId.WHISMUR, - SpeciesId.LOUDRED, - SpeciesId.EXPLOUD, - SpeciesId.MAKUHITA, - SpeciesId.HARIYAMA, - SpeciesId.AZURILL, - SpeciesId.NOSEPASS, - SpeciesId.SKITTY, - SpeciesId.DELCATTY, - SpeciesId.SABLEYE, - SpeciesId.MAWILE, - SpeciesId.ARON, - SpeciesId.LAIRON, - SpeciesId.AGGRON, - SpeciesId.MEDITITE, - SpeciesId.MEDICHAM, - SpeciesId.ELECTRIKE, - SpeciesId.MANECTRIC, - SpeciesId.PLUSLE, - SpeciesId.MINUN, - SpeciesId.VOLBEAT, - SpeciesId.ILLUMISE, - SpeciesId.ROSELIA, - SpeciesId.GULPIN, - SpeciesId.SWALOT, - SpeciesId.CARVANHA, - SpeciesId.SHARPEDO, - SpeciesId.WAILMER, - SpeciesId.WAILORD, - SpeciesId.NUMEL, - SpeciesId.CAMERUPT, - SpeciesId.TORKOAL, - SpeciesId.SPOINK, - SpeciesId.GRUMPIG, - SpeciesId.SPINDA, - SpeciesId.TRAPINCH, - SpeciesId.VIBRAVA, - SpeciesId.FLYGON, - SpeciesId.CACNEA, - SpeciesId.CACTURNE, - SpeciesId.SWABLU, - SpeciesId.ALTARIA, - SpeciesId.ZANGOOSE, - SpeciesId.SEVIPER, - SpeciesId.LUNATONE, - SpeciesId.SOLROCK, - SpeciesId.BARBOACH, - SpeciesId.WHISCASH, - SpeciesId.CORPHISH, - SpeciesId.CRAWDAUNT, - SpeciesId.BALTOY, - SpeciesId.CLAYDOL, - SpeciesId.LILEEP, - SpeciesId.CRADILY, - SpeciesId.ANORITH, - SpeciesId.ARMALDO, - SpeciesId.FEEBAS, - SpeciesId.MILOTIC, - SpeciesId.CASTFORM, - SpeciesId.KECLEON, - SpeciesId.SHUPPET, - SpeciesId.BANETTE, - SpeciesId.DUSKULL, - SpeciesId.DUSCLOPS, - SpeciesId.TROPIUS, - SpeciesId.CHIMECHO, - SpeciesId.ABSOL, - SpeciesId.SNORUNT, - SpeciesId.GLALIE, - SpeciesId.SPHEAL, - SpeciesId.SEALEO, - SpeciesId.WALREIN, - SpeciesId.CLAMPERL, - SpeciesId.HUNTAIL, - SpeciesId.GOREBYSS, - SpeciesId.RELICANTH, - SpeciesId.LUVDISC, - SpeciesId.BAGON, - SpeciesId.SHELGON, - SpeciesId.SALAMENCE, - SpeciesId.METANG, - SpeciesId.METAGROSS, - SpeciesId.REGIROCK, - SpeciesId.REGICE, - SpeciesId.REGISTEEL, - SpeciesId.LATIAS, - SpeciesId.LATIOS, - SpeciesId.KYOGRE, - SpeciesId.GROUDON, - SpeciesId.RAYQUAZA, - SpeciesId.JIRACHI, - SpeciesId.DEOXYS, - SpeciesId.TURTWIG, - SpeciesId.GROTLE, - SpeciesId.TORTERRA, - SpeciesId.CHIMCHAR, - SpeciesId.MONFERNO, - SpeciesId.INFERNAPE, - SpeciesId.PIPLUP, - SpeciesId.PRINPLUP, - SpeciesId.EMPOLEON, - SpeciesId.STARLY, - SpeciesId.STARAVIA, - SpeciesId.STARAPTOR, - SpeciesId.BIDOOF, - SpeciesId.BIBAREL, - SpeciesId.KRICKETUNE, - SpeciesId.SHINX, - SpeciesId.LUXIO, - SpeciesId.LUXRAY, - SpeciesId.BUDEW, - SpeciesId.ROSERADE, - SpeciesId.CRANIDOS, - SpeciesId.RAMPARDOS, - SpeciesId.SHIELDON, - SpeciesId.BASTIODON, - SpeciesId.WORMADAM, - SpeciesId.MOTHIM, - SpeciesId.VESPIQUEN, - SpeciesId.PACHIRISU, - SpeciesId.BUIZEL, - SpeciesId.FLOATZEL, - SpeciesId.CHERUBI, - SpeciesId.CHERRIM, - SpeciesId.SHELLOS, - SpeciesId.GASTRODON, - SpeciesId.AMBIPOM, - SpeciesId.DRIFLOON, - SpeciesId.DRIFBLIM, - SpeciesId.BUNEARY, - SpeciesId.LOPUNNY, - SpeciesId.MISMAGIUS, - SpeciesId.HONCHKROW, - SpeciesId.GLAMEOW, - SpeciesId.PURUGLY, - SpeciesId.CHINGLING, - SpeciesId.STUNKY, - SpeciesId.SKUNTANK, - SpeciesId.BRONZOR, - SpeciesId.BRONZONG, - SpeciesId.BONSLY, - SpeciesId.MIME_JR, - SpeciesId.HAPPINY, - SpeciesId.CHATOT, - SpeciesId.SPIRITOMB, - SpeciesId.GIBLE, - SpeciesId.GABITE, - SpeciesId.GARCHOMP, - SpeciesId.MUNCHLAX, - SpeciesId.RIOLU, - SpeciesId.LUCARIO, - SpeciesId.HIPPOPOTAS, - SpeciesId.HIPPOWDON, - SpeciesId.SKORUPI, - SpeciesId.DRAPION, - SpeciesId.CROAGUNK, - SpeciesId.TOXICROAK, - SpeciesId.CARNIVINE, - SpeciesId.FINNEON, - SpeciesId.LUMINEON, - SpeciesId.MANTYKE, - SpeciesId.SNOVER, - SpeciesId.ABOMASNOW, - SpeciesId.WEAVILE, - SpeciesId.MAGNEZONE, - SpeciesId.LICKILICKY, - SpeciesId.RHYPERIOR, - SpeciesId.TANGROWTH, - SpeciesId.ELECTIVIRE, - SpeciesId.MAGMORTAR, - SpeciesId.TOGEKISS, - SpeciesId.YANMEGA, - SpeciesId.LEAFEON, - SpeciesId.GLACEON, - SpeciesId.GLISCOR, - SpeciesId.MAMOSWINE, - SpeciesId.PORYGON_Z, - SpeciesId.GALLADE, - SpeciesId.PROBOPASS, - SpeciesId.DUSKNOIR, - SpeciesId.FROSLASS, - SpeciesId.ROTOM, - SpeciesId.UXIE, - SpeciesId.MESPRIT, - SpeciesId.AZELF, - SpeciesId.DIALGA, - SpeciesId.PALKIA, - SpeciesId.HEATRAN, - SpeciesId.REGIGIGAS, - SpeciesId.GIRATINA, - SpeciesId.CRESSELIA, - SpeciesId.PHIONE, - SpeciesId.MANAPHY, - SpeciesId.DARKRAI, - SpeciesId.SHAYMIN, - SpeciesId.ARCEUS, - SpeciesId.VICTINI, - SpeciesId.SNIVY, - SpeciesId.SERVINE, - SpeciesId.SERPERIOR, - SpeciesId.TEPIG, - SpeciesId.PIGNITE, - SpeciesId.EMBOAR, - SpeciesId.OSHAWOTT, - SpeciesId.DEWOTT, - SpeciesId.SAMUROTT, - SpeciesId.PATRAT, - SpeciesId.WATCHOG, - SpeciesId.LILLIPUP, - SpeciesId.HERDIER, - SpeciesId.STOUTLAND, - SpeciesId.PURRLOIN, - SpeciesId.LIEPARD, - SpeciesId.PANSAGE, - SpeciesId.SIMISAGE, - SpeciesId.PANSEAR, - SpeciesId.SIMISEAR, - SpeciesId.PANPOUR, - SpeciesId.SIMIPOUR, - SpeciesId.MUNNA, - SpeciesId.MUSHARNA, - SpeciesId.PIDOVE, - SpeciesId.TRANQUILL, - SpeciesId.UNFEZANT, - SpeciesId.BLITZLE, - SpeciesId.ZEBSTRIKA, - SpeciesId.ROGGENROLA, - SpeciesId.BOLDORE, - SpeciesId.GIGALITH, - SpeciesId.WOOBAT, - SpeciesId.SWOOBAT, - SpeciesId.DRILBUR, - SpeciesId.EXCADRILL, - SpeciesId.AUDINO, - SpeciesId.TIMBURR, - SpeciesId.GURDURR, - SpeciesId.CONKELDURR, - SpeciesId.TYMPOLE, - SpeciesId.PALPITOAD, - SpeciesId.SEISMITOAD, - SpeciesId.THROH, - SpeciesId.SAWK, - SpeciesId.SEWADDLE, - SpeciesId.SWADLOON, - SpeciesId.LEAVANNY, - SpeciesId.VENIPEDE, - SpeciesId.WHIRLIPEDE, - SpeciesId.SCOLIPEDE, - SpeciesId.COTTONEE, - SpeciesId.WHIMSICOTT, - SpeciesId.PETILIL, - SpeciesId.LILLIGANT, - SpeciesId.BASCULIN, - SpeciesId.SANDILE, - SpeciesId.KROKOROK, - SpeciesId.KROOKODILE, - SpeciesId.DARUMAKA, - SpeciesId.DARMANITAN, - SpeciesId.MARACTUS, - SpeciesId.DWEBBLE, - SpeciesId.CRUSTLE, - SpeciesId.SCRAGGY, - SpeciesId.SCRAFTY, - SpeciesId.SIGILYPH, - SpeciesId.YAMASK, - SpeciesId.COFAGRIGUS, - SpeciesId.TIRTOUGA, - SpeciesId.CARRACOSTA, - SpeciesId.ARCHEN, - SpeciesId.ARCHEOPS, - SpeciesId.TRUBBISH, - SpeciesId.GARBODOR, - SpeciesId.ZORUA, - SpeciesId.ZOROARK, - SpeciesId.MINCCINO, - SpeciesId.CINCCINO, - SpeciesId.GOTHITA, - SpeciesId.GOTHORITA, - SpeciesId.GOTHITELLE, - SpeciesId.SOLOSIS, - SpeciesId.DUOSION, - SpeciesId.REUNICLUS, - SpeciesId.DUCKLETT, - SpeciesId.SWANNA, - SpeciesId.VANILLITE, - SpeciesId.VANILLISH, - SpeciesId.VANILLUXE, - SpeciesId.DEERLING, - SpeciesId.SAWSBUCK, - SpeciesId.EMOLGA, - SpeciesId.KARRABLAST, - SpeciesId.ESCAVALIER, - SpeciesId.FOONGUS, - SpeciesId.AMOONGUSS, - SpeciesId.FRILLISH, - SpeciesId.JELLICENT, - SpeciesId.ALOMOMOLA, - SpeciesId.JOLTIK, - SpeciesId.GALVANTULA, - SpeciesId.FERROSEED, - SpeciesId.FERROTHORN, - SpeciesId.KLINK, - SpeciesId.KLANG, - SpeciesId.KLINKLANG, - SpeciesId.EELEKTRIK, - SpeciesId.EELEKTROSS, - SpeciesId.ELGYEM, - SpeciesId.BEHEEYEM, - SpeciesId.LITWICK, - SpeciesId.LAMPENT, - SpeciesId.CHANDELURE, - SpeciesId.AXEW, - SpeciesId.FRAXURE, - SpeciesId.HAXORUS, - SpeciesId.CUBCHOO, - SpeciesId.BEARTIC, - SpeciesId.CRYOGONAL, - SpeciesId.SHELMET, - SpeciesId.ACCELGOR, - SpeciesId.STUNFISK, - SpeciesId.MIENFOO, - SpeciesId.MIENSHAO, - SpeciesId.DRUDDIGON, - SpeciesId.GOLETT, - SpeciesId.GOLURK, - SpeciesId.PAWNIARD, - SpeciesId.BISHARP, - SpeciesId.BOUFFALANT, - SpeciesId.RUFFLET, - SpeciesId.BRAVIARY, - SpeciesId.VULLABY, - SpeciesId.MANDIBUZZ, - SpeciesId.HEATMOR, - SpeciesId.DURANT, - SpeciesId.DEINO, - SpeciesId.ZWEILOUS, - SpeciesId.HYDREIGON, - SpeciesId.LARVESTA, - SpeciesId.VOLCARONA, - SpeciesId.COBALION, - SpeciesId.TERRAKION, - SpeciesId.VIRIZION, - SpeciesId.TORNADUS, - SpeciesId.THUNDURUS, - SpeciesId.RESHIRAM, - SpeciesId.ZEKROM, - SpeciesId.LANDORUS, - SpeciesId.KYUREM, - SpeciesId.KELDEO, - SpeciesId.MELOETTA, - SpeciesId.GENESECT, - SpeciesId.CHESPIN, - SpeciesId.QUILLADIN, - SpeciesId.CHESNAUGHT, - SpeciesId.FENNEKIN, - SpeciesId.BRAIXEN, - SpeciesId.DELPHOX, - SpeciesId.FROAKIE, - SpeciesId.FROGADIER, - SpeciesId.GRENINJA, - SpeciesId.BUNNELBY, - SpeciesId.DIGGERSBY, - SpeciesId.FLETCHLING, - SpeciesId.FLETCHINDER, - SpeciesId.TALONFLAME, - SpeciesId.VIVILLON, - SpeciesId.LITLEO, - SpeciesId.PYROAR, - SpeciesId.FLABEBE, - SpeciesId.FLOETTE, - SpeciesId.FLORGES, - SpeciesId.SKIDDO, - SpeciesId.GOGOAT, - SpeciesId.PANCHAM, - SpeciesId.PANGORO, - SpeciesId.FURFROU, - SpeciesId.ESPURR, - SpeciesId.MEOWSTIC, - SpeciesId.HONEDGE, - SpeciesId.DOUBLADE, - SpeciesId.AEGISLASH, - SpeciesId.SPRITZEE, - SpeciesId.AROMATISSE, - SpeciesId.SWIRLIX, - SpeciesId.SLURPUFF, - SpeciesId.INKAY, - SpeciesId.MALAMAR, - SpeciesId.BINACLE, - SpeciesId.BARBARACLE, - SpeciesId.SKRELP, - SpeciesId.DRAGALGE, - SpeciesId.CLAUNCHER, - SpeciesId.CLAWITZER, - SpeciesId.HELIOPTILE, - SpeciesId.HELIOLISK, - SpeciesId.TYRUNT, - SpeciesId.TYRANTRUM, - SpeciesId.AMAURA, - SpeciesId.AURORUS, - SpeciesId.SYLVEON, - SpeciesId.HAWLUCHA, - SpeciesId.DEDENNE, - SpeciesId.CARBINK, - SpeciesId.GOOMY, - SpeciesId.SLIGGOO, - SpeciesId.GOODRA, - SpeciesId.KLEFKI, - SpeciesId.PHANTUMP, - SpeciesId.TREVENANT, - SpeciesId.PUMPKABOO, - SpeciesId.GOURGEIST, - SpeciesId.BERGMITE, - SpeciesId.AVALUGG, - SpeciesId.NOIBAT, - SpeciesId.NOIVERN, - SpeciesId.XERNEAS, - SpeciesId.YVELTAL, - SpeciesId.ZYGARDE, - SpeciesId.DIANCIE, - SpeciesId.HOOPA, - SpeciesId.VOLCANION, - SpeciesId.ROWLET, - SpeciesId.DARTRIX, - SpeciesId.DECIDUEYE, - SpeciesId.LITTEN, - SpeciesId.TORRACAT, - SpeciesId.INCINEROAR, - SpeciesId.POPPLIO, - SpeciesId.BRIONNE, - SpeciesId.PRIMARINA, - SpeciesId.PIKIPEK, - SpeciesId.TRUMBEAK, - SpeciesId.TOUCANNON, - SpeciesId.YUNGOOS, - SpeciesId.GUMSHOOS, - SpeciesId.GRUBBIN, - SpeciesId.CHARJABUG, - SpeciesId.VIKAVOLT, - SpeciesId.CRABRAWLER, - SpeciesId.CRABOMINABLE, - SpeciesId.ORICORIO, - SpeciesId.CUTIEFLY, - SpeciesId.RIBOMBEE, - SpeciesId.ROCKRUFF, - SpeciesId.LYCANROC, - SpeciesId.WISHIWASHI, - SpeciesId.MAREANIE, - SpeciesId.TOXAPEX, - SpeciesId.MUDBRAY, - SpeciesId.MUDSDALE, - SpeciesId.DEWPIDER, - SpeciesId.ARAQUANID, - SpeciesId.FOMANTIS, - SpeciesId.LURANTIS, - SpeciesId.MORELULL, - SpeciesId.SHIINOTIC, - SpeciesId.SALANDIT, - SpeciesId.SALAZZLE, - SpeciesId.STUFFUL, - SpeciesId.BEWEAR, - SpeciesId.BOUNSWEET, - SpeciesId.STEENEE, - SpeciesId.TSAREENA, - SpeciesId.COMFEY, - SpeciesId.ORANGURU, - SpeciesId.PASSIMIAN, - SpeciesId.WIMPOD, - SpeciesId.GOLISOPOD, - SpeciesId.SANDYGAST, - SpeciesId.PALOSSAND, - SpeciesId.TYPE_NULL, - SpeciesId.SILVALLY, - SpeciesId.MINIOR, - SpeciesId.KOMALA, - SpeciesId.TURTONATOR, - SpeciesId.TOGEDEMARU, - SpeciesId.MIMIKYU, - SpeciesId.BRUXISH, - SpeciesId.DRAMPA, - SpeciesId.DHELMISE, - SpeciesId.JANGMO_O, - SpeciesId.HAKAMO_O, - SpeciesId.KOMMO_O, - SpeciesId.TAPU_KOKO, - SpeciesId.TAPU_LELE, - SpeciesId.TAPU_BULU, - SpeciesId.TAPU_FINI, - SpeciesId.SOLGALEO, - SpeciesId.LUNALA, - SpeciesId.NIHILEGO, - SpeciesId.BUZZWOLE, - SpeciesId.PHEROMOSA, - SpeciesId.XURKITREE, - SpeciesId.CELESTEELA, - SpeciesId.KARTANA, - SpeciesId.GUZZLORD, - SpeciesId.NECROZMA, - SpeciesId.MAGEARNA, - SpeciesId.MARSHADOW, - SpeciesId.POIPOLE, - SpeciesId.NAGANADEL, - SpeciesId.STAKATAKA, - SpeciesId.BLACEPHALON, - SpeciesId.ZERAORA, - SpeciesId.MELTAN, - SpeciesId.MELMETAL, - SpeciesId.GROOKEY, - SpeciesId.THWACKEY, - SpeciesId.RILLABOOM, - SpeciesId.SCORBUNNY, - SpeciesId.RABOOT, - SpeciesId.CINDERACE, - SpeciesId.SOBBLE, - SpeciesId.DRIZZILE, - SpeciesId.INTELEON, - SpeciesId.SKWOVET, - SpeciesId.GREEDENT, - SpeciesId.ROOKIDEE, - SpeciesId.CORVISQUIRE, - SpeciesId.CORVIKNIGHT, - SpeciesId.DOTTLER, - SpeciesId.ORBEETLE, - SpeciesId.NICKIT, - SpeciesId.THIEVUL, - SpeciesId.GOSSIFLEUR, - SpeciesId.ELDEGOSS, - SpeciesId.WOOLOO, - SpeciesId.DUBWOOL, - SpeciesId.CHEWTLE, - SpeciesId.DREDNAW, - SpeciesId.YAMPER, - SpeciesId.BOLTUND, - SpeciesId.ROLYCOLY, - SpeciesId.CARKOL, - SpeciesId.COALOSSAL, - SpeciesId.FLAPPLE, - SpeciesId.APPLETUN, - SpeciesId.SILICOBRA, - SpeciesId.SANDACONDA, - SpeciesId.CRAMORANT, - SpeciesId.ARROKUDA, - SpeciesId.BARRASKEWDA, - SpeciesId.TOXEL, - SpeciesId.TOXTRICITY, - SpeciesId.SIZZLIPEDE, - SpeciesId.CENTISKORCH, - SpeciesId.CLOBBOPUS, - SpeciesId.GRAPPLOCT, - SpeciesId.SINISTEA, - SpeciesId.POLTEAGEIST, - SpeciesId.HATENNA, - SpeciesId.HATTREM, - SpeciesId.HATTERENE, - SpeciesId.IMPIDIMP, - SpeciesId.MORGREM, - SpeciesId.GRIMMSNARL, - SpeciesId.OBSTAGOON, - SpeciesId.PERRSERKER, - SpeciesId.CURSOLA, - SpeciesId.SIRFETCHD, - SpeciesId.MR_RIME, - SpeciesId.RUNERIGUS, - SpeciesId.MILCERY, - SpeciesId.ALCREMIE, - SpeciesId.FALINKS, - SpeciesId.PINCURCHIN, - SpeciesId.SNOM, - SpeciesId.FROSMOTH, - SpeciesId.STONJOURNER, - SpeciesId.EISCUE, - SpeciesId.INDEEDEE, - SpeciesId.MORPEKO, - SpeciesId.CUFANT, - SpeciesId.COPPERAJAH, - SpeciesId.DRACOZOLT, - SpeciesId.ARCTOZOLT, - SpeciesId.DRACOVISH, - SpeciesId.ARCTOVISH, - SpeciesId.DURALUDON, - SpeciesId.DREEPY, - SpeciesId.DRAKLOAK, - SpeciesId.DRAGAPULT, - SpeciesId.ZACIAN, - SpeciesId.ZAMAZENTA, - SpeciesId.ETERNATUS, - SpeciesId.KUBFU, - SpeciesId.URSHIFU, - SpeciesId.ZARUDE, - SpeciesId.REGIELEKI, - SpeciesId.REGIDRAGO, - SpeciesId.GLASTRIER, - SpeciesId.SPECTRIER, - SpeciesId.CALYREX, - SpeciesId.WYRDEER, - SpeciesId.KLEAVOR, - SpeciesId.URSALUNA, - SpeciesId.BASCULEGION, - SpeciesId.SNEASLER, - SpeciesId.OVERQWIL, - SpeciesId.ENAMORUS, - SpeciesId.SPRIGATITO, - SpeciesId.FLORAGATO, - SpeciesId.MEOWSCARADA, - SpeciesId.FUECOCO, - SpeciesId.CROCALOR, - SpeciesId.SKELEDIRGE, - SpeciesId.QUAXLY, - SpeciesId.QUAXWELL, - SpeciesId.QUAQUAVAL, - SpeciesId.LECHONK, - SpeciesId.OINKOLOGNE, - SpeciesId.TAROUNTULA, - SpeciesId.SPIDOPS, - SpeciesId.NYMBLE, - SpeciesId.LOKIX, - SpeciesId.PAWMI, - SpeciesId.PAWMO, - SpeciesId.PAWMOT, - SpeciesId.TANDEMAUS, - SpeciesId.MAUSHOLD, - SpeciesId.FIDOUGH, - SpeciesId.DACHSBUN, - SpeciesId.SMOLIV, - SpeciesId.DOLLIV, - SpeciesId.ARBOLIVA, - SpeciesId.SQUAWKABILLY, - SpeciesId.NACLI, - SpeciesId.NACLSTACK, - SpeciesId.GARGANACL, - SpeciesId.CHARCADET, - SpeciesId.ARMAROUGE, - SpeciesId.CERULEDGE, - SpeciesId.TADBULB, - SpeciesId.BELLIBOLT, - SpeciesId.WATTREL, - SpeciesId.KILOWATTREL, - SpeciesId.MASCHIFF, - SpeciesId.MABOSSTIFF, - SpeciesId.SHROODLE, - SpeciesId.GRAFAIAI, - SpeciesId.BRAMBLIN, - SpeciesId.BRAMBLEGHAST, - SpeciesId.TOEDSCOOL, - SpeciesId.TOEDSCRUEL, - SpeciesId.KLAWF, - SpeciesId.CAPSAKID, - SpeciesId.SCOVILLAIN, - SpeciesId.RELLOR, - SpeciesId.RABSCA, - SpeciesId.FLITTLE, - SpeciesId.ESPATHRA, - SpeciesId.TINKATINK, - SpeciesId.TINKATUFF, - SpeciesId.TINKATON, - SpeciesId.WIGLETT, - SpeciesId.WUGTRIO, - SpeciesId.BOMBIRDIER, - SpeciesId.FINIZEN, - SpeciesId.PALAFIN, - SpeciesId.VAROOM, - SpeciesId.REVAVROOM, - SpeciesId.CYCLIZAR, - SpeciesId.ORTHWORM, - SpeciesId.GLIMMET, - SpeciesId.GLIMMORA, - SpeciesId.GREAVARD, - SpeciesId.HOUNDSTONE, - SpeciesId.FLAMIGO, - SpeciesId.CETODDLE, - SpeciesId.CETITAN, - SpeciesId.VELUZA, - SpeciesId.DONDOZO, - SpeciesId.TATSUGIRI, - SpeciesId.ANNIHILAPE, - SpeciesId.CLODSIRE, - SpeciesId.FARIGIRAF, - SpeciesId.DUDUNSPARCE, - SpeciesId.KINGAMBIT, - SpeciesId.GREAT_TUSK, - SpeciesId.SCREAM_TAIL, - SpeciesId.BRUTE_BONNET, - SpeciesId.FLUTTER_MANE, - SpeciesId.SLITHER_WING, - SpeciesId.SANDY_SHOCKS, - SpeciesId.IRON_TREADS, - SpeciesId.IRON_BUNDLE, - SpeciesId.IRON_HANDS, - SpeciesId.IRON_JUGULIS, - SpeciesId.IRON_MOTH, - SpeciesId.IRON_THORNS, - SpeciesId.FRIGIBAX, - SpeciesId.ARCTIBAX, - SpeciesId.BAXCALIBUR, - SpeciesId.GIMMIGHOUL, - SpeciesId.GHOLDENGO, - SpeciesId.WO_CHIEN, - SpeciesId.CHIEN_PAO, - SpeciesId.TING_LU, - SpeciesId.CHI_YU, - SpeciesId.ROARING_MOON, - SpeciesId.IRON_VALIANT, - SpeciesId.KORAIDON, - SpeciesId.MIRAIDON, - SpeciesId.WALKING_WAKE, - SpeciesId.IRON_LEAVES, - SpeciesId.DIPPLIN, - SpeciesId.POLTCHAGEIST, - SpeciesId.SINISTCHA, - SpeciesId.OKIDOGI, - SpeciesId.MUNKIDORI, - SpeciesId.FEZANDIPITI, - SpeciesId.OGERPON, - SpeciesId.ARCHALUDON, - SpeciesId.HYDRAPPLE, - SpeciesId.GOUGING_FIRE, - SpeciesId.RAGING_BOLT, - SpeciesId.IRON_BOULDER, - SpeciesId.IRON_CROWN, - SpeciesId.TERAPAGOS, - SpeciesId.PECHARUNT, - SpeciesId.ALOLA_RATTATA, - SpeciesId.ALOLA_RATICATE, - SpeciesId.ALOLA_RAICHU, - SpeciesId.ALOLA_SANDSHREW, - SpeciesId.ALOLA_SANDSLASH, - SpeciesId.ALOLA_VULPIX, - SpeciesId.ALOLA_NINETALES, - SpeciesId.ALOLA_DIGLETT, - SpeciesId.ALOLA_DUGTRIO, - SpeciesId.ALOLA_MEOWTH, - SpeciesId.ALOLA_PERSIAN, - SpeciesId.ALOLA_GEODUDE, - SpeciesId.ALOLA_GRAVELER, - SpeciesId.ALOLA_GOLEM, - SpeciesId.ALOLA_GRIMER, - SpeciesId.ALOLA_MUK, - SpeciesId.ALOLA_EXEGGUTOR, - SpeciesId.ALOLA_MAROWAK, - SpeciesId.ETERNAL_FLOETTE, - SpeciesId.GALAR_MEOWTH, - SpeciesId.GALAR_PONYTA, - SpeciesId.GALAR_RAPIDASH, - SpeciesId.GALAR_SLOWPOKE, - SpeciesId.GALAR_SLOWBRO, - SpeciesId.GALAR_FARFETCHD, - SpeciesId.GALAR_WEEZING, - SpeciesId.GALAR_MR_MIME, - SpeciesId.GALAR_ARTICUNO, - SpeciesId.GALAR_ZAPDOS, - SpeciesId.GALAR_MOLTRES, - SpeciesId.GALAR_SLOWKING, - SpeciesId.GALAR_CORSOLA, - SpeciesId.GALAR_ZIGZAGOON, - SpeciesId.GALAR_LINOONE, - SpeciesId.GALAR_DARUMAKA, - SpeciesId.GALAR_DARMANITAN, - SpeciesId.GALAR_YAMASK, - SpeciesId.GALAR_STUNFISK, - SpeciesId.HISUI_GROWLITHE, - SpeciesId.HISUI_ARCANINE, - SpeciesId.HISUI_VOLTORB, - SpeciesId.HISUI_ELECTRODE, - SpeciesId.HISUI_TYPHLOSION, - SpeciesId.HISUI_QWILFISH, - SpeciesId.HISUI_SNEASEL, - SpeciesId.HISUI_SAMUROTT, - SpeciesId.HISUI_LILLIGANT, - SpeciesId.HISUI_ZORUA, - SpeciesId.HISUI_ZOROARK, - SpeciesId.HISUI_BRAVIARY, - SpeciesId.HISUI_SLIGGOO, - SpeciesId.HISUI_GOODRA, - SpeciesId.HISUI_AVALUGG, - SpeciesId.HISUI_DECIDUEYE, - SpeciesId.PALDEA_TAUROS, - SpeciesId.PALDEA_WOOPER, - SpeciesId.BLOODMOON_URSALUNA, - ], [MoveId.SAFEGUARD]: [ SpeciesId.BULBASAUR, SpeciesId.IVYSAUR, @@ -29835,6 +28550,8 @@ export const tmSpecies = { SpeciesId.RAPIDASH, SpeciesId.FARFETCHD, SpeciesId.HYPNO, + SpeciesId.HITMONLEE, + SpeciesId.HITMONCHAN, SpeciesId.MR_MIME, SpeciesId.SCYTHER, SpeciesId.EEVEE, @@ -29865,6 +28582,7 @@ export const tmSpecies = { SpeciesId.GLIGAR, SpeciesId.SCIZOR, SpeciesId.DELIBIRD, + SpeciesId.HITMONTOP, SpeciesId.CELEBI, SpeciesId.TORCHIC, SpeciesId.COMBUSKEN, @@ -29917,12 +28635,17 @@ export const tmSpecies = { SpeciesId.PURRLOIN, SpeciesId.LIEPARD, SpeciesId.MUNNA, + SpeciesId.MUSHARNA, + SpeciesId.BLITZLE, + SpeciesId.ZEBSTRIKA, SpeciesId.WOOBAT, SpeciesId.SWOOBAT, SpeciesId.SEWADDLE, SpeciesId.SWADLOON, SpeciesId.LEAVANNY, SpeciesId.SCOLIPEDE, + SpeciesId.MINCCINO, + SpeciesId.CINCCINO, SpeciesId.DEERLING, SpeciesId.SAWSBUCK, SpeciesId.EMOLGA, @@ -29932,18 +28655,21 @@ export const tmSpecies = { SpeciesId.MIENFOO, SpeciesId.MIENSHAO, SpeciesId.DURANT, + SpeciesId.KELDEO, SpeciesId.MELOETTA, SpeciesId.FLABEBE, SpeciesId.FLOETTE, SpeciesId.FLORGES, SpeciesId.INKAY, SpeciesId.MALAMAR, + SpeciesId.MEOWSTIC, SpeciesId.SYLVEON, SpeciesId.HAWLUCHA, SpeciesId.DIANCIE, SpeciesId.ROWLET, SpeciesId.DARTRIX, SpeciesId.DECIDUEYE, + SpeciesId.INCINEROAR, SpeciesId.GRUBBIN, SpeciesId.CHARJABUG, SpeciesId.VIKAVOLT, @@ -30042,6 +28768,9 @@ export const tmSpecies = { SpeciesId.SNORLAX, SpeciesId.DRAGONITE, SpeciesId.MEW, + SpeciesId.CHIKORITA, + SpeciesId.BAYLEEF, + SpeciesId.MEGANIUM, SpeciesId.LEDYBA, SpeciesId.LEDIAN, SpeciesId.PICHU, @@ -31875,6 +30604,8 @@ export const tmSpecies = { SpeciesId.SLOWBRO, SpeciesId.MAGNEMITE, SpeciesId.MAGNETON, + SpeciesId.DODUO, + SpeciesId.DODRIO, SpeciesId.SEEL, SpeciesId.DEWGONG, SpeciesId.GRIMER, @@ -32305,6 +31036,7 @@ export const tmSpecies = { SpeciesId.ZWEILOUS, SpeciesId.HYDREIGON, SpeciesId.VOLCARONA, + SpeciesId.COBALION, SpeciesId.TORNADUS, SpeciesId.THUNDURUS, SpeciesId.RESHIRAM, @@ -32412,6 +31144,7 @@ export const tmSpecies = { SpeciesId.KOMMO_O, SpeciesId.TAPU_KOKO, SpeciesId.TAPU_FINI, + SpeciesId.LUNALA, SpeciesId.XURKITREE, SpeciesId.SOBBLE, SpeciesId.DRIZZILE, @@ -32957,6 +31690,10 @@ export const tmSpecies = { SpeciesId.PIDOVE, SpeciesId.TRANQUILL, SpeciesId.UNFEZANT, + SpeciesId.BLITZLE, + SpeciesId.ZEBSTRIKA, + SpeciesId.DRILBUR, + SpeciesId.EXCADRILL, SpeciesId.AUDINO, SpeciesId.TIMBURR, SpeciesId.GURDURR, @@ -32984,6 +31721,8 @@ export const tmSpecies = { SpeciesId.ZOROARK, SpeciesId.MINCCINO, SpeciesId.CINCCINO, + SpeciesId.SOLOSIS, + SpeciesId.REUNICLUS, SpeciesId.DEERLING, SpeciesId.SAWSBUCK, SpeciesId.FOONGUS, @@ -33000,6 +31739,8 @@ export const tmSpecies = { SpeciesId.MIENFOO, SpeciesId.MIENSHAO, SpeciesId.DRUDDIGON, + SpeciesId.GOLETT, + SpeciesId.GOLURK, SpeciesId.BOUFFALANT, SpeciesId.RUFFLET, SpeciesId.BRAVIARY, @@ -33011,10 +31752,12 @@ export const tmSpecies = { SpeciesId.HYDREIGON, SpeciesId.LARVESTA, SpeciesId.VOLCARONA, + SpeciesId.COBALION, SpeciesId.VIRIZION, SpeciesId.TORNADUS, SpeciesId.THUNDURUS, SpeciesId.RESHIRAM, + SpeciesId.ZEKROM, SpeciesId.LANDORUS, SpeciesId.KYUREM, SpeciesId.KELDEO, @@ -33122,6 +31865,7 @@ export const tmSpecies = { SpeciesId.SOLGALEO, SpeciesId.LUNALA, SpeciesId.XURKITREE, + SpeciesId.NECROZMA, SpeciesId.MAGEARNA, SpeciesId.BLACEPHALON, SpeciesId.GROOKEY, @@ -33539,6 +32283,8 @@ export const tmSpecies = { SpeciesId.GOLDUCK, SpeciesId.MANKEY, SpeciesId.PRIMEAPE, + SpeciesId.POLIWHIRL, + SpeciesId.POLIWRATH, SpeciesId.ABRA, SpeciesId.KADABRA, SpeciesId.ALAKAZAM, @@ -33575,6 +32321,7 @@ export const tmSpecies = { SpeciesId.NATU, SpeciesId.XATU, SpeciesId.SUDOWOODO, + SpeciesId.POLITOED, SpeciesId.HOPPIP, SpeciesId.SKIPLOOM, SpeciesId.JUMPLUFF, @@ -33654,6 +32401,12 @@ export const tmSpecies = { SpeciesId.RAYQUAZA, SpeciesId.JIRACHI, SpeciesId.DEOXYS, + SpeciesId.CHIMCHAR, + SpeciesId.MONFERNO, + SpeciesId.INFERNAPE, + SpeciesId.PIPLUP, + SpeciesId.PRINPLUP, + SpeciesId.EMPOLEON, SpeciesId.BUDEW, SpeciesId.ROSERADE, SpeciesId.WORMADAM, @@ -33671,6 +32424,8 @@ export const tmSpecies = { SpeciesId.MIME_JR, SpeciesId.HAPPINY, SpeciesId.SPIRITOMB, + SpeciesId.RIOLU, + SpeciesId.LUCARIO, SpeciesId.FINNEON, SpeciesId.LUMINEON, SpeciesId.WEAVILE, @@ -33707,6 +32462,7 @@ export const tmSpecies = { SpeciesId.WOOBAT, SpeciesId.SWOOBAT, SpeciesId.AUDINO, + SpeciesId.LILLIGANT, SpeciesId.SIGILYPH, SpeciesId.YAMASK, SpeciesId.COFAGRIGUS, @@ -33728,6 +32484,8 @@ export const tmSpecies = { SpeciesId.CHANDELURE, SpeciesId.MIENFOO, SpeciesId.MIENSHAO, + SpeciesId.GOLETT, + SpeciesId.GOLURK, SpeciesId.VULLABY, SpeciesId.MANDIBUZZ, SpeciesId.DEINO, @@ -33756,11 +32514,14 @@ export const tmSpecies = { SpeciesId.AURORUS, SpeciesId.SYLVEON, SpeciesId.CARBINK, + SpeciesId.PHANTUMP, + SpeciesId.TREVENANT, SpeciesId.KLEFKI, SpeciesId.XERNEAS, SpeciesId.DIANCIE, SpeciesId.HOOPA, SpeciesId.PRIMARINA, + SpeciesId.TOUCANNON, SpeciesId.CUTIEFLY, SpeciesId.RIBOMBEE, SpeciesId.COMFEY, @@ -33768,6 +32529,7 @@ export const tmSpecies = { SpeciesId.GOLISOPOD, SpeciesId.PYUKUMUKU, SpeciesId.MINIOR, + SpeciesId.ORICORIO, SpeciesId.KOMALA, SpeciesId.MIMIKYU, SpeciesId.DRAMPA, @@ -33778,8 +32540,39 @@ export const tmSpecies = { SpeciesId.SOLGALEO, SpeciesId.LUNALA, SpeciesId.MARSHADOW, + SpeciesId.INTELEON, + SpeciesId.HATENNA, + SpeciesId.HATTREM, + SpeciesId.HATTERENE, + SpeciesId.FALINKS, + SpeciesId.STONJOURNER, SpeciesId.INDEEDEE, + SpeciesId.CALYREX, + SpeciesId.WYRDEER, + SpeciesId.ENAMORUS, + SpeciesId.MEOWSCARADA, + SpeciesId.QUAXLY, + SpeciesId.QUAXWELL, + SpeciesId.QUAQUAVAL, + SpeciesId.FIDOUGH, + SpeciesId.DACHSBUN, + SpeciesId.ARBOLIVA, + SpeciesId.ARMAROUGE, + SpeciesId.CERULEDGE, + SpeciesId.GRAFAIAI, + SpeciesId.BOMBIRDIER, SpeciesId.RABSCA, + SpeciesId.FINIZEN, + SpeciesId.PALAFIN, + SpeciesId.FLAMIGO, + SpeciesId.FARIGIRAF, + SpeciesId.DUDUNSPARCE, + SpeciesId.SCREAM_TAIL, + SpeciesId.IRON_VALIANT, + SpeciesId.POLTCHAGEIST, + SpeciesId.SINISTCHA, + SpeciesId.MUNKIDORI, + SpeciesId.FEZANDIPITI, SpeciesId.ALOLA_VULPIX, SpeciesId.ALOLA_NINETALES, SpeciesId.ALOLA_MEOWTH, @@ -33788,6 +32581,10 @@ export const tmSpecies = { SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWKING, + SpeciesId.HISUI_LILLIGANT, + SpeciesId.HISUI_ZORUA, + SpeciesId.HISUI_ZOROARK, + SpeciesId.HISUI_BRAVIARY, ], [MoveId.SHADOW_BALL]: [ SpeciesId.BUTTERFREE, @@ -34068,6 +32865,7 @@ export const tmSpecies = { SpeciesId.CURSOLA, SpeciesId.MR_RIME, SpeciesId.RUNERIGUS, + SpeciesId.ALCREMIE, SpeciesId.INDEEDEE, SpeciesId.DRAKLOAK, SpeciesId.DRAGAPULT, @@ -34131,6 +32929,7 @@ export const tmSpecies = { SpeciesId.HISUI_BRAVIARY, ], [MoveId.FUTURE_SIGHT]: [ + SpeciesId.CLEFABLE, SpeciesId.PSYDUCK, SpeciesId.GOLDUCK, SpeciesId.KADABRA, @@ -34154,6 +32953,7 @@ export const tmSpecies = { SpeciesId.AZUMARILL, SpeciesId.ESPEON, SpeciesId.SLOWKING, + SpeciesId.MISDREAVUS, SpeciesId.GIRAFARIG, SpeciesId.DELIBIRD, SpeciesId.LUGIA, @@ -34177,6 +32977,8 @@ export const tmSpecies = { SpeciesId.LATIAS, SpeciesId.LATIOS, SpeciesId.JIRACHI, + SpeciesId.DEOXYS, + SpeciesId.MISMAGIUS, SpeciesId.CHINGLING, SpeciesId.BRONZOR, SpeciesId.BRONZONG, @@ -34207,6 +33009,7 @@ export const tmSpecies = { SpeciesId.INKAY, SpeciesId.MALAMAR, SpeciesId.ORANGURU, + SpeciesId.HOOPA, SpeciesId.TAPU_LELE, SpeciesId.SOLGALEO, SpeciesId.LUNALA, @@ -34217,6 +33020,8 @@ export const tmSpecies = { SpeciesId.MR_RIME, SpeciesId.INDEEDEE, SpeciesId.CALYREX, + SpeciesId.WYRDEER, + SpeciesId.RABSCA, SpeciesId.IRON_VALIANT, SpeciesId.MUNKIDORI, SpeciesId.IRON_CROWN, @@ -34232,6 +33037,7 @@ export const tmSpecies = { SpeciesId.GALAR_MR_MIME, SpeciesId.GALAR_ARTICUNO, SpeciesId.GALAR_SLOWKING, + SpeciesId.HISUI_BRAVIARY, ], [MoveId.ROCK_SMASH]: [ SpeciesId.BULBASAUR, @@ -35620,6 +34426,14 @@ export const tmSpecies = { SpeciesId.ARCTOZOLT, SpeciesId.ARCTOVISH, SpeciesId.GLASTRIER, + SpeciesId.BASCULEGION, + SpeciesId.CETODDLE, + SpeciesId.CETITAN, + SpeciesId.IRON_BUNDLE, + SpeciesId.FRIGIBAX, + SpeciesId.ARCTIBAX, + SpeciesId.BAXCALIBUR, + SpeciesId.CHIEN_PAO, SpeciesId.ALOLA_SANDSHREW, SpeciesId.ALOLA_SANDSLASH, SpeciesId.ALOLA_VULPIX, @@ -35629,6 +34443,7 @@ export const tmSpecies = { SpeciesId.GALAR_MR_MIME, SpeciesId.GALAR_SLOWKING, SpeciesId.GALAR_CORSOLA, + SpeciesId.HISUI_AVALUGG, [ SpeciesId.CALYREX, "ice", @@ -36696,6 +35511,9 @@ export const tmSpecies = { SpeciesId.POPPLIO, SpeciesId.BRIONNE, SpeciesId.PRIMARINA, + SpeciesId.PIKIPEK, + SpeciesId.TRUMBEAK, + SpeciesId.TOUCANNON, SpeciesId.YUNGOOS, SpeciesId.GUMSHOOS, SpeciesId.GRUBBIN, @@ -37529,6 +36347,8 @@ export const tmSpecies = { SpeciesId.HYPNO, SpeciesId.VOLTORB, SpeciesId.ELECTRODE, + SpeciesId.HITMONLEE, + SpeciesId.HITMONCHAN, SpeciesId.KOFFING, SpeciesId.WEEZING, SpeciesId.MR_MIME, @@ -37654,6 +36474,7 @@ export const tmSpecies = { SpeciesId.PIDOVE, SpeciesId.TRANQUILL, SpeciesId.UNFEZANT, + SpeciesId.ZEBSTRIKA, SpeciesId.WOOBAT, SpeciesId.SWOOBAT, SpeciesId.TIMBURR, @@ -37904,6 +36725,9 @@ export const tmSpecies = { SpeciesId.NINETALES, SpeciesId.JIGGLYPUFF, SpeciesId.WIGGLYTUFF, + SpeciesId.ODDISH, + SpeciesId.GLOOM, + SpeciesId.VILEPLUME, SpeciesId.DIGLETT, SpeciesId.MEOWTH, SpeciesId.PERSIAN, @@ -37924,6 +36748,8 @@ export const tmSpecies = { SpeciesId.MAGNEMITE, SpeciesId.MAGNETON, SpeciesId.FARFETCHD, + SpeciesId.DODUO, + SpeciesId.DODRIO, SpeciesId.SEEL, SpeciesId.DEWGONG, SpeciesId.GRIMER, @@ -37934,6 +36760,8 @@ export const tmSpecies = { SpeciesId.HYPNO, SpeciesId.VOLTORB, SpeciesId.ELECTRODE, + SpeciesId.EXEGGCUTE, + SpeciesId.EXEGGUTOR, SpeciesId.HITMONLEE, SpeciesId.HITMONCHAN, SpeciesId.LICKITUNG, @@ -37962,7 +36790,11 @@ export const tmSpecies = { SpeciesId.DRAGONITE, SpeciesId.MEWTWO, SpeciesId.MEW, + SpeciesId.CHIKORITA, + SpeciesId.BAYLEEF, + SpeciesId.MEGANIUM, SpeciesId.TYPHLOSION, + SpeciesId.FERALIGATR, SpeciesId.SENTRET, SpeciesId.FURRET, SpeciesId.PICHU, @@ -37993,6 +36825,8 @@ export const tmSpecies = { SpeciesId.PINECO, SpeciesId.FORRETRESS, SpeciesId.DUNSPARCE, + SpeciesId.SNUBBULL, + SpeciesId.GRANBULL, SpeciesId.SCIZOR, SpeciesId.SHUCKLE, SpeciesId.HERACROSS, @@ -38024,9 +36858,14 @@ export const tmSpecies = { SpeciesId.LUGIA, SpeciesId.HO_OH, SpeciesId.CELEBI, + SpeciesId.TREECKO, + SpeciesId.GROVYLE, + SpeciesId.SCEPTILE, SpeciesId.TORCHIC, SpeciesId.COMBUSKEN, SpeciesId.BLAZIKEN, + SpeciesId.MARSHTOMP, + SpeciesId.SWAMPERT, SpeciesId.POOCHYENA, SpeciesId.MIGHTYENA, SpeciesId.ZIGZAGOON, @@ -38065,6 +36904,7 @@ export const tmSpecies = { SpeciesId.SPOINK, SpeciesId.GRUMPIG, SpeciesId.SPINDA, + SpeciesId.FLYGON, SpeciesId.CACNEA, SpeciesId.CACTURNE, SpeciesId.SWABLU, @@ -38166,6 +37006,9 @@ export const tmSpecies = { SpeciesId.PHIONE, SpeciesId.MANAPHY, SpeciesId.VICTINI, + SpeciesId.SNIVY, + SpeciesId.SERVINE, + SpeciesId.SERPERIOR, SpeciesId.TEPIG, SpeciesId.PIGNITE, SpeciesId.EMBOAR, @@ -38185,8 +37028,12 @@ export const tmSpecies = { SpeciesId.SIMIPOUR, SpeciesId.MUNNA, SpeciesId.MUSHARNA, + SpeciesId.BLITZLE, + SpeciesId.ZEBSTRIKA, SpeciesId.WOOBAT, SpeciesId.SWOOBAT, + SpeciesId.DRILBUR, + SpeciesId.EXCADRILL, SpeciesId.AUDINO, SpeciesId.TIMBURR, SpeciesId.GURDURR, @@ -38202,6 +37049,8 @@ export const tmSpecies = { SpeciesId.KROKOROK, SpeciesId.KROOKODILE, SpeciesId.MARACTUS, + SpeciesId.SCRAGGY, + SpeciesId.SCRAFTY, SpeciesId.ZORUA, SpeciesId.ZOROARK, SpeciesId.MINCCINO, @@ -38218,6 +37067,8 @@ export const tmSpecies = { SpeciesId.SAWSBUCK, SpeciesId.EMOLGA, SpeciesId.ALOMOMOLA, + SpeciesId.JOLTIK, + SpeciesId.GALVANTULA, SpeciesId.MIENFOO, SpeciesId.MIENSHAO, SpeciesId.GOLETT, @@ -38258,6 +37109,8 @@ export const tmSpecies = { SpeciesId.MEOWSTIC, SpeciesId.SPRITZEE, SpeciesId.AROMATISSE, + SpeciesId.INKAY, + SpeciesId.MALAMAR, SpeciesId.SWIRLIX, SpeciesId.SLURPUFF, SpeciesId.BINACLE, @@ -38271,9 +37124,15 @@ export const tmSpecies = { SpeciesId.ROWLET, SpeciesId.DARTRIX, SpeciesId.DECIDUEYE, + SpeciesId.LITTEN, + SpeciesId.TORRACAT, + SpeciesId.INCINEROAR, SpeciesId.POPPLIO, SpeciesId.BRIONNE, SpeciesId.PRIMARINA, + SpeciesId.PIKIPEK, + SpeciesId.TRUMBEAK, + SpeciesId.TOUCANNON, SpeciesId.YUNGOOS, SpeciesId.GUMSHOOS, SpeciesId.CRABRAWLER, @@ -38412,6 +37271,7 @@ export const tmSpecies = { SpeciesId.ALOLA_PERSIAN, SpeciesId.ALOLA_GRIMER, SpeciesId.ALOLA_MUK, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.ETERNAL_FLOETTE, SpeciesId.GALAR_MEOWTH, SpeciesId.GALAR_SLOWPOKE, @@ -38446,6 +37306,8 @@ export const tmSpecies = { SpeciesId.GENGAR, SpeciesId.DROWZEE, SpeciesId.HYPNO, + SpeciesId.EXEGGCUTE, + SpeciesId.EXEGGUTOR, SpeciesId.STARMIE, SpeciesId.MR_MIME, SpeciesId.JYNX, @@ -38554,6 +37416,8 @@ export const tmSpecies = { SpeciesId.FLORGES, SpeciesId.ESPURR, SpeciesId.MEOWSTIC, + SpeciesId.INKAY, + SpeciesId.MALAMAR, SpeciesId.PHANTUMP, SpeciesId.TREVENANT, SpeciesId.PUMPKABOO, @@ -38598,6 +37462,7 @@ export const tmSpecies = { SpeciesId.GHOLDENGO, SpeciesId.IRON_VALIANT, SpeciesId.MUNKIDORI, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.ETERNAL_FLOETTE, SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWBRO, @@ -39283,6 +38148,7 @@ export const tmSpecies = { SpeciesId.TORNADUS, SpeciesId.THUNDURUS, SpeciesId.LANDORUS, + SpeciesId.ZEKROM, SpeciesId.KELDEO, SpeciesId.MELOETTA, SpeciesId.CHESPIN, @@ -40240,6 +39106,8 @@ export const tmSpecies = { SpeciesId.GENGAR, SpeciesId.DROWZEE, SpeciesId.HYPNO, + SpeciesId.EXEGGCUTE, + SpeciesId.EXEGGUTOR, SpeciesId.MEWTWO, SpeciesId.MEW, SpeciesId.HOOTHOOT, @@ -40273,6 +39141,7 @@ export const tmSpecies = { SpeciesId.DUSCLOPS, SpeciesId.CHIMECHO, SpeciesId.JIRACHI, + SpeciesId.DEOXYS, SpeciesId.DRIFLOON, SpeciesId.DRIFBLIM, SpeciesId.MISMAGIUS, @@ -40374,6 +39243,7 @@ export const tmSpecies = { SpeciesId.ALOLA_NINETALES, SpeciesId.ALOLA_GRIMER, SpeciesId.ALOLA_MUK, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.ALOLA_MAROWAK, SpeciesId.GALAR_PONYTA, SpeciesId.GALAR_RAPIDASH, @@ -41768,6 +40638,7 @@ export const tmSpecies = { SpeciesId.CHATOT, SpeciesId.MUNCHLAX, SpeciesId.HIPPOWDON, + SpeciesId.MAGMORTAR, SpeciesId.TOGEKISS, SpeciesId.LEAFEON, SpeciesId.GLACEON, @@ -43466,6 +42337,9 @@ export const tmSpecies = { SpeciesId.LEAFEON, SpeciesId.SHAYMIN, SpeciesId.ARCEUS, + SpeciesId.SNIVY, + SpeciesId.SERVINE, + SpeciesId.SERPERIOR, SpeciesId.PATRAT, SpeciesId.WATCHOG, SpeciesId.PANSAGE, @@ -43482,6 +42356,7 @@ export const tmSpecies = { SpeciesId.AMOONGUSS, SpeciesId.FERROSEED, SpeciesId.FERROTHORN, + SpeciesId.VIRIZION, SpeciesId.CHESPIN, SpeciesId.QUILLADIN, SpeciesId.CHESNAUGHT, @@ -43789,6 +42664,7 @@ export const tmSpecies = { SpeciesId.TERRAKION, SpeciesId.VIRIZION, SpeciesId.TORNADUS, + SpeciesId.KYUREM, SpeciesId.KELDEO, SpeciesId.GENESECT, SpeciesId.CHESPIN, @@ -43821,6 +42697,7 @@ export const tmSpecies = { SpeciesId.ROWLET, SpeciesId.DARTRIX, SpeciesId.DECIDUEYE, + SpeciesId.INCINEROAR, SpeciesId.PIKIPEK, SpeciesId.TRUMBEAK, SpeciesId.TOUCANNON, @@ -44250,6 +43127,7 @@ export const tmSpecies = { SpeciesId.LATIOS, SpeciesId.GROUDON, SpeciesId.RAYQUAZA, + SpeciesId.RAMPARDOS, SpeciesId.GIBLE, SpeciesId.GABITE, SpeciesId.GARCHOMP, @@ -45471,6 +44349,7 @@ export const tmSpecies = { SpeciesId.TERAPAGOS, SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_NINETALES, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.ETERNAL_FLOETTE, SpeciesId.GALAR_PONYTA, SpeciesId.GALAR_RAPIDASH, @@ -45640,6 +44519,8 @@ export const tmSpecies = { SpeciesId.REGIROCK, SpeciesId.GROUDON, SpeciesId.TORTERRA, + SpeciesId.CRANIDOS, + SpeciesId.RAMPARDOS, SpeciesId.SHIELDON, SpeciesId.BASTIODON, SpeciesId.GASTRODON, @@ -46512,6 +45393,8 @@ export const tmSpecies = { SpeciesId.GENESECT, SpeciesId.ESPURR, SpeciesId.MEOWSTIC, + SpeciesId.INKAY, + SpeciesId.MALAMAR, SpeciesId.CARBINK, SpeciesId.XERNEAS, SpeciesId.DIANCIE, @@ -46896,6 +45779,8 @@ export const tmSpecies = { SpeciesId.GOLBAT, SpeciesId.VENOMOTH, SpeciesId.FARFETCHD, + SpeciesId.DODUO, + SpeciesId.DODRIO, SpeciesId.SCYTHER, SpeciesId.AERODACTYL, SpeciesId.ARTICUNO, @@ -48948,6 +47833,9 @@ export const tmSpecies = { SpeciesId.ARCEUS, SpeciesId.MIENFOO, SpeciesId.MIENSHAO, + SpeciesId.COBALION, + SpeciesId.TERRAKION, + SpeciesId.VIRIZION, SpeciesId.KELDEO, SpeciesId.CLAUNCHER, SpeciesId.CLAWITZER, @@ -49080,6 +47968,7 @@ export const tmSpecies = { SpeciesId.ALOLA_MUK, ], [MoveId.POISON_JAB]: [ + SpeciesId.VENUSAUR, SpeciesId.BEEDRILL, SpeciesId.EKANS, SpeciesId.ARBOK, @@ -49111,10 +48000,12 @@ export const tmSpecies = { SpeciesId.HAUNTER, SpeciesId.GENGAR, SpeciesId.HITMONLEE, + SpeciesId.HITMONCHAN, SpeciesId.RHYHORN, SpeciesId.RHYDON, SpeciesId.GOLDEEN, SpeciesId.SEAKING, + SpeciesId.MAGMAR, SpeciesId.MEWTWO, SpeciesId.MEW, SpeciesId.SPINARAK, @@ -49126,8 +48017,10 @@ export const tmSpecies = { SpeciesId.QWILFISH, SpeciesId.SNEASEL, SpeciesId.DONPHAN, + SpeciesId.MAGBY, SpeciesId.COMBUSKEN, SpeciesId.BLAZIKEN, + SpeciesId.SWAMPERT, SpeciesId.BRELOOM, SpeciesId.SLAKOTH, SpeciesId.VIGOROTH, @@ -49162,6 +48055,7 @@ export const tmSpecies = { SpeciesId.WEAVILE, SpeciesId.RHYPERIOR, SpeciesId.TANGROWTH, + SpeciesId.MAGMORTAR, SpeciesId.GLISCOR, SpeciesId.GALLADE, SpeciesId.DARKRAI, @@ -49300,6 +48194,7 @@ export const tmSpecies = { SpeciesId.MISDREAVUS, SpeciesId.GLIGAR, SpeciesId.STEELIX, + SpeciesId.QWILFISH, SpeciesId.SNEASEL, SpeciesId.SKARMORY, SpeciesId.HOUNDOUR, @@ -50329,6 +49224,9 @@ export const tmSpecies = { SpeciesId.SPOINK, SpeciesId.GRUMPIG, SpeciesId.LUNATONE, + SpeciesId.REGIROCK, + SpeciesId.SHIELDON, + SpeciesId.BASTIODON, SpeciesId.VESPIQUEN, SpeciesId.MISMAGIUS, SpeciesId.BRONZOR, @@ -51410,6 +50308,8 @@ export const tmSpecies = { SpeciesId.ELECTRODE, SpeciesId.EXEGGUTOR, SpeciesId.MAROWAK, + SpeciesId.HITMONLEE, + SpeciesId.HITMONCHAN, SpeciesId.LICKITUNG, SpeciesId.WEEZING, SpeciesId.RHYDON, @@ -51483,11 +50383,13 @@ export const tmSpecies = { SpeciesId.OCTILLERY, SpeciesId.DELIBIRD, SpeciesId.MANTINE, + SpeciesId.SKARMORY, SpeciesId.HOUNDOOM, SpeciesId.KINGDRA, SpeciesId.DONPHAN, SpeciesId.PORYGON2, SpeciesId.STANTLER, + SpeciesId.HITMONTOP, SpeciesId.MILTANK, SpeciesId.BLISSEY, SpeciesId.RAIKOU, @@ -51754,6 +50656,7 @@ export const tmSpecies = { SpeciesId.DECIDUEYE, SpeciesId.INCINEROAR, SpeciesId.PRIMARINA, + SpeciesId.TOUCANNON, SpeciesId.GUMSHOOS, SpeciesId.VIKAVOLT, SpeciesId.CRABOMINABLE, @@ -51761,6 +50664,7 @@ export const tmSpecies = { SpeciesId.LYCANROC, SpeciesId.TOXAPEX, SpeciesId.MUDSDALE, + SpeciesId.ARAQUANID, SpeciesId.LURANTIS, SpeciesId.SHIINOTIC, SpeciesId.SALAZZLE, @@ -52236,6 +51140,7 @@ export const tmSpecies = { SpeciesId.CUBCHOO, SpeciesId.BEARTIC, SpeciesId.CRYOGONAL, + SpeciesId.KYUREM, SpeciesId.AMAURA, SpeciesId.AURORUS, SpeciesId.BERGMITE, @@ -52648,6 +51553,7 @@ export const tmSpecies = { SpeciesId.DEINO, SpeciesId.ZWEILOUS, SpeciesId.HYDREIGON, + SpeciesId.KYUREM, SpeciesId.TYRUNT, SpeciesId.TYRANTRUM, SpeciesId.BERGMITE, @@ -54084,6 +52990,8 @@ export const tmSpecies = { SpeciesId.PERSIAN, SpeciesId.MANKEY, SpeciesId.PRIMEAPE, + SpeciesId.TENTACOOL, + SpeciesId.TENTACRUEL, SpeciesId.GRIMER, SpeciesId.MUK, SpeciesId.GASTLY, @@ -56247,6 +55155,7 @@ export const tmSpecies = { SpeciesId.LARVITAR, SpeciesId.PUPITAR, SpeciesId.TYRANITAR, + SpeciesId.SWAMPERT, SpeciesId.SLAKING, SpeciesId.LOUDRED, SpeciesId.EXPLOUD, @@ -56301,6 +55210,8 @@ export const tmSpecies = { SpeciesId.ARCHEOPS, SpeciesId.GARBODOR, SpeciesId.DRUDDIGON, + SpeciesId.GOLETT, + SpeciesId.GOLURK, SpeciesId.TERRAKION, SpeciesId.TORNADUS, SpeciesId.THUNDURUS, @@ -56808,6 +55719,7 @@ export const tmSpecies = { SpeciesId.SUDOWOODO, SpeciesId.POLITOED, SpeciesId.AIPOM, + SpeciesId.GRANBULL, SpeciesId.SNEASEL, SpeciesId.TYROGUE, SpeciesId.HITMONTOP, @@ -57085,6 +55997,7 @@ export const tmSpecies = { SpeciesId.BANETTE, SpeciesId.ABSOL, SpeciesId.GLALIE, + SpeciesId.BASTIODON, SpeciesId.AMBIPOM, SpeciesId.MISMAGIUS, SpeciesId.HONCHKROW, @@ -58908,6 +57821,8 @@ export const tmSpecies = { SpeciesId.LITWICK, SpeciesId.LAMPENT, SpeciesId.CHANDELURE, + SpeciesId.GOLETT, + SpeciesId.GOLURK, SpeciesId.DELPHOX, SpeciesId.PHANTUMP, SpeciesId.TREVENANT, @@ -59255,6 +58170,8 @@ export const tmSpecies = { SpeciesId.MANKEY, SpeciesId.PRIMEAPE, SpeciesId.FARFETCHD, + SpeciesId.DODUO, + SpeciesId.DODRIO, SpeciesId.SCYTHER, SpeciesId.ZAPDOS, SpeciesId.MOLTRES, @@ -59273,6 +58190,7 @@ export const tmSpecies = { SpeciesId.SCIZOR, SpeciesId.DELIBIRD, SpeciesId.MANTINE, + SpeciesId.LUGIA, SpeciesId.TREECKO, SpeciesId.GROVYLE, SpeciesId.SCEPTILE, @@ -59357,6 +58275,9 @@ export const tmSpecies = { SpeciesId.POPPLIO, SpeciesId.BRIONNE, SpeciesId.PRIMARINA, + SpeciesId.PIKIPEK, + SpeciesId.TRUMBEAK, + SpeciesId.TOUCANNON, SpeciesId.GRUBBIN, SpeciesId.CHARJABUG, SpeciesId.VIKAVOLT, @@ -60106,6 +59027,7 @@ export const tmSpecies = { SpeciesId.PIGNITE, SpeciesId.EMBOAR, SpeciesId.SAMUROTT, + SpeciesId.ZEBSTRIKA, SpeciesId.ROGGENROLA, SpeciesId.BOLDORE, SpeciesId.GIGALITH, @@ -60353,6 +59275,9 @@ export const tmSpecies = { SpeciesId.STEELIX, SpeciesId.TYRANITAR, SpeciesId.LUGIA, + SpeciesId.TREECKO, + SpeciesId.GROVYLE, + SpeciesId.SCEPTILE, SpeciesId.AGGRON, SpeciesId.VIBRAVA, SpeciesId.FLYGON, @@ -61143,8 +60068,10 @@ export const tmSpecies = { SpeciesId.CROBAT, SpeciesId.MURKROW, SpeciesId.MANTINE, + SpeciesId.SKARMORY, SpeciesId.KINGDRA, SpeciesId.LUGIA, + SpeciesId.HO_OH, SpeciesId.SHIFTRY, SpeciesId.TAILLOW, SpeciesId.WINGULL, @@ -61179,6 +60106,7 @@ export const tmSpecies = { SpeciesId.NOIVERN, SpeciesId.YVELTAL, SpeciesId.DECIDUEYE, + SpeciesId.TOUCANNON, SpeciesId.ORICORIO, SpeciesId.DRAMPA, SpeciesId.CORVISQUIRE, @@ -61215,6 +60143,7 @@ export const tmSpecies = { SpeciesId.GROWLITHE, SpeciesId.ARCANINE, SpeciesId.MEW, + SpeciesId.FERALIGATR, SpeciesId.UMBREON, SpeciesId.MURKROW, SpeciesId.SNUBBULL, @@ -63682,10 +62611,15 @@ export const tmSpecies = { SpeciesId.BLOODMOON_URSALUNA, ], [MoveId.SOLAR_BLADE]: [ + SpeciesId.VILEPLUME, SpeciesId.PONYTA, SpeciesId.RAPIDASH, SpeciesId.FARFETCHD, SpeciesId.MEW, + SpeciesId.CHIKORITA, + SpeciesId.BAYLEEF, + SpeciesId.MEGANIUM, + SpeciesId.BELLOSSOM, SpeciesId.CELEBI, SpeciesId.GROVYLE, SpeciesId.SCEPTILE, @@ -63736,13 +62670,19 @@ export const tmSpecies = { SpeciesId.NIDOQUEEN, SpeciesId.NIDOKING, SpeciesId.PARASECT, + SpeciesId.DIGLETT, + SpeciesId.DUGTRIO, SpeciesId.MEOWTH, SpeciesId.PERSIAN, SpeciesId.PRIMEAPE, SpeciesId.POLIWRATH, SpeciesId.MACHAMP, + SpeciesId.TENTACOOL, + SpeciesId.TENTACRUEL, SpeciesId.RAPIDASH, SpeciesId.FARFETCHD, + SpeciesId.DODUO, + SpeciesId.DODRIO, SpeciesId.MAROWAK, SpeciesId.HITMONLEE, SpeciesId.HITMONCHAN, @@ -63761,6 +62701,7 @@ export const tmSpecies = { SpeciesId.URSARING, SpeciesId.CORSOLA, SpeciesId.HOUNDOOM, + SpeciesId.DONPHAN, SpeciesId.STANTLER, SpeciesId.RAIKOU, SpeciesId.SCEPTILE, @@ -63771,16 +62712,21 @@ export const tmSpecies = { SpeciesId.VIGOROTH, SpeciesId.SLAKING, SpeciesId.HARIYAMA, + SpeciesId.SABLEYE, SpeciesId.VIBRAVA, SpeciesId.FLYGON, + SpeciesId.CACNEA, + SpeciesId.CACTURNE, SpeciesId.ZANGOOSE, SpeciesId.SEVIPER, SpeciesId.BANETTE, SpeciesId.ABSOL, SpeciesId.DEOXYS, + SpeciesId.INFERNAPE, SpeciesId.EMPOLEON, SpeciesId.KRICKETUNE, SpeciesId.LUXRAY, + SpeciesId.AMBIPOM, SpeciesId.PURUGLY, SpeciesId.STUNKY, SpeciesId.SKUNTANK, @@ -63789,6 +62735,7 @@ export const tmSpecies = { SpeciesId.CARNIVINE, SpeciesId.WEAVILE, SpeciesId.GLISCOR, + SpeciesId.MAMOSWINE, SpeciesId.GALLADE, SpeciesId.DARKRAI, SpeciesId.LIEPARD, @@ -63796,6 +62743,7 @@ export const tmSpecies = { SpeciesId.SIMISEAR, SpeciesId.SIMIPOUR, SpeciesId.GIGALITH, + SpeciesId.EXCADRILL, SpeciesId.AUDINO, SpeciesId.SAWK, SpeciesId.LEAVANNY, @@ -63804,20 +62752,27 @@ export const tmSpecies = { SpeciesId.MARACTUS, SpeciesId.SCRAFTY, SpeciesId.ZOROARK, + SpeciesId.SAWSBUCK, SpeciesId.GALVANTULA, SpeciesId.EELEKTRIK, SpeciesId.EELEKTROSS, SpeciesId.BEARTIC, SpeciesId.BISHARP, SpeciesId.BOUFFALANT, + SpeciesId.VULLABY, + SpeciesId.MANDIBUZZ, SpeciesId.HEATMOR, SpeciesId.HYDREIGON, + SpeciesId.GOGOAT, SpeciesId.PANGORO, SpeciesId.MALAMAR, SpeciesId.HAWLUCHA, SpeciesId.HOOPA, SpeciesId.INCINEROAR, + SpeciesId.TOUCANNON, SpeciesId.GOLISOPOD, + SpeciesId.HAKAMO_O, + SpeciesId.KOMMO_O, SpeciesId.PHEROMOSA, SpeciesId.MARSHADOW, SpeciesId.NAGANADEL, @@ -63837,26 +62792,46 @@ export const tmSpecies = { SpeciesId.PINCURCHIN, SpeciesId.ZARUDE, SpeciesId.GLASTRIER, + SpeciesId.WYRDEER, + SpeciesId.URSALUNA, + SpeciesId.SNEASLER, + SpeciesId.OVERQWIL, + SpeciesId.MEOWSCARADA, SpeciesId.TAROUNTULA, SpeciesId.SPIDOPS, SpeciesId.LOKIX, + SpeciesId.PAWMOT, + SpeciesId.CERULEDGE, + SpeciesId.GRAFAIAI, + SpeciesId.KLAWF, SpeciesId.WIGLETT, SpeciesId.WUGTRIO, + SpeciesId.PALAFIN, SpeciesId.FLAMIGO, + SpeciesId.ANNIHILAPE, + SpeciesId.DUDUNSPARCE, + SpeciesId.KINGAMBIT, + SpeciesId.GREAT_TUSK, + SpeciesId.IRON_JUGULIS, SpeciesId.CHIEN_PAO, SpeciesId.TING_LU, SpeciesId.ROARING_MOON, + SpeciesId.IRON_VALIANT, + SpeciesId.IRON_LEAVES, + SpeciesId.OKIDOGI, SpeciesId.OGERPON, SpeciesId.IRON_BOULDER, SpeciesId.ALOLA_RATICATE, SpeciesId.ALOLA_SANDSHREW, SpeciesId.ALOLA_SANDSLASH, + SpeciesId.ALOLA_DUGTRIO, SpeciesId.ALOLA_MEOWTH, SpeciesId.ALOLA_PERSIAN, SpeciesId.ALOLA_MAROWAK, [ SpeciesId.LYCANROC, "midnight", + "dusk", ], SpeciesId.GALAR_MEOWTH, SpeciesId.GALAR_RAPIDASH, @@ -63872,6 +62847,9 @@ export const tmSpecies = { SpeciesId.CALYREX, "ice", ], + SpeciesId.HISUI_QWILFISH, + SpeciesId.HISUI_SAMUROTT, + SpeciesId.HISUI_ZOROARK, ], [MoveId.POLLEN_PUFF]: [ SpeciesId.BUTTERFREE, @@ -63913,6 +62891,7 @@ export const tmSpecies = { SpeciesId.HYDRAPPLE, SpeciesId.ETERNAL_FLOETTE, SpeciesId.HISUI_LILLIGANT, + SpeciesId.PALDEA_TAUROS, ], [MoveId.PSYCHIC_TERRAIN]: [ SpeciesId.ABRA, @@ -63923,6 +62902,7 @@ export const tmSpecies = { SpeciesId.DROWZEE, SpeciesId.HYPNO, SpeciesId.MR_MIME, + SpeciesId.EXEGGUTOR, SpeciesId.JYNX, SpeciesId.MEWTWO, SpeciesId.MEW, @@ -63943,6 +62923,7 @@ export const tmSpecies = { SpeciesId.CLAYDOL, SpeciesId.BRONZOR, SpeciesId.BRONZONG, + SpeciesId.DEOXYS, SpeciesId.MIME_JR, SpeciesId.GALLADE, SpeciesId.CRESSELIA, @@ -63982,6 +62963,7 @@ export const tmSpecies = { SpeciesId.IRON_LEAVES, SpeciesId.MUNKIDORI, SpeciesId.ALOLA_RAICHU, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.GALAR_RAPIDASH, SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWBRO, @@ -63998,6 +62980,7 @@ export const tmSpecies = { SpeciesId.DODUO, SpeciesId.DODRIO, SpeciesId.MUK, + SpeciesId.HITMONLEE, SpeciesId.SCYTHER, SpeciesId.MEW, SpeciesId.SPINARAK, @@ -64035,6 +63018,8 @@ export const tmSpecies = { SpeciesId.EELEKTROSS, SpeciesId.LARVESTA, SpeciesId.VOLCARONA, + SpeciesId.INKAY, + SpeciesId.MALAMAR, SpeciesId.HAWLUCHA, SpeciesId.GRUBBIN, SpeciesId.CHARJABUG, @@ -64119,6 +63104,8 @@ export const tmSpecies = { SpeciesId.RHYPERIOR, SpeciesId.TOGEKISS, SpeciesId.SAMUROTT, + SpeciesId.BLITZLE, + SpeciesId.ZEBSTRIKA, SpeciesId.EXCADRILL, SpeciesId.SCOLIPEDE, SpeciesId.SAWSBUCK, @@ -64445,6 +63432,7 @@ export const tmSpecies = { SpeciesId.MEW, SpeciesId.MEGANIUM, SpeciesId.TYPHLOSION, + SpeciesId.FERALIGATR, SpeciesId.ARIADOS, SpeciesId.AMPHAROS, SpeciesId.SUDOWOODO, @@ -64893,6 +63881,7 @@ export const tmSpecies = { SpeciesId.RELICANTH, SpeciesId.METAGROSS, SpeciesId.REGIROCK, + SpeciesId.REGICE, SpeciesId.REGISTEEL, SpeciesId.GROUDON, SpeciesId.TORTERRA, @@ -65112,6 +64101,8 @@ export const tmSpecies = { SpeciesId.REGISTEEL, SpeciesId.JIRACHI, SpeciesId.EMPOLEON, + SpeciesId.SHIELDON, + SpeciesId.BASTIODON, SpeciesId.BRONZOR, SpeciesId.BRONZONG, SpeciesId.LUCARIO, @@ -65339,6 +64330,7 @@ export const tmSpecies = { SpeciesId.DRAGONAIR, SpeciesId.DRAGONITE, SpeciesId.MEW, + SpeciesId.FERALIGATR, SpeciesId.DUNSPARCE, SpeciesId.GLIGAR, SpeciesId.QWILFISH, @@ -65360,6 +64352,7 @@ export const tmSpecies = { SpeciesId.GARCHOMP, SpeciesId.DIALGA, SpeciesId.PALKIA, + SpeciesId.SERPERIOR, SpeciesId.BASCULIN, SpeciesId.KROKOROK, SpeciesId.KROOKODILE, @@ -65539,6 +64532,9 @@ export const tmSpecies = { SpeciesId.EXEGGUTOR, SpeciesId.TANGELA, SpeciesId.MEW, + SpeciesId.CHIKORITA, + SpeciesId.BAYLEEF, + SpeciesId.MEGANIUM, SpeciesId.BELLOSSOM, SpeciesId.SUNFLORA, SpeciesId.CELEBI, @@ -65566,6 +64562,9 @@ export const tmSpecies = { SpeciesId.TANGROWTH, SpeciesId.LEAFEON, SpeciesId.SHAYMIN, + SpeciesId.SNIVY, + SpeciesId.SERVINE, + SpeciesId.SERPERIOR, SpeciesId.SEWADDLE, SpeciesId.SWADLOON, SpeciesId.LEAVANNY, @@ -65907,8 +64906,12 @@ export const tmSpecies = { SpeciesId.GYARADOS, SpeciesId.MEWTWO, SpeciesId.MEW, + SpeciesId.FERALIGATR, SpeciesId.UMBREON, SpeciesId.MURKROW, + SpeciesId.SNUBBULL, + SpeciesId.GRANBULL, + SpeciesId.QWILFISH, SpeciesId.SNEASEL, SpeciesId.HOUNDOUR, SpeciesId.HOUNDOOM, @@ -67682,6 +66685,7 @@ export const tmSpecies = { SpeciesId.SHELLDER, SpeciesId.CLOYSTER, SpeciesId.CHANSEY, + SpeciesId.JYNX, SpeciesId.LAPRAS, SpeciesId.ARTICUNO, SpeciesId.DRAGONITE, @@ -67736,6 +66740,8 @@ export const tmSpecies = { SpeciesId.GRENINJA, SpeciesId.SKRELP, SpeciesId.DRAGALGE, + SpeciesId.AMAURA, + SpeciesId.AURORUS, SpeciesId.BERGMITE, SpeciesId.AVALUGG, SpeciesId.DIANCIE, @@ -67743,8 +66749,11 @@ export const tmSpecies = { SpeciesId.CRABOMINABLE, SpeciesId.MAGEARNA, SpeciesId.INTELEON, + SpeciesId.MR_RIME, SpeciesId.FROSMOTH, SpeciesId.EISCUE, + SpeciesId.ARCTOZOLT, + SpeciesId.ARCTOVISH, SpeciesId.GLASTRIER, SpeciesId.BASCULEGION, SpeciesId.CETODDLE, @@ -67760,6 +66769,7 @@ export const tmSpecies = { SpeciesId.ALOLA_SANDSLASH, SpeciesId.ALOLA_VULPIX, SpeciesId.ALOLA_NINETALES, + SpeciesId.GALAR_MR_MIME, SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_ARTICUNO, @@ -68029,6 +67039,8 @@ export const tmSpecies = { SpeciesId.MIENSHAO, SpeciesId.LARVESTA, SpeciesId.VOLCARONA, + SpeciesId.VIRIZION, + SpeciesId.KELDEO, SpeciesId.CHESPIN, SpeciesId.QUILLADIN, SpeciesId.CHESNAUGHT, @@ -68052,6 +67064,9 @@ export const tmSpecies = { SpeciesId.ROWLET, SpeciesId.DARTRIX, SpeciesId.DECIDUEYE, + SpeciesId.LITTEN, + SpeciesId.TORRACAT, + SpeciesId.INCINEROAR, SpeciesId.YUNGOOS, SpeciesId.GUMSHOOS, SpeciesId.ORICORIO, @@ -68136,6 +67151,7 @@ export const tmSpecies = { SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_MEOWTH, SpeciesId.ALOLA_PERSIAN, + SpeciesId.ALOLA_EXEGGUTOR, SpeciesId.ETERNAL_FLOETTE, SpeciesId.GALAR_MEOWTH, SpeciesId.GALAR_ZAPDOS, diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index 75413a261ef..a309b4eb8b8 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -86,7 +86,6 @@ export const tmPoolTiers: TmPoolTiers = { [MoveId.ICE_PUNCH]: ModifierTier.GREAT, [MoveId.THUNDER_PUNCH]: ModifierTier.GREAT, [MoveId.SWORDS_DANCE]: ModifierTier.GREAT, - [MoveId.CUT]: ModifierTier.COMMON, [MoveId.FLY]: ModifierTier.GREAT, [MoveId.MEGA_KICK]: ModifierTier.GREAT, [MoveId.BODY_SLAM]: ModifierTier.GREAT, @@ -160,7 +159,6 @@ export const tmPoolTiers: TmPoolTiers = { [MoveId.SLEEP_TALK]: ModifierTier.COMMON, [MoveId.HEAL_BELL]: ModifierTier.COMMON, [MoveId.RETURN]: ModifierTier.ULTRA, - [MoveId.FRUSTRATION]: ModifierTier.COMMON, [MoveId.SAFEGUARD]: ModifierTier.COMMON, [MoveId.PAIN_SPLIT]: ModifierTier.COMMON, [MoveId.MEGAHORN]: ModifierTier.ULTRA,