diff --git a/src/data/challenge.ts b/src/data/challenge.ts index f786152ca3d..3d67ebd0189 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,4 +1,5 @@ -import { BooleanHolder, type NumberHolder, randSeedItem, deepCopy } from "#app/utils/common"; +import { BooleanHolder, type NumberHolder, randSeedItem } from "#app/utils/common"; +import { deepCopy } from "#app/utils/data"; import i18next from "i18next"; import type { DexAttrProps, GameData } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/system/game-data"; diff --git a/src/enums/biome.ts b/src/enums/biome.ts index bb9eaf454cc..7284528767d 100644 --- a/src/enums/biome.ts +++ b/src/enums/biome.ts @@ -1,4 +1,5 @@ export enum Biome { + // TODO: Should -1 be part of the enum signature (for "unknown place") TOWN, PLAINS, GRASS, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6f6ce78d1b1..80c7f2fd764 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -819,7 +819,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id))); // Load the assets for the species form - const formIndex = !!this.summonData.illusion && useIllusion ? this.summonData.illusion.formIndex : this.formIndex; + const formIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; loadPromises.push( this.getSpeciesForm(false, useIllusion).loadAssets( this.getGender(useIllusion) === Gender.FEMALE, @@ -836,9 +836,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); } if (this.getFusionSpeciesForm()) { - const fusionFormIndex = !!this.summonData.illusion && useIllusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; - const fusionShiny = !!this.summonData.illusion && !useIllusion ? this.summonData.illusion.basePokemon!.fusionShiny : this.fusionShiny; - const fusionVariant = !!this.summonData.illusion && !useIllusion ? this.summonData.illusion.basePokemon!.fusionVariant : this.fusionVariant; + // TODO: why is fusionFormIndex using illusion if defined but fusionShiny/variant aren't? + const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; + const fusionShiny = !useIllusion && this.summonData.illusion?.basePokemon ? this.summonData.illusion.basePokemon.fusionShiny : this.fusionShiny; + const fusionVariant = !useIllusion && this.summonData.illusion?.basePokemon ? this.summonData.illusion.basePokemon.fusionVariant : this.fusionVariant; loadPromises.push(this.getFusionSpeciesForm(false, useIllusion).loadAssets( this.getFusionGender(false, useIllusion) === Gender.FEMALE, fusionFormIndex, @@ -1006,7 +1007,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getSpriteId(ignoreOverride?: boolean): string { - const formIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.formIndex! : this.formIndex; + const formIndex = this.summonData.illusion?.formIndex ?? this.formIndex; return this.getSpeciesForm(ignoreOverride, true).getSpriteId( this.getGender(ignoreOverride, true) === Gender.FEMALE, formIndex, @@ -1020,7 +1021,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { back = this.isPlayer(); } - const formIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.formIndex! : this.formIndex; + const formIndex = this.summonData.illusion?.formIndex ?? this.formIndex; return this.getSpeciesForm(ignoreOverride, true).getSpriteId( this.getGender(ignoreOverride, true) === Gender.FEMALE, @@ -1045,7 +1046,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getFusionSpriteId(ignoreOverride?: boolean): string { - const fusionFormIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; + const fusionFormIndex = this.summonData.illusion?.fusionFormIndex ?? this.fusionFormIndex; return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId( this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, fusionFormIndex, @@ -1059,7 +1060,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { back = this.isPlayer(); } - const fusionFormIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; + const fusionFormIndex = this.summonData.illusion?.fusionFormIndex ?? this.fusionFormIndex; return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId( this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, @@ -1085,7 +1086,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getIconAtlasKey(ignoreOverride?: boolean): string { - const formIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; + const formIndex = this.summonData.illusion?.formIndex ?? this.formIndex; return this.getSpeciesForm(ignoreOverride, true).getIconAtlasKey( formIndex, this.shiny, @@ -1102,7 +1103,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getIconId(ignoreOverride?: boolean): string { - const formIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; + const formIndex = this.summonData.illusion?.formIndex ?? this.formIndex; return this.getSpeciesForm(ignoreOverride, true).getIconId( this.getGender(ignoreOverride, true) === Gender.FEMALE, formIndex, @@ -1112,7 +1113,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getFusionIconId(ignoreOverride?: boolean): string { - const fusionFormIndex: integer = !!this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; + const fusionFormIndex = this.summonData.illusion?.fusionFormIndex ?? this.fusionFormIndex; return this.getFusionSpeciesForm(ignoreOverride, true).getIconId( this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, fusionFormIndex, @@ -1128,7 +1129,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getSpeciesForm(ignoreOverride: boolean = false, useIllusion: boolean = false): PokemonSpeciesForm { const species: PokemonSpecies = useIllusion && this.summonData.illusion ? getPokemonSpecies(this.summonData.illusion.species) : this.species; - const formIndex: integer = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; + const formIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.formIndex : this.formIndex; if (!ignoreOverride && this.summonData.speciesForm) { return this.summonData.speciesForm; @@ -1145,10 +1146,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not. */ getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm { - const fusionSpecies: PokemonSpecies = useIllusion && !!this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; - const fusionFormIndex: integer = useIllusion && !!this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; + const fusionSpecies: PokemonSpecies = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; + const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex! : this.fusionFormIndex; - if (!ignoreOverride && this.summonData.speciesForm) { + if (!ignoreOverride && this.summonData.fusionSpeciesForm) { return this.summonData.fusionSpeciesForm; } if ( @@ -1817,9 +1818,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). */ getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { - if (useIllusion && !!this.summonData.illusion) { - return this.summonData.illusion.gender!; - } else if (!ignoreOverride && this.summonData.gender !== undefined) { + if (useIllusion && this.summonData.illusion) { + return this.summonData.illusion.gender; + } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) { return this.summonData.gender; } return this.gender; @@ -1829,9 +1830,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability). */ getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { - if (useIllusion && !!this.summonData.illusion) { - return this.summonData.illusion.fusionGender!; - } else if (!ignoreOverride && this.summonData.fusionGender !== undefined) { + if (useIllusion && this.summonData.illusion?.fusionGender) { + return this.summonData.illusion.fusionGender; + } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) { return this.summonData.fusionGender; } return this.fusionGender; @@ -1841,8 +1842,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {boolean} useIllusion - Whether we want the fake or real shininess (illusion ability). */ isShiny(useIllusion: boolean = false): boolean { - if (!useIllusion && !!this.summonData.illusion) { - return this.summonData.illusion.basePokemon?.shiny || (!!this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; + if (!useIllusion && this.summonData.illusion) { + return this.summonData.illusion.basePokemon?.shiny || (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; } else { return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); } @@ -1854,7 +1855,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise */ isDoubleShiny(useIllusion: boolean = false): boolean { - if (!useIllusion && !!this.summonData.illusion) { + if (!useIllusion && this.summonData.illusion?.basePokemon) { return this.isFusion(false) && this.summonData.illusion.basePokemon.shiny && this.summonData.illusion.basePokemon.fusionShiny; } else { return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; @@ -1865,7 +1866,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {boolean} useIllusion - Whether we want the fake or real variant (illusion ability). */ getVariant(useIllusion: boolean = false): Variant { - if (!useIllusion && !!this.summonData.illusion) { + if (!useIllusion && this.summonData.illusion) { return !this.isFusion(false) ? this.summonData.illusion.basePokemon!.variant : Math.max(this.variant, this.fusionVariant) as Variant; @@ -1878,12 +1879,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getBaseVariant(doubleShiny: boolean): Variant { if (doubleShiny) { - return !!this.summonData.illusion - ? this.summonData.illusion.basePokemon!.variant - : this.variant; - } else { - return this.getVariant(); + return this.summonData.illusion?.basePokemon?.variant ?? this.variant; } + + return this.getVariant(); } getLuck(): number { @@ -1891,18 +1890,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isFusion(useIllusion: boolean = false): boolean { - if (useIllusion && !!this.summonData.illusion) { + if (useIllusion && this.summonData.illusion) { return !!this.summonData.illusion.fusionSpecies; - } else { - return !!this.fusionSpecies; } + return !!this.fusionSpecies; } /** * @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability). */ getName(useIllusion: boolean = false): string { - return (!useIllusion && !!this.summonData.illusion && this.summonData.illusion.basePokemon) + return (!useIllusion && this.summonData.illusion?.basePokemon) ? this.summonData.illusion.basePokemon.name : this.name; } @@ -7887,8 +7885,8 @@ export class PokemonSummonData { public moveQueue: TurnMove[] = []; public tags: BattlerTag[] = []; public abilitySuppressed = false; - public speciesForm: PokemonSpeciesForm | null; - public fusionSpeciesForm: PokemonSpeciesForm; + public speciesForm: PokemonSpeciesForm | null = null; + public fusionSpeciesForm: PokemonSpeciesForm | null = null; public ability: Abilities = Abilities.NONE; public passiveAbility: Abilities = Abilities.NONE; public gender: Gender; @@ -7913,12 +7911,18 @@ export class PokemonSummonData { public waveTurnCount = 1; /** The list of moves the pokemon has used since entering the battle */ public moveHistory: TurnMove[] = []; + + constructor(source?: PokemonSummonData | Partial) { + if (!isNullOrUndefined(source)) { + Object.assign(this, source) + } + } } /** -Persistent data for a {@linkcode Pokemon}. -Resets at the start of a new battle (but not on switch). -*/ + * Persistent data for a {@linkcode Pokemon}. + * Resets at the start of a new battle (but not on switch). + */ export class PokemonBattleData { /** counts the hits the pokemon received during this battle; used for {@linkcode Moves.RAGE_FIST} */ public hitCount = 0; @@ -7926,20 +7930,29 @@ export class PokemonBattleData { public hasEatenBerry: boolean = false; /** A list of all berries eaten in this current battle; used by {@linkcode Abilities.HARVEST} */ public berriesEaten: BerryType[] = []; + + constructor(source?: PokemonBattleData | Partial) { + if (!isNullOrUndefined(source)) { + this.hitCount = source.hitCount ?? 0; + this.hasEatenBerry = source.hasEatenBerry ?? this.hasEatenBerry; + this.berriesEaten = source.berriesEaten ?? this.berriesEaten; + } + } } /** -Temporary data for a {@linkcode Pokemon}. -Resets on new wave. -*/ + * Temporary data for a {@linkcode Pokemon}. + * Resets on new wave/battle start. + */ 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. - */ + * 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; } diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index 7fde0f2aca8..02a95f71ac4 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -1,5 +1,6 @@ import Phaser from "phaser"; -import { deepCopy, getEnumValues } from "#app/utils/common"; +import { getEnumValues } from "#app/utils/common"; +import { deepCopy } from "#app/utils/data"; import pad_generic from "./configs/inputs/pad_generic"; import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES"; import pad_xbox360 from "./configs/inputs/pad_xbox360"; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 022d8a865b3..d50d20bea4e 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -280,6 +280,7 @@ export class EncounterPhase extends BattlePhase { }); if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) { + // generate modifiers for MEs, overriding prior ones as applicable regenerateModifierPoolThresholds( globalScene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, @@ -292,8 +293,8 @@ export class EncounterPhase extends BattlePhase { } } - if (battle.battleType === BattleType.TRAINER) { - globalScene.currentBattle.trainer!.genAI(globalScene.getEnemyParty()); + if (battle.battleType === BattleType.TRAINER && globalScene.currentBattle.trainer) { + globalScene.currentBattle.trainer.genAI(globalScene.getEnemyParty()); } globalScene.ui.setMode(UiMode.MESSAGE).then(() => { @@ -336,7 +337,7 @@ export class EncounterPhase extends BattlePhase { for (const pokemon of globalScene.getPlayerParty()) { // Only reset wave data, not battle data if (pokemon) { - pokemon.resetWaveData(); + pokemon.resetBattleAndWaveData(); } } diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index e3af7b0edfe..e32ce707b98 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -4,17 +4,12 @@ import type { Gender } from "../data/gender"; import { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; -import { Status } from "../data/status-effect"; -import Pokemon, { - EnemyPokemon, - type PokemonMove, - type PokemonSummonData, - type PokemonBattleData, -} from "../field/pokemon"; +import type { Status } from "../data/status-effect"; +import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonMove, PokemonSummonData } from "../field/pokemon"; import { TrainerSlot } from "#enums/trainer-slot"; import type { Variant } from "#app/sprites/variant"; import type { Biome } from "#enums/biome"; -import type { Moves } from "#enums/moves"; +import { Moves } from "#enums/moves"; import type { Species } from "#enums/species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { PokemonType } from "#enums/pokemon-type"; @@ -67,8 +62,8 @@ export default class PokemonData { public bossSegments?: number; // Effects that need to be preserved between waves - public summonData: PokemonSummonData; - public battleData: PokemonBattleData; + public summonData: PokemonSummonData = new PokemonSummonData(); + public battleData: PokemonBattleData = new PokemonBattleData(); public summonDataSpeciesFormIndex: number; public customPokemonData: CustomPokemonData; @@ -79,14 +74,14 @@ export default class PokemonData { * or JSON representation thereof. * @param source The {@linkcode Pokemon} to convert into data (or a JSON object representing one) */ - // TODO: Remove any from type signature + // TODO: Remove any from type signature in favor of 2 separate method funcs constructor(source: Pokemon | any) { const sourcePokemon = source instanceof Pokemon ? source : undefined; + this.id = source.id; - this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player; - this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species; - this.nickname = - sourcePokemon?.summonData.illusion?.basePokemon.nickname ?? sourcePokemon?.nickname ?? source.nickname; + this.player = sourcePokemon?.isPlayer() ?? source.player; + this.species = sourcePokemon?.species.speciesId ?? source.species; + this.nickname = sourcePokemon?.summonData.illusion?.basePokemon.nickname ?? source.nickname; this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0); this.abilityIndex = source.abilityIndex; this.passive = source.passive; @@ -95,60 +90,49 @@ export default class PokemonData { this.pokeball = source.pokeball; this.level = source.level; this.exp = source.exp; + this.levelExp = source.levelExp; this.gender = source.gender; + this.hp = source.hp; this.stats = source.stats; this.ivs = source.ivs; + + // TODO: Can't we move some of this verification stuff to an upgrade script? this.nature = source.nature ?? Nature.HARDY; + this.moveset = source.moveset ?? [new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL)]; + this.status = source.status ?? null; this.friendship = source.friendship ?? getPokemonSpecies(this.species).baseFriendship; this.metLevel = source.metLevel || 5; this.metBiome = source.metBiome ?? -1; this.metSpecies = source.metSpecies; this.metWave = source.metWave ?? (this.metBiome === -1 ? -1 : 0); this.luck = source.luck ?? (source.shiny ? source.variant + 1 : 0); + this.pauseEvolutions = !!source.pauseEvolutions; this.pokerus = !!source.pokerus; + this.usedTMs = source.usedTMs ?? []; + this.evoCounter = source.evoCounter ?? 0; this.teraType = source.teraType as PokemonType; this.isTerastallized = !!source.isTerastallized; this.stellarTypesBoosted = source.stellarTypesBoosted ?? []; - this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies; + this.fusionSpecies = sourcePokemon?.fusionSpecies?.speciesId ?? source.fusionSpecies; this.fusionFormIndex = source.fusionFormIndex; this.fusionAbilityIndex = source.fusionAbilityIndex; - this.fusionShiny = - sourcePokemon?.summonData.illusion?.basePokemon.fusionShiny ?? sourcePokemon?.fusionShiny ?? source.fusionShiny; - this.fusionVariant = - sourcePokemon?.summonData.illusion?.basePokemon.fusionVariant ?? - sourcePokemon?.fusionVariant ?? - source.fusionVariant; + this.fusionShiny = sourcePokemon?.summonData.illusion?.basePokemon.fusionShiny ?? source.fusionShiny; + this.fusionVariant = sourcePokemon?.summonData.illusion?.basePokemon.fusionVariant ?? source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck ?? (source.fusionShiny ? source.fusionVariant + 1 : 0); - this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); this.fusionTeraType = (source.fusionTeraType ?? 0) as PokemonType; - this.usedTMs = source.usedTMs ?? []; - - this.customPokemonData = new CustomPokemonData(source.customPokemonData); - - this.moveset = sourcePokemon?.moveset ?? source.moveset; - - this.levelExp = source.levelExp; - this.hp = source.hp; - - this.pauseEvolutions = !!source.pauseEvolutions; - this.evoCounter = source.evoCounter ?? 0; this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); this.bossSegments = source.bossSegments ?? 0; - this.status = - sourcePokemon?.status ?? - (source.status - ? new Status(source.status.effect, source.status.toxicTurnCount, source.status.sleepTurnsRemaining) - : null); - this.summonData = source.summonData; - this.battleData = source.battleData; + this.summonData = new PokemonSummonData(source.summonData); + this.battleData = new PokemonBattleData(source.battleData); + this.summonDataSpeciesFormIndex = + sourcePokemon?.summonData.speciesForm?.formIndex ?? source.summonDataSpeciesFormIndex; - this.summonDataSpeciesFormIndex = sourcePokemon - ? this.getSummonDataSpeciesFormIndex() - : source.summonDataSpeciesFormIndex; + this.customPokemonData = new CustomPokemonData(source.customPokemonData); + this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); } toPokemon(battleType?: BattleType, partyMemberIndex = 0, double = false): Pokemon { @@ -197,16 +181,4 @@ export default class PokemonData { } return ret; } - - /** - * Method to save summon data species form index - * Necessary in case the pokemon is transformed - * to reload the correct form - */ - getSummonDataSpeciesFormIndex(): number { - if (this.summonData.speciesForm) { - return this.summonData.speciesForm.formIndex; - } - return 0; - } } diff --git a/src/system/version_migration/versions/v1_9_0.ts b/src/system/version_migration/versions/v1_9_0.ts index 06e0e6de221..f0301aec230 100644 --- a/src/system/version_migration/versions/v1_9_0.ts +++ b/src/system/version_migration/versions/v1_9_0.ts @@ -1,5 +1,6 @@ import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import { loadBattlerTag } from "#app/data/battler-tags"; +import { Status } from "#app/data/status-effect"; import { PokemonMove } from "#app/field/pokemon"; import type { SessionSaveData } from "#app/system/game-data"; import PokemonData from "#app/system/pokemon-data"; @@ -14,8 +15,14 @@ import { PokeballType } from "#enums/pokeball"; const migratePartyData: SessionSaveMigrator = { version: "1.9.0", migrate: (data: SessionSaveData): void => { + // this stuff is copied straight from the constructor fwiw const mapParty = (pkmnData: PokemonData) => { - // this stuff is copied straight from the constructor fwiw + pkmnData.status &&= new Status( + pkmnData.status.effect, + pkmnData.status.toxicTurnCount, + pkmnData.status.sleepTurnsRemaining, + ); + // remove empty moves from moveset pkmnData.moveset = pkmnData.moveset.filter(m => !!m) ?? [ new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.GROWL), diff --git a/src/utils/common.ts b/src/utils/common.ts index 1501e206d2d..847f8a7ecdb 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -467,16 +467,6 @@ export function truncateString(str: string, maxLength = 10) { return str; } -/** - * Perform a deep copy of an object. - * @param values - The object to be deep copied. - * @returns A new object that is a deep copy of the input. - */ -export function deepCopy(values: object): object { - // Convert the object to a JSON string and parse it back to an object to perform a deep copy - return JSON.parse(JSON.stringify(values)); -} - /** * Convert a space-separated string into a capitalized and underscored string. * @param input - The string to be converted. diff --git a/src/utils/data.ts b/src/utils/data.ts index 026d8c475a7..abf5327b7cb 100644 --- a/src/utils/data.ts +++ b/src/utils/data.ts @@ -1,3 +1,13 @@ +/** + * Perform a deep copy of an object. + * @param values - The object to be deep copied. + * @returns A new object that is a deep copy of the input. + */ +export function deepCopy(values: object): object { + // Convert the object to a JSON string and parse it back to an object to perform a deep copy + return JSON.parse(JSON.stringify(values)); +} + /** * Deeply merge two JSON objects' common properties together. * This copies all values from `source` that match properties inside `dest`, @@ -18,16 +28,17 @@ export function deepMergeSpriteData(dest: object, source: object) { const sourceVal = source[key]; return ( - // Somewhat redundant, but makes it clear that we're explicitly interested in properties that exist in both + // 1st part somewhat redundant, but makes it clear that we're explicitly interested in properties that exist in both key in source && Array.isArray(sourceVal) === Array.isArray(destVal) && typeof sourceVal === typeof destVal ); }); for (const key of matchingKeys) { - if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) { - deepMergeSpriteData(dest[key], source[key]); - } else { + // Pure objects get recursed into; everything else gets overwritten + if (typeof source[key] !== "object" || source[key] === null || Array.isArray(source[key])) { dest[key] = source[key]; + } else { + deepMergeSpriteData(dest[key], source[key]); } } } diff --git a/test/moves/rage_fist.test.ts b/test/moves/rage_fist.test.ts index 60ddf4b5deb..57bc5ef563b 100644 --- a/test/moves/rage_fist.test.ts +++ b/test/moves/rage_fist.test.ts @@ -7,6 +7,7 @@ import type Move from "#app/data/moves/move"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { BattleType } from "#enums/battle-type"; describe("Moves - Rage Fist", () => { let phaserGame: Phaser.Game; @@ -126,18 +127,31 @@ describe("Moves - Rage Fist", () => { expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(4); }); - it("should reset hits recieved during trainer battles", async () => { - game.override.enemySpecies(Species.MAGIKARP).startingWave(19); + it("should reset hits recieved before trainer battles", async () => { + game.override.enemySpecies(Species.MAGIKARP).moveset(Moves.DOUBLE_IRON_BASH); + await game.classicMode.startBattle([Species.MARSHADOW]); - await game.classicMode.startBattle([Species.MAGIKARP]); + const marshadow = game.scene.getPlayerPokemon()!; + expect(marshadow).toBeDefined(); + // beat up a magikarp game.move.select(Moves.RAGE_FIST); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.isVictory()).toBe(true); + expect(marshadow.battleData.hitCount).toBe(2); + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + + game.override.battleType(BattleType.TRAINER); await game.toNextWave(); + expect(game.scene.lastEnemyTrainer).not.toBeNull(); + expect(marshadow.battleData.hitCount).toBe(0); + game.move.select(Moves.RAGE_FIST); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); - await game.phaseInterceptor.to("BerryPhase", false); + await game.phaseInterceptor.to("TurnEndPhase"); expect(move.calculateBattlePower).toHaveLastReturnedWith(150); }); diff --git a/test/settingMenu/rebinding_setting.test.ts b/test/settingMenu/rebinding_setting.test.ts index 45c647248c4..20a1fe51484 100644 --- a/test/settingMenu/rebinding_setting.test.ts +++ b/test/settingMenu/rebinding_setting.test.ts @@ -2,7 +2,7 @@ import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; import { getKeyWithKeycode, getKeyWithSettingName } from "#app/configs/inputs/configHandler"; import type { InterfaceConfig } from "#app/inputs-controller"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; -import { deepCopy } from "#app/utils/common"; +import { deepCopy } from "#app/utils/data"; import { Button } from "#enums/buttons"; import { Device } from "#enums/devices"; import { InGameManip } from "#test/settingMenu/helpers/inGameManip";