From 3b490e7ab40ce983d1f20d6179b6fdbcb1d11456 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:50:35 -0400 Subject: [PATCH 1/7] [Bug] Status moves blocked by terrain now use correct message https://github.com/pagefaultgames/pokerogue/pull/5931 * Moved terrain messages to `terrain.ts`, made status failures use correct text * Revert overrides.ts * Comment fix * Fixed confusion message not appearing for misty terrain blockages * Fixed bug * re-added import * Update battler-tags.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Added exhaustiveness checking * ran boime --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 7 +++- src/data/terrain.ts | 74 ++++++++++++++++++++++++++++++++++++++++ src/data/weather.ts | 45 ------------------------ src/field/arena.ts | 8 ++--- src/field/pokemon.ts | 54 ++++++++++++++++++++--------- src/phases/move-phase.ts | 3 +- 6 files changed, 123 insertions(+), 68 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 7e9b9825e06..0c6d1df7ad8 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -673,7 +673,12 @@ export class ConfusedTag extends BattlerTag { } canAdd(pokemon: Pokemon): boolean { - return globalScene.arena.terrain?.terrainType !== TerrainType.MISTY || !pokemon.isGrounded(); + const blockedByTerrain = pokemon.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.MISTY; + if (blockedByTerrain) { + pokemon.queueStatusImmuneMessage(false, TerrainType.MISTY); + return false; + } + return true; } onAdd(pokemon: Pokemon): void { diff --git a/src/data/terrain.ts b/src/data/terrain.ts index b3ee62ac2f9..bcb28558de6 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -3,6 +3,7 @@ import type Move from "./moves/move"; import { PokemonType } from "#enums/pokemon-type"; import type { BattlerIndex } from "#enums/battler-index"; import i18next from "i18next"; +import { getPokemonNameWithAffix } from "#app/messages"; export enum TerrainType { NONE, @@ -96,3 +97,76 @@ export function getTerrainColor(terrainType: TerrainType): [number, number, numb return [0, 0, 0]; } + +/** + * Return the message associated with a terrain effect starting. + * @param terrainType - The {@linkcode TerrainType} starting. + * @returns A string containing the appropriate terrain start text. + */ +export function getTerrainStartMessage(terrainType: TerrainType): string { + switch (terrainType) { + case TerrainType.MISTY: + return i18next.t("terrain:mistyStartMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricStartMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyStartMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicStartMessage"); + case TerrainType.NONE: + default: + terrainType satisfies TerrainType.NONE; + console.warn(`${terrainType} unexpectedly provided as terrain type to getTerrainStartMessage!`); + return ""; + } +} + +/** + * Return the message associated with a terrain effect ceasing to exist. + * @param terrainType - The {@linkcode TerrainType} being cleared. + * @returns A string containing the appropriate terrain clear text. + */ +export function getTerrainClearMessage(terrainType: TerrainType): string { + switch (terrainType) { + case TerrainType.MISTY: + return i18next.t("terrain:mistyClearMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricClearMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyClearMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicClearMessage"); + case TerrainType.NONE: + default: + terrainType satisfies TerrainType.NONE; + console.warn(`${terrainType} unexpectedly provided as terrain type to getTerrainClearMessage!`); + return ""; + } +} + +/** + * Return the message associated with a terrain-induced move/effect blockage. + * @param pokemon - The {@linkcode Pokemon} being protected. + * @param terrainType - The {@linkcode TerrainType} in question + * @returns A string containing the appropriate terrain block text. + */ +export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string { + switch (terrainType) { + case TerrainType.MISTY: + return i18next.t("terrain:mistyBlockMessage", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + }); + case TerrainType.ELECTRIC: + case TerrainType.GRASSY: + case TerrainType.PSYCHIC: + return i18next.t("terrain:defaultBlockMessage", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + terrainName: getTerrainName(terrainType), + }); + case TerrainType.NONE: + default: + terrainType satisfies TerrainType.NONE; + console.warn(`${terrainType} unexpectedly provided as terrain type to getTerrainBlockMessage!`); + return ""; + } +} diff --git a/src/data/weather.ts b/src/data/weather.ts index 425e15b12a8..3fde932a1d7 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -5,7 +5,6 @@ import type Pokemon from "../field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import type Move from "./moves/move"; import { randSeedInt } from "#app/utils/common"; -import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import type { Arena } from "#app/field/arena"; @@ -235,50 +234,6 @@ export function getWeatherBlockMessage(weatherType: WeatherType): string { return i18next.t("weather:defaultEffectMessage"); } -export function getTerrainStartMessage(terrainType: TerrainType): string | null { - switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyStartMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricStartMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyStartMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicStartMessage"); - default: - console.warn("getTerrainStartMessage not defined. Using default null"); - return null; - } -} - -export function getTerrainClearMessage(terrainType: TerrainType): string | null { - switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyClearMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricClearMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyClearMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicClearMessage"); - default: - console.warn("getTerrainClearMessage not defined. Using default null"); - return null; - } -} - -export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string { - if (terrainType === TerrainType.MISTY) { - return i18next.t("terrain:mistyBlockMessage", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - }); - } - return i18next.t("terrain:defaultBlockMessage", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - terrainName: getTerrainName(terrainType), - }); -} - export interface WeatherPoolEntry { weatherType: WeatherType; weight: number; diff --git a/src/field/arena.ts b/src/field/arena.ts index 6893678d4a8..4479748667c 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -5,8 +5,6 @@ import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from " import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { - getTerrainClearMessage, - getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, getLegendaryWeatherContinuesMessage, @@ -19,7 +17,7 @@ import type { ArenaTag } from "#app/data/arena-tag"; import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import type { BattlerIndex } from "#enums/battler-index"; -import { Terrain, TerrainType } from "#app/data/terrain"; +import { Terrain, TerrainType, getTerrainClearMessage, getTerrainStartMessage } from "#app/data/terrain"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; import Overrides from "#app/overrides"; @@ -445,9 +443,9 @@ export class Arena { CommonAnim.MISTY_TERRAIN + (terrain - 1), ); } - globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct? + globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain)); } else { - globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? + globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType)); } globalScene diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index eee6c309859..3e6a4318d95 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -169,12 +169,13 @@ import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode"; import { FieldPosition } from "#enums/field-position"; -import { LearnMoveSituation } from "#enums/learn-move-situation"; import { HitResult } from "#enums/hit-result"; import { AiType } from "#enums/ai-type"; import type { MoveResult } from "#enums/move-result"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types"; +import { getTerrainBlockMessage } from "#app/data/terrain"; +import { LearnMoveSituation } from "#enums/learn-move-situation"; /** Base typeclass for damage parameter methods, used for DRY */ type damageParams = { @@ -4654,16 +4655,37 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } - queueImmuneMessage(quiet: boolean, effect?: StatusEffect): void { - if (!effect || quiet) { + /** + * Display an immunity message for a failed status application. + * @param quiet - Whether to suppress message and return early + * @param reason - The reason for the status application failure - + * can be "overlap" (already has same status), "other" (generic fail message) + * or a {@linkcode TerrainType} for terrain-based blockages. + * Defaults to "other". + */ + queueStatusImmuneMessage( + quiet: boolean, + reason: "overlap" | "other" | Exclude = "other", + ): void { + if (quiet) { return; } - const message = - effect && this.status?.effect === effect - ? getStatusEffectOverlapText(effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this)) - : i18next.t("abilityTriggers:moveImmunity", { - pokemonNameWithAffix: getPokemonNameWithAffix(this), - }); + + let message: string; + if (reason === "overlap") { + // "XYZ is already XXX!" + message = getStatusEffectOverlapText(this.status?.effect ?? StatusEffect.NONE, getPokemonNameWithAffix(this)); + } else if (typeof reason === "number") { + // "XYZ was protected by the XXX terrain!" / + // "XYZ surrounds itself with a protective mist!" + message = getTerrainBlockMessage(this, reason); + } else { + // "It doesn't affect XXX!" + message = i18next.t("abilityTriggers:moveImmunity", { + pokemonNameWithAffix: getPokemonNameWithAffix(this), + }); + } + globalScene.phaseManager.queueMessage(message); } @@ -4685,11 +4707,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ): boolean { if (effect !== StatusEffect.FAINT) { if (overrideStatus ? this.status?.effect === effect : this.status) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet, overrideStatus ? "overlap" : "other"); // having different status displays generic fail message return false; } if (this.isGrounded() && !ignoreField && globalScene.arena.terrain?.terrainType === TerrainType.MISTY) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet, TerrainType.MISTY); return false; } } @@ -4726,7 +4748,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isOfType(PokemonType.POISON) || this.isOfType(PokemonType.STEEL)) { if (poisonImmunity.includes(true)) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet); return false; } } @@ -4734,13 +4756,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } case StatusEffect.PARALYSIS: if (this.isOfType(PokemonType.ELECTRIC)) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet); return false; } break; case StatusEffect.SLEEP: if (this.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet, TerrainType.ELECTRIC); return false; } break; @@ -4751,13 +4773,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { globalScene?.arena?.weather?.weatherType && [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(globalScene.arena.weather.weatherType)) ) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet); return false; } break; case StatusEffect.BURN: if (this.isOfType(PokemonType.FIRE)) { - this.queueImmuneMessage(quiet, effect); + this.queueStatusImmuneMessage(quiet); return false; } break; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index ef376dc5957..3bc46d3aea8 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -11,7 +11,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; -import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather"; +import { getWeatherBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; @@ -26,6 +26,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { MoveId } from "#enums/move-id"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { getTerrainBlockMessage } from "#app/data/terrain"; import { isVirtual, isIgnorePP, isReflected, MoveUseMode, isIgnoreStatus } from "#enums/move-use-mode"; import { frenzyMissFunc } from "#app/data/moves/move-utils"; From ecdaac20faebed67324b8e35ab51bd0c69ee5d8d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:40:15 -0700 Subject: [PATCH 2/7] [Test] Fix Gorilla Tactics tests --- test/abilities/gorilla_tactics.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/abilities/gorilla_tactics.test.ts b/test/abilities/gorilla_tactics.test.ts index 330b4f51bcd..7a563f5b37f 100644 --- a/test/abilities/gorilla_tactics.test.ts +++ b/test/abilities/gorilla_tactics.test.ts @@ -28,8 +28,10 @@ describe("Abilities - Gorilla Tactics", () => { game = new GameManager(phaserGame); game.override .battleStyle("single") + .criticalHits(false) .enemyAbility(AbilityId.BALL_FETCH) .enemySpecies(SpeciesId.MAGIKARP) + .enemyMoveset(MoveId.SPLASH) .enemyLevel(30) .moveset([MoveId.SPLASH, MoveId.TACKLE, MoveId.GROWL, MoveId.METRONOME]) .ability(AbilityId.GORILLA_TACTICS); @@ -42,7 +44,6 @@ describe("Abilities - Gorilla Tactics", () => { const initialAtkStat = darmanitan.getStat(Stat.ATK); game.move.select(MoveId.SPLASH); - await game.move.forceEnemyMove(MoveId.SPLASH); await game.toEndOfTurn(); expect(darmanitan.getStat(Stat.ATK, false)).toBeCloseTo(initialAtkStat * 1.5); @@ -59,7 +60,6 @@ describe("Abilities - Gorilla Tactics", () => { // First turn, lock move to Growl game.move.select(MoveId.GROWL); - await game.move.forceEnemyMove(MoveId.SPLASH); await game.toNextTurn(); // Second turn, Growl is interrupted by Disable @@ -72,7 +72,7 @@ describe("Abilities - Gorilla Tactics", () => { // Third turn, Struggle is used game.move.select(MoveId.TACKLE); - await game.move.forceEnemyMove(MoveId.SPLASH); //prevent protect from being used by the enemy + await game.move.forceEnemyMove(MoveId.SPLASH); // prevent disable from being used by the enemy await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.phaseInterceptor.to("MoveEndPhase"); @@ -106,11 +106,13 @@ describe("Abilities - Gorilla Tactics", () => { const darmanitan = game.field.getPlayerPokemon(); game.move.select(MoveId.TACKLE); - await game.move.selectEnemyMove(MoveId.PROTECT); + await game.move.forceEnemyMove(MoveId.PROTECT); await game.toEndOfTurn(); expect(darmanitan.isMoveRestricted(MoveId.SPLASH)).toBe(true); expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(false); + const enemy = game.field.getEnemyPokemon(); + expect(enemy.hp).toBe(enemy.getMaxHp()); }); it("should activate when a move is succesfully executed but misses", async () => { @@ -119,7 +121,6 @@ describe("Abilities - Gorilla Tactics", () => { const darmanitan = game.field.getPlayerPokemon(); game.move.select(MoveId.TACKLE); - await game.move.selectEnemyMove(MoveId.SPLASH); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); await game.move.forceMiss(); await game.toEndOfTurn(); From c389b7acdbe9374829410d83c99e07645729c5c9 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 25 Jun 2025 18:16:30 -0700 Subject: [PATCH 3/7] [Dev] Mark the fixer for Biome's `useBlockStatements` as "safe" (#6031) --- biome.jsonc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/biome.jsonc b/biome.jsonc index e517d3a18d0..ff541f4dcc2 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -70,7 +70,10 @@ }, "style": { "useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome - "useBlockStatements": "error", + "useBlockStatements": { + "level": "error", + "fix": "safe" + }, "useConst": "error", "useImportType": "error", "noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files From 07f5c3009c63a855f9be0f523e62a3fa8b5cfc0d Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:01:54 -0700 Subject: [PATCH 4/7] [Dev] Add player and enemy Nature and IV overrides (#6032) * [Dev] Add player and enemy Nature and IV overrides * Fix broken tests --- src/battle-scene.ts | 49 +++++++-- src/overrides.ts | 25 ++++- test/abilities/wimp_out.test.ts | 4 +- test/moves/heal_block.test.ts | 2 +- test/moves/instruct.test.ts | 40 ++++--- test/testUtils/helpers/classicModeHelper.ts | 15 ++- test/testUtils/helpers/overridesHelper.ts | 116 ++++++++++++++++++-- 7 files changed, 209 insertions(+), 42 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 2ac13033412..59c0e28422b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -18,6 +18,7 @@ import { isNullOrUndefined, BooleanHolder, type Constructor, + isBetween, } from "#app/utils/common"; import { deepMergeSpriteData } from "#app/utils/data"; import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; @@ -164,10 +165,6 @@ import { PhaseManager } from "./phase-manager"; const DEBUG_RNG = false; -const OPP_IVS_OVERRIDE_VALIDATED: number[] = ( - Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE) -).map(iv => (Number.isNaN(iv) || iv === null || iv > 31 ? -1 : iv)); - export interface PokeballCounts { [pb: string]: number; } @@ -934,9 +931,32 @@ export default class BattleScene extends SceneBase { nature, dataSource, ); + if (postProcess) { postProcess(pokemon); } + + if (Overrides.IVS_OVERRIDE === null) { + // do nothing + } else if (Array.isArray(Overrides.IVS_OVERRIDE)) { + if (Overrides.IVS_OVERRIDE.length !== 6) { + throw new Error("The Player IVs override must be an array of length 6 or a number!"); + } + if (Overrides.IVS_OVERRIDE.some(value => !isBetween(value, 0, 31))) { + throw new Error("All IVs in the player IV override must be between 0 and 31!"); + } + pokemon.ivs = Overrides.IVS_OVERRIDE; + } else { + if (!isBetween(Overrides.IVS_OVERRIDE, 0, 31)) { + throw new Error("The Player IV override must be a value between 0 and 31!"); + } + pokemon.ivs = new Array(6).fill(Overrides.IVS_OVERRIDE); + } + + if (Overrides.NATURE_OVERRIDE !== null) { + pokemon.nature = Overrides.NATURE_OVERRIDE; + } + pokemon.init(); return pokemon; } @@ -981,10 +1001,25 @@ export default class BattleScene extends SceneBase { postProcess(pokemon); } - for (let i = 0; i < pokemon.ivs.length; i++) { - if (OPP_IVS_OVERRIDE_VALIDATED[i] > -1) { - pokemon.ivs[i] = OPP_IVS_OVERRIDE_VALIDATED[i]; + if (Overrides.ENEMY_IVS_OVERRIDE === null) { + // do nothing + } else if (Array.isArray(Overrides.ENEMY_IVS_OVERRIDE)) { + if (Overrides.ENEMY_IVS_OVERRIDE.length !== 6) { + throw new Error("The Enemy IVs override must be an array of length 6 or a number!"); } + if (Overrides.ENEMY_IVS_OVERRIDE.some(value => !isBetween(value, 0, 31))) { + throw new Error("All IVs in the enemy IV override must be between 0 and 31!"); + } + pokemon.ivs = Overrides.ENEMY_IVS_OVERRIDE; + } else { + if (!isBetween(Overrides.ENEMY_IVS_OVERRIDE, 0, 31)) { + throw new Error("The Enemy IV override must be a value between 0 and 31!"); + } + pokemon.ivs = new Array(6).fill(Overrides.ENEMY_IVS_OVERRIDE); + } + + if (Overrides.ENEMY_NATURE_OVERRIDE !== null) { + pokemon.nature = Overrides.ENEMY_NATURE_OVERRIDE; } pokemon.init(); diff --git a/src/overrides.ts b/src/overrides.ts index b390b9fa70f..82462431fb0 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,18 +1,18 @@ import { type PokeballCounts } from "#app/battle-scene"; import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { Gender } from "#app/data/gender"; -import { FormChangeItem } from "#enums/form-change-item"; import { type ModifierOverride } from "#app/modifier/modifier-type"; import { Variant } from "#app/sprites/variant"; -import { Unlockables } from "#enums/unlockables"; import { AbilityId } from "#enums/ability-id"; import { BattleType } from "#enums/battle-type"; import { BerryType } from "#enums/berry-type"; import { BiomeId } from "#enums/biome-id"; import { EggTier } from "#enums/egg-type"; +import { FormChangeItem } from "#enums/form-change-item"; import { MoveId } from "#enums/move-id"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Nature } from "#enums/nature"; import { PokeballType } from "#enums/pokeball"; import { PokemonType } from "#enums/pokemon-type"; import { SpeciesId } from "#enums/species-id"; @@ -20,6 +20,7 @@ import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; +import { Unlockables } from "#enums/unlockables"; import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; @@ -159,10 +160,20 @@ class DefaultOverrides { readonly MOVESET_OVERRIDE: MoveId | Array = []; readonly SHINY_OVERRIDE: boolean | null = null; readonly VARIANT_OVERRIDE: Variant | null = null; + /** + * Overrides the IVs of player pokemon. Values must never be outside the range `0` to `31`! + * - If set to a number between `0` and `31`, set all IVs of all player pokemon to that number. + * - If set to an array, set the IVs of all player pokemon to that array. Array length must be exactly `6`! + * - If set to `null`, disable the override. + */ + readonly IVS_OVERRIDE: number | number[] | null = null; + /** Override the nature of all player pokemon to the specified nature. Disabled if `null`. */ + readonly NATURE_OVERRIDE: Nature | null = null; // -------------------------- // OPPONENT / ENEMY OVERRIDES // -------------------------- + // TODO: rename `OPP_` to `ENEMY_` readonly OPP_SPECIES_OVERRIDE: SpeciesId | number = 0; /** * This will make all opponents fused Pokemon @@ -181,7 +192,15 @@ class DefaultOverrides { readonly OPP_MOVESET_OVERRIDE: MoveId | Array = []; readonly OPP_SHINY_OVERRIDE: boolean | null = null; readonly OPP_VARIANT_OVERRIDE: Variant | null = null; - readonly OPP_IVS_OVERRIDE: number | number[] = []; + /** + * Overrides the IVs of enemy pokemon. Values must never be outside the range `0` to `31`! + * - If set to a number between `0` and `31`, set all IVs of all enemy pokemon to that number. + * - If set to an array, set the IVs of all enemy pokemon to that array. Array length must be exactly `6`! + * - If set to `null`, disable the override. + */ + readonly ENEMY_IVS_OVERRIDE: number | number[] | null = null; + /** Override the nature of all enemy pokemon to the specified nature. Disabled if `null`. */ + readonly ENEMY_NATURE_OVERRIDE: Nature | null = null; readonly OPP_FORM_OVERRIDES: Partial> = {}; /** * Override to give the enemy Pokemon a given amount of health segments diff --git a/test/abilities/wimp_out.test.ts b/test/abilities/wimp_out.test.ts index 1db0b80fcd0..8e97618d46f 100644 --- a/test/abilities/wimp_out.test.ts +++ b/test/abilities/wimp_out.test.ts @@ -108,7 +108,7 @@ describe("Abilities - Wimp Out", () => { }); it("Trapping moves do not prevent Wimp Out from activating.", async () => { - game.override.enemyMoveset([MoveId.SPIRIT_SHACKLE]).startingLevel(53).enemyLevel(45); + game.override.enemyMoveset([MoveId.SPIRIT_SHACKLE]).startingLevel(1).passiveAbility(AbilityId.STURDY); await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); game.move.select(MoveId.SPLASH); @@ -123,7 +123,7 @@ describe("Abilities - Wimp Out", () => { }); it("If this Ability activates due to being hit by U-turn or Volt Switch, the user of that move will not be switched out.", async () => { - game.override.startingLevel(95).enemyMoveset([MoveId.U_TURN]); + game.override.startingLevel(1).enemyMoveset([MoveId.U_TURN]).passiveAbility(AbilityId.STURDY); await game.classicMode.startBattle([SpeciesId.WIMPOD, SpeciesId.TYRUNT]); game.move.select(MoveId.SPLASH); diff --git a/test/moves/heal_block.test.ts b/test/moves/heal_block.test.ts index 77a10927930..dc69b5c2974 100644 --- a/test/moves/heal_block.test.ts +++ b/test/moves/heal_block.test.ts @@ -42,7 +42,7 @@ describe("Moves - Heal Block", () => { const player = game.scene.getPlayerPokemon()!; const enemy = game.scene.getEnemyPokemon()!; - player.damageAndUpdate(enemy.getMaxHp() - 1); + player.damageAndUpdate(player.getMaxHp() - 1); game.move.select(MoveId.ABSORB); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index d12859301b6..5c853dd280e 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -1,12 +1,13 @@ -import { BattlerIndex } from "#enums/battler-index"; -import { RandomMoveAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; +import { RandomMoveAttr } from "#app/data/moves/move"; import type Pokemon from "#app/field/pokemon"; -import { MoveResult } from "#enums/move-result"; +import type { TurnMove } from "#app/field/pokemon"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; -import { MoveUseMode } from "#enums/move-use-mode"; +import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; +import { MoveResult } from "#enums/move-result"; +import { MoveUseMode } from "#enums/move-use-mode"; import { SpeciesId } from "#enums/species-id"; import { Stat } from "#enums/stat"; import GameManager from "#test/testUtils/gameManager"; @@ -202,21 +203,32 @@ describe("Moves - Instruct", () => { game.override.battleStyle("double").enemyMoveset(MoveId.SPLASH).enemySpecies(SpeciesId.MAGIKARP).enemyLevel(1); await game.classicMode.startBattle([SpeciesId.HISUI_ELECTRODE, SpeciesId.KOMMO_O]); - const [electrode, kommo_o] = game.scene.getPlayerField()!; - game.move.changeMoveset(electrode, MoveId.CHLOROBLAST); + const [electrode, kommo_o] = game.scene.getPlayerField(); + game.move.changeMoveset(electrode, MoveId.THUNDERBOLT); game.move.changeMoveset(kommo_o, MoveId.INSTRUCT); - game.move.select(MoveId.CHLOROBLAST, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(MoveId.THUNDERBOLT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER); await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]); - await game.phaseInterceptor.to("BerryPhase"); + await game.toEndOfTurn(); - // Chloroblast always deals 50% max HP% recoil UNLESS you whiff - // due to lack of targets or similar, - // so all we have to do is check whether electrode fainted or not. - // Naturally, both karps should also be dead as well. - expect(electrode.isFainted()).toBe(true); - const [karp1, karp2] = game.scene.getEnemyField()!; + expect(electrode.getMoveHistory()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + result: MoveResult.SUCCESS, + move: MoveId.THUNDERBOLT, + targets: [BattlerIndex.ENEMY], + useMode: MoveUseMode.NORMAL, + }), + expect.objectContaining({ + result: MoveResult.SUCCESS, + move: MoveId.THUNDERBOLT, + targets: [BattlerIndex.ENEMY_2], + useMode: MoveUseMode.NORMAL, + }), + ]), + ); + const [karp1, karp2] = game.scene.getEnemyField(); expect(karp1.isFainted()).toBe(true); expect(karp2.isFainted()).toBe(true); }); diff --git a/test/testUtils/helpers/classicModeHelper.ts b/test/testUtils/helpers/classicModeHelper.ts index eff97483777..24c4a97e9bf 100644 --- a/test/testUtils/helpers/classicModeHelper.ts +++ b/test/testUtils/helpers/classicModeHelper.ts @@ -1,15 +1,16 @@ import { BattleStyle } from "#app/enums/battle-style"; -import type { SpeciesId } from "#enums/species-id"; import { getGameMode } from "#app/game-mode"; -import { GameModes } from "#enums/game-modes"; import overrides from "#app/overrides"; import { CommandPhase } from "#app/phases/command-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { GameModes } from "#enums/game-modes"; +import { Nature } from "#enums/nature"; +import type { SpeciesId } from "#enums/species-id"; import { UiMode } from "#enums/ui-mode"; -import { generateStarter } from "../gameManagerUtils"; -import { GameManagerHelper } from "./gameManagerHelper"; +import { generateStarter } from "#test/testUtils/gameManagerUtils"; +import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; /** * Helper to handle classic-mode specific operations. @@ -36,6 +37,12 @@ export class ClassicModeHelper extends GameManagerHelper { if (this.game.override.disableShinies) { this.game.override.shiny(false).enemyShiny(false); } + if (this.game.override.normalizeIVs) { + this.game.override.playerIVs(31).enemyIVs(31); + } + if (this.game.override.normalizeNatures) { + this.game.override.nature(Nature.HARDY).enemyNature(Nature.HARDY); + } this.game.onNextPrompt("TitlePhase", UiMode.TITLE, () => { this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); diff --git a/test/testUtils/helpers/overridesHelper.ts b/test/testUtils/helpers/overridesHelper.ts index 3bf0fbbda47..a26dc883a30 100644 --- a/test/testUtils/helpers/overridesHelper.ts +++ b/test/testUtils/helpers/overridesHelper.ts @@ -1,35 +1,55 @@ -import type { Variant } from "#app/sprites/variant"; +/** biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */ +import type { NewArenaEvent } from "#app/events/battle-scene"; +/** biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */ + import { Weather } from "#app/data/weather"; -import { AbilityId } from "#enums/ability-id"; import type { ModifierOverride } from "#app/modifier/modifier-type"; -import type { BattleStyle } from "#app/overrides"; +import type { BattleStyle, RandomTrainerOverride } from "#app/overrides"; import Overrides, { defaultOverrides } from "#app/overrides"; -import type { Unlockables } from "#enums/unlockables"; +import type { Variant } from "#app/sprites/variant"; +import { coerceArray, shiftCharCodes } from "#app/utils/common"; +import { AbilityId } from "#enums/ability-id"; +import type { BattleType } from "#enums/battle-type"; import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Nature } from "#enums/nature"; import { SpeciesId } from "#enums/species-id"; import { StatusEffect } from "#enums/status-effect"; +import type { Unlockables } from "#enums/unlockables"; import type { WeatherType } from "#enums/weather-type"; +import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; import { expect, vi } from "vitest"; -import { GameManagerHelper } from "./gameManagerHelper"; -import { coerceArray, shiftCharCodes } from "#app/utils/common"; -import type { RandomTrainerOverride } from "#app/overrides"; -import type { BattleType } from "#enums/battle-type"; /** * Helper to handle overrides in tests */ export class OverridesHelper extends GameManagerHelper { - /** If `true`, removes the starting items from enemies at the start of each test; default `true` */ + /** + * If `true`, removes the starting items from enemies at the start of each test. + * @defaultValue `true` + */ public removeEnemyStartingItems = true; - /** If `true`, sets the shiny overrides to disable shinies at the start of each test; default `true` */ + /** + * If `true`, sets the shiny overrides to disable shinies at the start of each test. + * @defaultValue `true` + */ public disableShinies = true; + /** + * If `true`, will set the IV overrides for player and enemy pokemon to `31` at the start of each test. + * @defaultValue `true` + */ + public normalizeIVs = true; + /** + * If `true`, will set the Nature overrides for player and enemy pokemon to a neutral nature at the start of each test. + * @defaultValue `true` + */ + public normalizeNatures = true; /** * Override the starting biome - * @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line + * @warning Any event listeners that are attached to {@linkcode NewArenaEvent} may need to be handled down the line * @param biome - The biome to set */ public startingBiome(biome: BiomeId): this { @@ -219,6 +239,80 @@ export class OverridesHelper extends GameManagerHelper { return this; } + /** + * Overrides the IVs of the player pokemon + * @param ivs - If set to a number, all IVs are set to the same value. Must be between `0` and `31`! + * + * If set to an array, that array is applied to the pokemon's IV field as-is. + * All values must be between `0` and `31`, and the array must be of exactly length `6`! + * + * If set to `null`, the override is disabled. + * @returns `this` + */ + public playerIVs(ivs: number | number[] | null): this { + this.normalizeIVs = false; + vi.spyOn(Overrides, "IVS_OVERRIDE", "get").mockReturnValue(ivs); + if (ivs === null) { + this.log("Player IVs override disabled!"); + } else { + this.log(`Player IVs set to ${ivs}!`); + } + return this; + } + + /** + * Overrides the nature of the player's pokemon + * @param nature - The nature to set, or `null` to disable the override. + * @returns `this` + */ + public nature(nature: Nature | null): this { + this.normalizeNatures = false; + vi.spyOn(Overrides, "NATURE_OVERRIDE", "get").mockReturnValue(nature); + if (nature === null) { + this.log("Player Nature override disabled!"); + } else { + this.log(`Player Nature set to ${Nature[nature]} (=${nature})!`); + } + return this; + } + + /** + * Overrides the IVs of the enemy pokemon + * @param ivs - If set to a number, all IVs are set to the same value. Must be between `0` and `31`! + * + * If set to an array, that array is applied to the pokemon's IV field as-is. + * All values must be between `0` and `31`, and the array must be of exactly length `6`! + * + * If set to `null`, the override is disabled. + * @returns `this` + */ + public enemyIVs(ivs: number | number[] | null): this { + this.normalizeIVs = false; + vi.spyOn(Overrides, "ENEMY_IVS_OVERRIDE", "get").mockReturnValue(ivs); + if (ivs === null) { + this.log("Enemy IVs override disabled!"); + } else { + this.log(`Enemy IVs set to ${ivs}!`); + } + return this; + } + + /** + * Overrides the nature of the enemy's pokemon + * @param nature - The nature to set, or `null` to disable the override. + * @returns `this` + */ + public enemyNature(nature: Nature | null): this { + this.normalizeNatures = false; + vi.spyOn(Overrides, "ENEMY_NATURE_OVERRIDE", "get").mockReturnValue(nature); + if (nature === null) { + this.log("Enemy Nature override disabled!"); + } else { + this.log(`Enemy Nature set to ${Nature[nature]} (=${nature})!`); + } + return this; + } + /** * Override each wave to not have standard trainer battles * @returns `this` From 25757ca649c5a69b6ed4dbfaaedde7cf268ec9e5 Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:38:31 -0400 Subject: [PATCH 5/7] [Docs] Fix typo in CONTRIBUTING.md (#6034) Fix typo in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f4ee992cb3..aeb42114744 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for taking the time to contribute, every little bit helps. This project is entirely open-source and unmonetized - community contributions are what keep it alive! -Please make sure you understand everything relevant to your changes from the [Table of Contents](#-table-of-contents), and absolutely *feel free to reach out reach out in the **#dev-corner** channel on [Discord](https://discord.gg/pokerogue)*. We are here to help and the better you understand what you're working on, the easier it will be for it to find its way into the game. +Please make sure you understand everything relevant to your changes from the [Table of Contents](#-table-of-contents), and absolutely *feel free to reach out in the **#dev-corner** channel on [Discord](https://discord.gg/pokerogue)*. We are here to help and the better you understand what you're working on, the easier it will be for it to find its way into the game. ## 📄 Table of Contents From 40a92df19050931a79e46209d33adb0a8efceeca Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Fri, 27 Jun 2025 04:04:14 +0200 Subject: [PATCH 6/7] [i18n] [Localization] Translatable Manaphy Egg Tier (#6038) Translatable Manaphy Egg Tier --- src/data/egg.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/egg.ts b/src/data/egg.ts index 1ef08a4f1da..4fd0bf27dbd 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -294,7 +294,7 @@ export class Egg { public getEggDescriptor(): string { if (this.isManaphyEgg()) { - return "Manaphy"; + return i18next.t("egg:manaphyTier"); } switch (this.tier) { case EggTier.RARE: From 1bd152a738e54c8fb1b93b06169d3b3a7519854e Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:55:47 +0100 Subject: [PATCH 7/7] [Refactor] Moved various classes/interfaces to own files (#5870) * Split up Pokemon data types to own files * Moved `AttackMoveResult` and `TurnMove` to own files * Moved `customPokemonData` into types folder + fixed comments * Moved files around + fixed stuff * Moved `DamageResult` into new file * Fixed imports * ran biome * Fix merge issue --- src/@types/attack-move-result.ts | 12 + src/@types/damage-result.ts | 21 ++ src/@types/illusion-data.ts | 41 +++ src/@types/turn-move.ts | 13 + src/battle.ts | 3 +- src/data/custom-pokemon-data.ts | 31 --- src/data/moves/move.ts | 3 +- .../encounters/clowning-around-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 2 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 2 +- .../utils/encounter-pokemon-utils.ts | 2 +- src/data/pokemon/pokemon-data.ts | 208 +++++++++++++++ src/field/damage-number-handler.ts | 2 +- src/field/pokemon.ts | 252 +----------------- src/phases/command-phase.ts | 3 +- src/phases/damage-anim-phase.ts | 2 +- src/phases/move-effect-phase.ts | 3 +- src/system/pokemon-data.ts | 4 +- .../version_migration/versions/v1_0_4.ts | 2 +- .../version_migration/versions/v1_10_0.ts | 2 +- src/ui/party-ui-handler.ts | 3 +- test/battlerTags/stockpiling.test.ts | 2 +- test/battlerTags/substitute.test.ts | 3 +- test/field/pokemon.test.ts | 2 +- test/moves/instruct.test.ts | 2 +- test/moves/steamroller.test.ts | 2 +- .../the-strong-stuff-encounter.test.ts | 2 +- 28 files changed, 334 insertions(+), 294 deletions(-) create mode 100644 src/@types/attack-move-result.ts create mode 100644 src/@types/damage-result.ts create mode 100644 src/@types/illusion-data.ts create mode 100644 src/@types/turn-move.ts delete mode 100644 src/data/custom-pokemon-data.ts create mode 100644 src/data/pokemon/pokemon-data.ts diff --git a/src/@types/attack-move-result.ts b/src/@types/attack-move-result.ts new file mode 100644 index 00000000000..c6aedf8096d --- /dev/null +++ b/src/@types/attack-move-result.ts @@ -0,0 +1,12 @@ +import type { BattlerIndex } from "#enums/battler-index"; +import type { DamageResult } from "#app/@types/damage-result"; +import type { MoveId } from "#enums/move-id"; + +export interface AttackMoveResult { + move: MoveId; + result: DamageResult; + damage: number; + critical: boolean; + sourceId: number; + sourceBattlerIndex: BattlerIndex; +} diff --git a/src/@types/damage-result.ts b/src/@types/damage-result.ts new file mode 100644 index 00000000000..b6eb6b82770 --- /dev/null +++ b/src/@types/damage-result.ts @@ -0,0 +1,21 @@ +import type { HitResult } from "#enums/hit-result"; + +/** Union type containing all damage-dealing {@linkcode HitResult}s. */ +export type DamageResult = + | HitResult.EFFECTIVE + | HitResult.SUPER_EFFECTIVE + | HitResult.NOT_VERY_EFFECTIVE + | HitResult.ONE_HIT_KO + | HitResult.CONFUSION + | HitResult.INDIRECT_KO + | HitResult.INDIRECT; + +/** Interface containing the results of a damage calculation for a given move. */ +export interface DamageCalculationResult { + /** `true` if the move was cancelled (thus suppressing "No Effect" messages) */ + cancelled: boolean; + /** The effectiveness of the move */ + result: HitResult; + /** The damage dealt by the move */ + damage: number; +} diff --git a/src/@types/illusion-data.ts b/src/@types/illusion-data.ts new file mode 100644 index 00000000000..98e39af11f5 --- /dev/null +++ b/src/@types/illusion-data.ts @@ -0,0 +1,41 @@ +import type { Gender } from "#app/data/gender"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import type { Variant } from "#app/sprites/variant"; +import type { PokeballType } from "#enums/pokeball"; +import type { SpeciesId } from "#enums/species-id"; + +/** + * Data pertaining to a Pokemon's Illusion. + */ +export interface IllusionData { + basePokemon: { + /** The actual name of the Pokemon */ + name: string; + /** The actual nickname of the Pokemon */ + nickname: string; + /** Whether the base pokemon is shiny or not */ + shiny: boolean; + /** The shiny variant of the base pokemon */ + variant: Variant; + /** Whether the fusion species of the base pokemon is shiny or not */ + fusionShiny: boolean; + /** The variant of the fusion species of the base pokemon */ + fusionVariant: Variant; + }; + /** The species of the illusion */ + species: SpeciesId; + /** The formIndex of the illusion */ + formIndex: number; + /** The gender of the illusion */ + gender: Gender; + /** The pokeball of the illusion */ + pokeball: PokeballType; + /** The fusion species of the illusion if it's a fusion */ + fusionSpecies?: PokemonSpecies; + /** The fusionFormIndex of the illusion */ + fusionFormIndex?: number; + /** The fusionGender of the illusion if it's a fusion */ + fusionGender?: Gender; + /** The level of the illusion (not used currently) */ + level?: number; +} diff --git a/src/@types/turn-move.ts b/src/@types/turn-move.ts new file mode 100644 index 00000000000..a5a69eb3749 --- /dev/null +++ b/src/@types/turn-move.ts @@ -0,0 +1,13 @@ +import type { BattlerIndex } from "#enums/battler-index"; +import type { MoveId } from "#enums/move-id"; +import type { MoveResult } from "#enums/move-result"; +import type { MoveUseMode } from "#enums/move-use-mode"; + +/** A record of a move having been used. */ +export interface TurnMove { + move: MoveId; + targets: BattlerIndex[]; + useMode: MoveUseMode; + result?: MoveResult; + turn?: number; +} diff --git a/src/battle.ts b/src/battle.ts index 45373402e12..ba4152227dd 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -17,7 +17,8 @@ import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "./modifie import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; -import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { TurnMove } from "./@types/turn-move"; import type Pokemon from "#app/field/pokemon"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; diff --git a/src/data/custom-pokemon-data.ts b/src/data/custom-pokemon-data.ts deleted file mode 100644 index 252e302ccf3..00000000000 --- a/src/data/custom-pokemon-data.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { AbilityId } from "#enums/ability-id"; -import type { PokemonType } from "#enums/pokemon-type"; -import type { Nature } from "#enums/nature"; - -/** - * Data that can customize a Pokemon in non-standard ways from its Species. - * Includes abilities, nature, changed types, etc. - */ -export class CustomPokemonData { - // TODO: Change the default value for all these from -1 to something a bit more sensible - /** - * The scale at which to render this Pokemon's sprite. - */ - public spriteScale = -1; - public ability: AbilityId | -1; - public passive: AbilityId | -1; - public nature: Nature | -1; - public types: PokemonType[]; - /** Deprecated but needed for session save migration */ - // TODO: Remove this once pre-session migration is implemented - public hitsRecCount: number | null = null; - - constructor(data?: CustomPokemonData | Partial) { - this.spriteScale = data?.spriteScale ?? -1; - this.ability = data?.ability ?? -1; - this.passive = data?.passive ?? -1; - this.nature = data?.nature ?? -1; - this.types = data?.types ?? []; - this.hitsRecCount = data?.hitsRecCount ?? null; - } -} diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index f05c0c3014b..0878ece2f01 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -13,7 +13,8 @@ import { TypeBoostTag, } from "../battler-tags"; import { getPokemonNameWithAffix } from "../../messages"; -import type { AttackMoveResult, TurnMove } from "../../field/pokemon"; +import type { TurnMove } from "#app/@types/turn-move"; +import type { AttackMoveResult } from "#app/@types/attack-move-result"; import type Pokemon from "../../field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon"; import { PokemonMove } from "./pokemon-move"; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 818318bb499..553c4deb74e 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -44,7 +44,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { MoveId } from "#enums/move-id"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { MoveCategory } from "#enums/MoveCategory"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 4169fd6d7c5..d6a85dee119 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -29,7 +29,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { randSeedInt } from "#app/utils/common"; import { MoveUseMode } from "#enums/move-use-mode"; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 37d16075543..b853539acf5 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -25,7 +25,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { MoveUseMode } from "#enums/move-use-mode"; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index f698f636cac..bd2dfa998f4 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -43,7 +43,7 @@ import { TrainerSlot } from "#enums/trainer-slot"; import type PokemonSpecies from "#app/data/pokemon-species"; import type { IEggOptions } from "#app/data/egg"; import { Egg } from "#app/data/egg"; -import type { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import type { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import type HeldModifierConfig from "#app/@types/held-modifier-config"; import type { Variant } from "#app/sprites/variant"; import { StatusEffect } from "#enums/status-effect"; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index f6ac5b0d38b..f3655217b5a 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -33,7 +33,7 @@ import { modifierTypes } from "#app/data/data-lists"; import { Gender } from "#app/data/gender"; import type { PermanentStat } from "#enums/stat"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import type { AbilityId } from "#enums/ability-id"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; diff --git a/src/data/pokemon/pokemon-data.ts b/src/data/pokemon/pokemon-data.ts new file mode 100644 index 00000000000..db60b4ce1b4 --- /dev/null +++ b/src/data/pokemon/pokemon-data.ts @@ -0,0 +1,208 @@ +import { type BattlerTag, loadBattlerTag } from "#app/data/battler-tags"; +import type { Gender } from "#app/data/gender"; +import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; +import type { TypeDamageMultiplier } from "#app/data/type"; +import { isNullOrUndefined } from "#app/utils/common"; +import type { AbilityId } from "#enums/ability-id"; +import type { BerryType } from "#enums/berry-type"; +import type { MoveId } from "#enums/move-id"; +import type { PokemonType } from "#enums/pokemon-type"; +import { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { TurnMove } from "#app/@types/turn-move"; +import type { AttackMoveResult } from "#app/@types/attack-move-result"; +import type { Nature } from "#enums/nature"; +import type { IllusionData } from "#app/@types/illusion-data"; + +/** + * Permanent data that can customize a Pokemon in non-standard ways from its Species. + * Includes abilities, nature, changed types, etc. + */ +export class CustomPokemonData { + // TODO: Change the default value for all these from -1 to something a bit more sensible + /** + * The scale at which to render this Pokemon's sprite. + */ + public spriteScale = -1; + public ability: AbilityId | -1; + public passive: AbilityId | -1; + public nature: Nature | -1; + public types: PokemonType[]; + /** Deprecated but needed for session save migration */ + // TODO: Remove this once pre-session migration is implemented + public hitsRecCount: number | null = null; + + constructor(data?: CustomPokemonData | Partial) { + this.spriteScale = data?.spriteScale ?? -1; + this.ability = data?.ability ?? -1; + this.passive = data?.passive ?? -1; + this.nature = data?.nature ?? -1; + this.types = data?.types ?? []; + this.hitsRecCount = data?.hitsRecCount ?? null; + } +} + +/** + * Persistent in-battle data for a {@linkcode Pokemon}. + * Resets on switch or new battle. + */ +export class PokemonSummonData { + /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ + public statStages: number[] = [0, 0, 0, 0, 0, 0, 0]; + /** + * A queue of moves yet to be executed, used by charging, recharging and frenzy moves. + * So long as this array is nonempty, this Pokemon's corresponding `CommandPhase` will be skipped over entirely + * in favor of using the queued move. + * TODO: Clean up a lot of the code surrounding the move queue. + */ + public moveQueue: TurnMove[] = []; + public tags: BattlerTag[] = []; + public abilitySuppressed = false; + + // Overrides for transform. + // TODO: Move these into a separate class & add rage fist hit count + public speciesForm: PokemonSpeciesForm | null = null; + public fusionSpeciesForm: PokemonSpeciesForm | null = null; + public ability: AbilityId | undefined; + public passiveAbility: AbilityId | undefined; + public gender: Gender | undefined; + public fusionGender: Gender | undefined; + 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. */ + public illusion: IllusionData | null = null; + public illusionBroken = false; + + /** Array containing all berries eaten in the last turn; used by {@linkcode AbilityId.CUD_CHEW} */ + public berriesEatenLast: BerryType[] = []; + + /** + * 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. + */ + public moveHistory: TurnMove[] = []; + + constructor(source?: PokemonSummonData | Partial) { + if (isNullOrUndefined(source)) { + return; + } + + // TODO: Rework this into an actual generic function for use elsewhere + for (const [key, value] of Object.entries(source)) { + if (isNullOrUndefined(value) && this.hasOwnProperty(key)) { + continue; + } + + if (key === "moveset") { + this.moveset = value?.map((m: any) => PokemonMove.loadMove(m)); + continue; + } + + if (key === "tags") { + // load battler tags + this.tags = value.map((t: BattlerTag) => loadBattlerTag(t)); + continue; + } + this[key] = value; + } + } +} + +// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added +export class PokemonTempSummonData { + /** + * The number of turns this pokemon has spent without switching out. + * Only currently used for positioning the battle cursor. + */ + turnCount = 1; + + /** + * The number of turns this pokemon has spent in the active position since the start of the wave + * without switching out. + * Reset on switch and new wave, but not stored in `SummonData` to avoid being written to the save file. + + * Used to evaluate "first turn only" conditions such as + * {@linkcode MoveId.FAKE_OUT | Fake Out} and {@linkcode MoveId.FIRST_IMPRESSION | First Impression}). + */ + waveTurnCount = 1; +} + +/** + * Persistent data for a {@linkcode Pokemon}. + * Resets at the start of a new battle (but not on switch). + */ +export class PokemonBattleData { + /** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode MoveId.RAGE_FIST} */ + public hitCount = 0; + /** Whether this Pokemon has eaten a berry this battle; used for {@linkcode MoveId.BELCH} */ + public hasEatenBerry = false; + /** Array containing all berries eaten and not yet recovered during this current battle; used by {@linkcode AbilityId.HARVEST} */ + public berriesEaten: BerryType[] = []; + + constructor(source?: PokemonBattleData | Partial) { + if (!isNullOrUndefined(source)) { + this.hitCount = source.hitCount ?? 0; + this.hasEatenBerry = source.hasEatenBerry ?? false; + this.berriesEaten = source.berriesEaten ?? []; + } + } +} + +/** + * Temporary data for a {@linkcode Pokemon}. + * Resets on new wave/battle start (but not on switch). + */ +export class PokemonWaveData { + /** Whether the pokemon has endured due to a {@linkcode BattlerTagType.ENDURE_TOKEN} */ + public endured = false; + /** + * A set of all the abilities this {@linkcode Pokemon} has used in this wave. + * Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness. + */ + public abilitiesApplied: Set = new Set(); + /** Whether the pokemon's ability has been revealed or not */ + public abilityRevealed = false; +} + +/** + * Temporary data for a {@linkcode Pokemon}. + * Resets at the start of a new turn, as well as on switch. + */ +export class PokemonTurnData { + public acted = false; + /** 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 + */ + public hitsLeft = -1; + public totalDamageDealt = 0; + public singleHitDamageDealt = 0; + public damageTaken = 0; + public attacksReceived: AttackMoveResult[] = []; + public order: number; + public statStagesIncreased = false; + public statStagesDecreased = false; + public moveEffectiveness: TypeDamageMultiplier | null = null; + public combiningPledge?: MoveId; + public switchedInThisTurn = false; + public failedRunAway = false; + public joinedRound = false; + /** + * 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. + * @see {@linkcode PokemonSummonData.berriesEatenLast} + */ + public berriesEaten: BerryType[] = []; +} diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index b8b3ed76e18..24c5e107d15 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -1,5 +1,5 @@ import { TextStyle, addTextObject } from "../ui/text"; -import type { DamageResult } from "./pokemon"; +import type { DamageResult } from "../@types/damage-result"; import type Pokemon from "./pokemon"; import { HitResult } from "#enums/hit-result"; import { formatStat, fixedInt } from "#app/utils/common"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 3e6a4318d95..0a8e8469115 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -100,7 +100,6 @@ import { TarShotTag, AutotomizedTag, PowerTrickTag, - loadBattlerTag, type GrudgeTag, } from "../data/battler-tags"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; @@ -151,7 +150,14 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { + CustomPokemonData, + PokemonBattleData, + PokemonSummonData, + PokemonTempSummonData, + PokemonTurnData, + PokemonWaveData, +} from "#app/data/pokemon/pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { getStatusEffectOverlapText } from "#app/data/status-effect"; @@ -171,8 +177,10 @@ import { isVirtual, isIgnorePP, MoveUseMode } from "#enums/move-use-mode"; import { FieldPosition } from "#enums/field-position"; import { HitResult } from "#enums/hit-result"; import { AiType } from "#enums/ai-type"; -import type { MoveResult } from "#enums/move-result"; import { PokemonMove } from "#app/data/moves/pokemon-move"; +import type { IllusionData } from "#app/@types/illusion-data"; +import type { TurnMove } from "#app/@types/turn-move"; +import type { DamageCalculationResult, DamageResult } from "#app/@types/damage-result"; import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types"; import { getTerrainBlockMessage } from "#app/data/terrain"; import { LearnMoveSituation } from "#enums/learn-move-situation"; @@ -6812,241 +6820,3 @@ export class EnemyPokemon extends Pokemon { this.battleInfo.toggleFlyout(visible); } } - -/** - * Illusion property - */ -interface IllusionData { - basePokemon: { - /** The actual name of the Pokemon */ - name: string; - /** The actual nickname of the Pokemon */ - nickname: string; - /** Whether the base pokemon is shiny or not */ - shiny: boolean; - /** The shiny variant of the base pokemon */ - variant: Variant; - /** Whether the fusion species of the base pokemon is shiny or not */ - fusionShiny: boolean; - /** The variant of the fusion species of the base pokemon */ - fusionVariant: Variant; - }; - /** The species of the illusion */ - species: SpeciesId; - /** The formIndex of the illusion */ - formIndex: number; - /** The gender of the illusion */ - gender: Gender; - /** The pokeball of the illusion */ - pokeball: PokeballType; - /** The fusion species of the illusion if it's a fusion */ - fusionSpecies?: PokemonSpecies; - /** The fusionFormIndex of the illusion */ - fusionFormIndex?: number; - /** The fusionGender of the illusion if it's a fusion */ - fusionGender?: Gender; - /** The level of the illusion (not used currently) */ - level?: number; -} - -export interface TurnMove { - move: MoveId; - targets: BattlerIndex[]; - useMode: MoveUseMode; - result?: MoveResult; - turn?: number; -} - -export interface AttackMoveResult { - move: MoveId; - result: DamageResult; - damage: number; - critical: boolean; - sourceId: number; - sourceBattlerIndex: BattlerIndex; -} - -/** - * Persistent in-battle data for a {@linkcode Pokemon}. - * Resets on switch or new battle. - */ -export class PokemonSummonData { - /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ - public statStages: number[] = [0, 0, 0, 0, 0, 0, 0]; - /** - * A queue of moves yet to be executed, used by charging, recharging and frenzy moves. - * So long as this array is nonempty, this Pokemon's corresponding `CommandPhase` will be skipped over entirely - * in favor of using the queued move. - * TODO: Clean up a lot of the code surrounding the move queue. - */ - public moveQueue: TurnMove[] = []; - public tags: BattlerTag[] = []; - public abilitySuppressed = false; - - // Overrides for transform. - // TODO: Move these into a separate class & add rage fist hit count - public speciesForm: PokemonSpeciesForm | null = null; - public fusionSpeciesForm: PokemonSpeciesForm | null = null; - public ability: AbilityId | undefined; - public passiveAbility: AbilityId | undefined; - public gender: Gender | undefined; - public fusionGender: Gender | undefined; - 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. */ - public illusion: IllusionData | null = null; - public illusionBroken = false; - - /** Array containing all berries eaten in the last turn; used by {@linkcode AbilityId.CUD_CHEW} */ - public berriesEatenLast: BerryType[] = []; - - /** - * 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. - */ - public moveHistory: TurnMove[] = []; - - constructor(source?: PokemonSummonData | Partial) { - if (isNullOrUndefined(source)) { - return; - } - - // TODO: Rework this into an actual generic function for use elsewhere - for (const [key, value] of Object.entries(source)) { - if (isNullOrUndefined(value) && this.hasOwnProperty(key)) { - continue; - } - - if (key === "moveset") { - this.moveset = value?.map((m: any) => PokemonMove.loadMove(m)); - continue; - } - - if (key === "tags") { - // load battler tags - this.tags = value.map((t: BattlerTag) => loadBattlerTag(t)); - continue; - } - this[key] = value; - } - } -} - -// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added -export class PokemonTempSummonData { - /** - * The number of turns this pokemon has spent without switching out. - * Only currently used for positioning the battle cursor. - */ - turnCount = 1; - - /** - * The number of turns this pokemon has spent in the active position since the start of the wave - * without switching out. - * Reset on switch and new wave, but not stored in `SummonData` to avoid being written to the save file. - - * Used to evaluate "first turn only" conditions such as - * {@linkcode MoveId.FAKE_OUT | Fake Out} and {@linkcode MoveId.FIRST_IMPRESSION | First Impression}). - */ - waveTurnCount = 1; -} - -/** - * Persistent data for a {@linkcode Pokemon}. - * Resets at the start of a new battle (but not on switch). - */ -export class PokemonBattleData { - /** Counter tracking direct hits this Pokemon has received during this battle; used for {@linkcode MoveId.RAGE_FIST} */ - public hitCount = 0; - /** Whether this Pokemon has eaten a berry this battle; used for {@linkcode MoveId.BELCH} */ - public hasEatenBerry = false; - /** Array containing all berries eaten and not yet recovered during this current battle; used by {@linkcode AbilityId.HARVEST} */ - public berriesEaten: BerryType[] = []; - - constructor(source?: PokemonBattleData | Partial) { - if (!isNullOrUndefined(source)) { - this.hitCount = source.hitCount ?? 0; - this.hasEatenBerry = source.hasEatenBerry ?? false; - this.berriesEaten = source.berriesEaten ?? []; - } - } -} - -/** - * Temporary data for a {@linkcode Pokemon}. - * Resets on new wave/battle start (but not on switch). - */ -export class PokemonWaveData { - /** Whether the pokemon has endured due to a {@linkcode BattlerTagType.ENDURE_TOKEN} */ - public endured = false; - /** - * A set of all the abilities this {@linkcode Pokemon} has used in this wave. - * Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness. - */ - public abilitiesApplied: Set = new Set(); - /** Whether the pokemon's ability has been revealed or not */ - public abilityRevealed = false; -} - -/** - * Temporary data for a {@linkcode Pokemon}. - * Resets at the start of a new turn, as well as on switch. - */ -export class PokemonTurnData { - public acted = false; - /** 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 - */ - public hitsLeft = -1; - public totalDamageDealt = 0; - public singleHitDamageDealt = 0; - public damageTaken = 0; - public attacksReceived: AttackMoveResult[] = []; - public order: number; - public statStagesIncreased = false; - public statStagesDecreased = false; - public moveEffectiveness: TypeDamageMultiplier | null = null; - public combiningPledge?: MoveId; - public switchedInThisTurn = false; - public failedRunAway = false; - public joinedRound = false; - /** - * 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. - * @see {@linkcode PokemonSummonData.berriesEatenLast} - */ - public berriesEaten: BerryType[] = []; -} - -export type DamageResult = - | HitResult.EFFECTIVE - | HitResult.SUPER_EFFECTIVE - | HitResult.NOT_VERY_EFFECTIVE - | HitResult.ONE_HIT_KO - | HitResult.CONFUSION - | HitResult.INDIRECT_KO - | HitResult.INDIRECT; - -/** Interface containing the results of a damage calculation for a given move */ -export interface DamageCalculationResult { - /** `true` if the move was cancelled (thus suppressing "No Effect" messages) */ - cancelled: boolean; - /** The effectiveness of the move */ - result: HitResult; - /** The damage dealt by the move */ - damage: number; -} diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 754d54de70a..8281019b3c4 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -11,7 +11,8 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BiomeId } from "#enums/biome-id"; import { MoveId } from "#enums/move-id"; import { PokeballType } from "#enums/pokeball"; -import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { TurnMove } from "#app/@types/turn-move"; import { FieldPosition } from "#enums/field-position"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#enums/command"; diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index aa5a0a6c3e6..8dc377cdaf5 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -1,7 +1,7 @@ import { globalScene } from "#app/global-scene"; import type { BattlerIndex } from "#enums/battler-index"; import { BattleSpec } from "#enums/battle-spec"; -import type { DamageResult } from "#app/field/pokemon"; +import type { DamageResult } from "#app/@types/damage-result"; import { HitResult } from "#enums/hit-result"; import { fixedInt } from "#app/utils/common"; import { PokemonPhase } from "#app/phases/pokemon-phase"; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 610d670dcb9..0f53561f5f3 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -21,7 +21,8 @@ import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { PokemonType } from "#enums/pokemon-type"; -import type { DamageResult, TurnMove } from "#app/field/pokemon"; +import type { DamageResult } from "#app/@types/damage-result"; +import type { TurnMove } from "#app/@types/turn-move"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 050da57e0be..85f7b12a2a5 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -6,14 +6,14 @@ import { PokeballType } from "#enums/pokeball"; import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/utils/pokemon-utils"; import { Status } from "../data/status-effect"; -import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonSummonData } from "../field/pokemon"; +import Pokemon, { EnemyPokemon } from "../field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; import { TrainerSlot } from "#enums/trainer-slot"; import type { Variant } from "#app/sprites/variant"; import type { BiomeId } from "#enums/biome-id"; import type { MoveId } from "#enums/move-id"; import type { SpeciesId } from "#enums/species-id"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData, PokemonBattleData, PokemonSummonData } from "#app/data/pokemon/pokemon-data"; import type { PokemonType } from "#enums/pokemon-type"; export default class PokemonData { diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index a08327a3b70..eacb4f7da1a 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -4,7 +4,7 @@ import { defaultStarterSpecies } from "#app/constants"; import { AbilityAttr } from "#enums/ability-attr"; import { DexAttr } from "#enums/dex-attr"; import { allSpecies } from "#app/data/data-lists"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { isNullOrUndefined } from "#app/utils/common"; import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator"; diff --git a/src/system/version_migration/versions/v1_10_0.ts b/src/system/version_migration/versions/v1_10_0.ts index 4d1dedf701e..6ed3253a0c9 100644 --- a/src/system/version_migration/versions/v1_10_0.ts +++ b/src/system/version_migration/versions/v1_10_0.ts @@ -1,6 +1,6 @@ import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { BattlerIndex } from "#enums/battler-index"; -import type { TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/@types/turn-move"; import type { MoveResult } from "#enums/move-result"; import type { SessionSaveData } from "#app/system/game-data"; import { MoveUseMode } from "#enums/move-use-mode"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index cf6333f4580..60e9e846859 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,4 +1,5 @@ -import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { TurnMove } from "#app/@types/turn-move"; import type { PokemonMove } from "#app/data/moves/pokemon-move"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; diff --git a/test/battlerTags/stockpiling.test.ts b/test/battlerTags/stockpiling.test.ts index 37873db9eab..8f524ae7a4d 100644 --- a/test/battlerTags/stockpiling.test.ts +++ b/test/battlerTags/stockpiling.test.ts @@ -1,6 +1,6 @@ import { StockpilingTag } from "#app/data/battler-tags"; import type Pokemon from "#app/field/pokemon"; -import { PokemonSummonData } from "#app/field/pokemon"; +import { PokemonSummonData } from "#app/data/pokemon/pokemon-data"; import * as messages from "#app/messages"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; diff --git a/test/battlerTags/substitute.test.ts b/test/battlerTags/substitute.test.ts index 06aedab19f4..55c620bbb40 100644 --- a/test/battlerTags/substitute.test.ts +++ b/test/battlerTags/substitute.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { PokemonTurnData, TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/@types/turn-move"; +import type { PokemonTurnData } from "#app/data/pokemon/pokemon-data"; import type Pokemon from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result"; import type BattleScene from "#app/battle-scene"; diff --git a/test/field/pokemon.test.ts b/test/field/pokemon.test.ts index c6524e7397f..2c32f704e4e 100644 --- a/test/field/pokemon.test.ts +++ b/test/field/pokemon.test.ts @@ -5,7 +5,7 @@ import { PokeballType } from "#enums/pokeball"; import type BattleScene from "#app/battle-scene"; import { MoveId } from "#enums/move-id"; import { PokemonType } from "#enums/pokemon-type"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; describe("Spec - Pokemon", () => { let phaserGame: Phaser.Game; diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index 5c853dd280e..a7bcb4c86ea 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -1,7 +1,7 @@ +import type { TurnMove } from "#app/@types/turn-move"; import { allMoves } from "#app/data/data-lists"; import { RandomMoveAttr } from "#app/data/moves/move"; import type Pokemon from "#app/field/pokemon"; -import type { TurnMove } from "#app/field/pokemon"; import type { MovePhase } from "#app/phases/move-phase"; import { AbilityId } from "#enums/ability-id"; import { BattlerIndex } from "#enums/battler-index"; diff --git a/test/moves/steamroller.test.ts b/test/moves/steamroller.test.ts index 4eb011c47f5..67c63ff32e4 100644 --- a/test/moves/steamroller.test.ts +++ b/test/moves/steamroller.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#enums/battler-index"; import { allMoves } from "#app/data/data-lists"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import type { DamageCalculationResult } from "#app/field/pokemon"; +import type { DamageCalculationResult } from "#app/@types/damage-result"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; diff --git a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 96060e4114c..c1f13d61817 100644 --- a/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -24,7 +24,7 @@ import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modif import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";