Fixed constructor (maybe), moved deepCopy and deepMergeSpriteData to own file

`common.ts` is hella bloated so seems legit
This commit is contained in:
Bertie690 2025-04-21 10:42:58 -04:00
parent 169789f25e
commit 2746a658df
11 changed files with 137 additions and 126 deletions

View File

@ -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 i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data"; import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/system/game-data";

View File

@ -1,4 +1,5 @@
export enum Biome { export enum Biome {
// TODO: Should -1 be part of the enum signature (for "unknown place")
TOWN, TOWN,
PLAINS, PLAINS,
GRASS, GRASS,

View File

@ -819,7 +819,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id))); loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id)));
// Load the assets for the species form // 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( loadPromises.push(
this.getSpeciesForm(false, useIllusion).loadAssets( this.getSpeciesForm(false, useIllusion).loadAssets(
this.getGender(useIllusion) === Gender.FEMALE, this.getGender(useIllusion) === Gender.FEMALE,
@ -836,9 +836,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
); );
} }
if (this.getFusionSpeciesForm()) { if (this.getFusionSpeciesForm()) {
const fusionFormIndex = !!this.summonData.illusion && useIllusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex; // TODO: why is fusionFormIndex using illusion if defined but fusionShiny/variant aren't?
const fusionShiny = !!this.summonData.illusion && !useIllusion ? this.summonData.illusion.basePokemon!.fusionShiny : this.fusionShiny; const fusionFormIndex = useIllusion && this.summonData.illusion ? this.summonData.illusion.fusionFormIndex : this.fusionFormIndex;
const fusionVariant = !!this.summonData.illusion && !useIllusion ? this.summonData.illusion.basePokemon!.fusionVariant : this.fusionVariant; 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( loadPromises.push(this.getFusionSpeciesForm(false, useIllusion).loadAssets(
this.getFusionGender(false, useIllusion) === Gender.FEMALE, this.getFusionGender(false, useIllusion) === Gender.FEMALE,
fusionFormIndex, fusionFormIndex,
@ -1006,7 +1007,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getSpriteId(ignoreOverride?: boolean): string { 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( return this.getSpeciesForm(ignoreOverride, true).getSpriteId(
this.getGender(ignoreOverride, true) === Gender.FEMALE, this.getGender(ignoreOverride, true) === Gender.FEMALE,
formIndex, formIndex,
@ -1020,7 +1021,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
back = this.isPlayer(); 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( return this.getSpeciesForm(ignoreOverride, true).getSpriteId(
this.getGender(ignoreOverride, true) === Gender.FEMALE, this.getGender(ignoreOverride, true) === Gender.FEMALE,
@ -1045,7 +1046,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getFusionSpriteId(ignoreOverride?: boolean): string { 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( return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId(
this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, this.getFusionGender(ignoreOverride, true) === Gender.FEMALE,
fusionFormIndex, fusionFormIndex,
@ -1059,7 +1060,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
back = this.isPlayer(); 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( return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId(
this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, this.getFusionGender(ignoreOverride, true) === Gender.FEMALE,
@ -1085,7 +1086,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getIconAtlasKey(ignoreOverride?: boolean): string { 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( return this.getSpeciesForm(ignoreOverride, true).getIconAtlasKey(
formIndex, formIndex,
this.shiny, this.shiny,
@ -1102,7 +1103,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getIconId(ignoreOverride?: boolean): string { 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( return this.getSpeciesForm(ignoreOverride, true).getIconId(
this.getGender(ignoreOverride, true) === Gender.FEMALE, this.getGender(ignoreOverride, true) === Gender.FEMALE,
formIndex, formIndex,
@ -1112,7 +1113,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getFusionIconId(ignoreOverride?: boolean): string { 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( return this.getFusionSpeciesForm(ignoreOverride, true).getIconId(
this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, this.getFusionGender(ignoreOverride, true) === Gender.FEMALE,
fusionFormIndex, fusionFormIndex,
@ -1128,7 +1129,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getSpeciesForm(ignoreOverride: boolean = false, useIllusion: boolean = false): PokemonSpeciesForm { getSpeciesForm(ignoreOverride: boolean = false, useIllusion: boolean = false): PokemonSpeciesForm {
const species: PokemonSpecies = useIllusion && this.summonData.illusion ? getPokemonSpecies(this.summonData.illusion.species) : this.species; 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) { if (!ignoreOverride && this.summonData.speciesForm) {
return 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. * @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not.
*/ */
getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm { getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm {
const fusionSpecies: PokemonSpecies = useIllusion && !!this.summonData.illusion ? this.summonData.illusion.fusionSpecies! : this.fusionSpecies!; 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 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; return this.summonData.fusionSpeciesForm;
} }
if ( 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). * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability).
*/ */
getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender {
if (useIllusion && !!this.summonData.illusion) { if (useIllusion && this.summonData.illusion) {
return this.summonData.illusion.gender!; return this.summonData.illusion.gender;
} else if (!ignoreOverride && this.summonData.gender !== undefined) { } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.gender)) {
return this.summonData.gender; return this.summonData.gender;
} }
return this.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). * @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability).
*/ */
getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender { getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender {
if (useIllusion && !!this.summonData.illusion) { if (useIllusion && this.summonData.illusion?.fusionGender) {
return this.summonData.illusion.fusionGender!; return this.summonData.illusion.fusionGender;
} else if (!ignoreOverride && this.summonData.fusionGender !== undefined) { } else if (!ignoreOverride && !isNullOrUndefined(this.summonData.fusionGender)) {
return this.summonData.fusionGender; return this.summonData.fusionGender;
} }
return this.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). * @param {boolean} useIllusion - Whether we want the fake or real shininess (illusion ability).
*/ */
isShiny(useIllusion: boolean = false): boolean { isShiny(useIllusion: boolean = false): boolean {
if (!useIllusion && !!this.summonData.illusion) { if (!useIllusion && this.summonData.illusion) {
return this.summonData.illusion.basePokemon?.shiny || (!!this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false; return this.summonData.illusion.basePokemon?.shiny || (this.summonData.illusion.fusionSpecies && this.summonData.illusion.basePokemon?.fusionShiny) || false;
} else { } else {
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny); 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 * @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise
*/ */
isDoubleShiny(useIllusion: boolean = false): boolean { 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; return this.isFusion(false) && this.summonData.illusion.basePokemon.shiny && this.summonData.illusion.basePokemon.fusionShiny;
} else { } else {
return this.isFusion(useIllusion) && this.shiny && this.fusionShiny; 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). * @param {boolean} useIllusion - Whether we want the fake or real variant (illusion ability).
*/ */
getVariant(useIllusion: boolean = false): Variant { getVariant(useIllusion: boolean = false): Variant {
if (!useIllusion && !!this.summonData.illusion) { if (!useIllusion && this.summonData.illusion) {
return !this.isFusion(false) return !this.isFusion(false)
? this.summonData.illusion.basePokemon!.variant ? this.summonData.illusion.basePokemon!.variant
: Math.max(this.variant, this.fusionVariant) as 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 { getBaseVariant(doubleShiny: boolean): Variant {
if (doubleShiny) { if (doubleShiny) {
return !!this.summonData.illusion return this.summonData.illusion?.basePokemon?.variant ?? this.variant;
? this.summonData.illusion.basePokemon!.variant
: this.variant;
} else {
return this.getVariant();
} }
return this.getVariant();
} }
getLuck(): number { getLuck(): number {
@ -1891,18 +1890,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
isFusion(useIllusion: boolean = false): boolean { isFusion(useIllusion: boolean = false): boolean {
if (useIllusion && !!this.summonData.illusion) { if (useIllusion && this.summonData.illusion) {
return !!this.summonData.illusion.fusionSpecies; 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). * @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability).
*/ */
getName(useIllusion: boolean = false): string { 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.summonData.illusion.basePokemon.name
: this.name; : this.name;
} }
@ -7887,8 +7885,8 @@ export class PokemonSummonData {
public moveQueue: TurnMove[] = []; public moveQueue: TurnMove[] = [];
public tags: BattlerTag[] = []; public tags: BattlerTag[] = [];
public abilitySuppressed = false; public abilitySuppressed = false;
public speciesForm: PokemonSpeciesForm | null; public speciesForm: PokemonSpeciesForm | null = null;
public fusionSpeciesForm: PokemonSpeciesForm; public fusionSpeciesForm: PokemonSpeciesForm | null = null;
public ability: Abilities = Abilities.NONE; public ability: Abilities = Abilities.NONE;
public passiveAbility: Abilities = Abilities.NONE; public passiveAbility: Abilities = Abilities.NONE;
public gender: Gender; public gender: Gender;
@ -7913,12 +7911,18 @@ export class PokemonSummonData {
public waveTurnCount = 1; public waveTurnCount = 1;
/** The list of moves the pokemon has used since entering the battle */ /** The list of moves the pokemon has used since entering the battle */
public moveHistory: TurnMove[] = []; public moveHistory: TurnMove[] = [];
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
if (!isNullOrUndefined(source)) {
Object.assign(this, source)
}
}
} }
/** /**
Persistent data for a {@linkcode Pokemon}. * Persistent data for a {@linkcode Pokemon}.
Resets at the start of a new battle (but not on switch). * Resets at the start of a new battle (but not on switch).
*/ */
export class PokemonBattleData { export class PokemonBattleData {
/** counts the hits the pokemon received during this battle; used for {@linkcode Moves.RAGE_FIST} */ /** counts the hits the pokemon received during this battle; used for {@linkcode Moves.RAGE_FIST} */
public hitCount = 0; public hitCount = 0;
@ -7926,20 +7930,29 @@ export class PokemonBattleData {
public hasEatenBerry: boolean = false; public hasEatenBerry: boolean = false;
/** A list of all berries eaten in this current battle; used by {@linkcode Abilities.HARVEST} */ /** A list of all berries eaten in this current battle; used by {@linkcode Abilities.HARVEST} */
public berriesEaten: BerryType[] = []; public berriesEaten: BerryType[] = [];
constructor(source?: PokemonBattleData | Partial<PokemonBattleData>) {
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}. * Temporary data for a {@linkcode Pokemon}.
Resets on new wave. * Resets on new wave/battle start.
*/ */
export class PokemonWaveData { export class PokemonWaveData {
/** whether the pokemon has endured due to a {@linkcode BattlerTagType.ENDURE_TOKEN} */ /** whether the pokemon has endured due to a {@linkcode BattlerTagType.ENDURE_TOKEN} */
public endured = false; public endured = false;
/** /**
A set of all the abilities this {@linkcode Pokemon} has used in this wave. * 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. * Used to track once per battle conditions, as well as (hopefully) by the updated AI for move effectiveness.
*/ */
public abilitiesApplied: Set<Abilities> = new Set<Abilities>; public abilitiesApplied: Set<Abilities> = new Set<Abilities>;
/** Whether the pokemon's ability has been revealed or not */
public abilityRevealed = false; public abilityRevealed = false;
} }

View File

@ -1,5 +1,6 @@
import Phaser from "phaser"; 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_generic from "./configs/inputs/pad_generic";
import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES"; import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES";
import pad_xbox360 from "./configs/inputs/pad_xbox360"; import pad_xbox360 from "./configs/inputs/pad_xbox360";

View File

@ -280,6 +280,7 @@ export class EncounterPhase extends BattlePhase {
}); });
if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) { if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) {
// generate modifiers for MEs, overriding prior ones as applicable
regenerateModifierPoolThresholds( regenerateModifierPoolThresholds(
globalScene.getEnemyField(), globalScene.getEnemyField(),
battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD,
@ -292,8 +293,8 @@ export class EncounterPhase extends BattlePhase {
} }
} }
if (battle.battleType === BattleType.TRAINER) { if (battle.battleType === BattleType.TRAINER && globalScene.currentBattle.trainer) {
globalScene.currentBattle.trainer!.genAI(globalScene.getEnemyParty()); globalScene.currentBattle.trainer.genAI(globalScene.getEnemyParty());
} }
globalScene.ui.setMode(UiMode.MESSAGE).then(() => { globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
@ -336,7 +337,7 @@ export class EncounterPhase extends BattlePhase {
for (const pokemon of globalScene.getPlayerParty()) { for (const pokemon of globalScene.getPlayerParty()) {
// Only reset wave data, not battle data // Only reset wave data, not battle data
if (pokemon) { if (pokemon) {
pokemon.resetWaveData(); pokemon.resetBattleAndWaveData();
} }
} }

View File

@ -4,17 +4,12 @@ import type { Gender } from "../data/gender";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "../data/pokemon-species";
import { Status } from "../data/status-effect"; import type { Status } from "../data/status-effect";
import Pokemon, { import Pokemon, { EnemyPokemon, PokemonBattleData, PokemonMove, PokemonSummonData } from "../field/pokemon";
EnemyPokemon,
type PokemonMove,
type PokemonSummonData,
type PokemonBattleData,
} from "../field/pokemon";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import type { Biome } from "#enums/biome"; import type { Biome } from "#enums/biome";
import type { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
@ -67,8 +62,8 @@ export default class PokemonData {
public bossSegments?: number; public bossSegments?: number;
// Effects that need to be preserved between waves // Effects that need to be preserved between waves
public summonData: PokemonSummonData; public summonData: PokemonSummonData = new PokemonSummonData();
public battleData: PokemonBattleData; public battleData: PokemonBattleData = new PokemonBattleData();
public summonDataSpeciesFormIndex: number; public summonDataSpeciesFormIndex: number;
public customPokemonData: CustomPokemonData; public customPokemonData: CustomPokemonData;
@ -79,14 +74,14 @@ export default class PokemonData {
* or JSON representation thereof. * or JSON representation thereof.
* @param source The {@linkcode Pokemon} to convert into data (or a JSON object representing one) * @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) { constructor(source: Pokemon | any) {
const sourcePokemon = source instanceof Pokemon ? source : undefined; const sourcePokemon = source instanceof Pokemon ? source : undefined;
this.id = source.id; this.id = source.id;
this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player; this.player = sourcePokemon?.isPlayer() ?? source.player;
this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species; this.species = sourcePokemon?.species.speciesId ?? source.species;
this.nickname = this.nickname = sourcePokemon?.summonData.illusion?.basePokemon.nickname ?? source.nickname;
sourcePokemon?.summonData.illusion?.basePokemon.nickname ?? sourcePokemon?.nickname ?? source.nickname;
this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0); this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0);
this.abilityIndex = source.abilityIndex; this.abilityIndex = source.abilityIndex;
this.passive = source.passive; this.passive = source.passive;
@ -95,60 +90,49 @@ export default class PokemonData {
this.pokeball = source.pokeball; this.pokeball = source.pokeball;
this.level = source.level; this.level = source.level;
this.exp = source.exp; this.exp = source.exp;
this.levelExp = source.levelExp;
this.gender = source.gender; this.gender = source.gender;
this.hp = source.hp;
this.stats = source.stats; this.stats = source.stats;
this.ivs = source.ivs; 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.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.friendship = source.friendship ?? getPokemonSpecies(this.species).baseFriendship;
this.metLevel = source.metLevel || 5; this.metLevel = source.metLevel || 5;
this.metBiome = source.metBiome ?? -1; this.metBiome = source.metBiome ?? -1;
this.metSpecies = source.metSpecies; this.metSpecies = source.metSpecies;
this.metWave = source.metWave ?? (this.metBiome === -1 ? -1 : 0); this.metWave = source.metWave ?? (this.metBiome === -1 ? -1 : 0);
this.luck = source.luck ?? (source.shiny ? source.variant + 1 : 0); this.luck = source.luck ?? (source.shiny ? source.variant + 1 : 0);
this.pauseEvolutions = !!source.pauseEvolutions;
this.pokerus = !!source.pokerus; this.pokerus = !!source.pokerus;
this.usedTMs = source.usedTMs ?? [];
this.evoCounter = source.evoCounter ?? 0;
this.teraType = source.teraType as PokemonType; this.teraType = source.teraType as PokemonType;
this.isTerastallized = !!source.isTerastallized; this.isTerastallized = !!source.isTerastallized;
this.stellarTypesBoosted = source.stellarTypesBoosted ?? []; this.stellarTypesBoosted = source.stellarTypesBoosted ?? [];
this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies; this.fusionSpecies = sourcePokemon?.fusionSpecies?.speciesId ?? source.fusionSpecies;
this.fusionFormIndex = source.fusionFormIndex; this.fusionFormIndex = source.fusionFormIndex;
this.fusionAbilityIndex = source.fusionAbilityIndex; this.fusionAbilityIndex = source.fusionAbilityIndex;
this.fusionShiny = this.fusionShiny = sourcePokemon?.summonData.illusion?.basePokemon.fusionShiny ?? source.fusionShiny;
sourcePokemon?.summonData.illusion?.basePokemon.fusionShiny ?? sourcePokemon?.fusionShiny ?? source.fusionShiny; this.fusionVariant = sourcePokemon?.summonData.illusion?.basePokemon.fusionVariant ?? source.fusionVariant;
this.fusionVariant =
sourcePokemon?.summonData.illusion?.basePokemon.fusionVariant ??
sourcePokemon?.fusionVariant ??
source.fusionVariant;
this.fusionGender = source.fusionGender; this.fusionGender = source.fusionGender;
this.fusionLuck = source.fusionLuck ?? (source.fusionShiny ? source.fusionVariant + 1 : 0); this.fusionLuck = source.fusionLuck ?? (source.fusionShiny ? source.fusionVariant + 1 : 0);
this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData);
this.fusionTeraType = (source.fusionTeraType ?? 0) as PokemonType; 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.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
this.bossSegments = source.bossSegments ?? 0; 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.summonData = new PokemonSummonData(source.summonData);
this.battleData = source.battleData; this.battleData = new PokemonBattleData(source.battleData);
this.summonDataSpeciesFormIndex =
sourcePokemon?.summonData.speciesForm?.formIndex ?? source.summonDataSpeciesFormIndex;
this.summonDataSpeciesFormIndex = sourcePokemon this.customPokemonData = new CustomPokemonData(source.customPokemonData);
? this.getSummonDataSpeciesFormIndex() this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData);
: source.summonDataSpeciesFormIndex;
} }
toPokemon(battleType?: BattleType, partyMemberIndex = 0, double = false): Pokemon { toPokemon(battleType?: BattleType, partyMemberIndex = 0, double = false): Pokemon {
@ -197,16 +181,4 @@ export default class PokemonData {
} }
return ret; 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;
}
} }

View File

@ -1,5 +1,6 @@
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
import { loadBattlerTag } from "#app/data/battler-tags"; import { loadBattlerTag } from "#app/data/battler-tags";
import { Status } from "#app/data/status-effect";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import type { SessionSaveData } from "#app/system/game-data"; import type { SessionSaveData } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
@ -14,8 +15,14 @@ import { PokeballType } from "#enums/pokeball";
const migratePartyData: SessionSaveMigrator = { const migratePartyData: SessionSaveMigrator = {
version: "1.9.0", version: "1.9.0",
migrate: (data: SessionSaveData): void => { migrate: (data: SessionSaveData): void => {
// this stuff is copied straight from the constructor fwiw
const mapParty = (pkmnData: PokemonData) => { 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) ?? [ pkmnData.moveset = pkmnData.moveset.filter(m => !!m) ?? [
new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.TACKLE),
new PokemonMove(Moves.GROWL), new PokemonMove(Moves.GROWL),

View File

@ -467,16 +467,6 @@ export function truncateString(str: string, maxLength = 10) {
return str; 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. * Convert a space-separated string into a capitalized and underscored string.
* @param input - The string to be converted. * @param input - The string to be converted.

View File

@ -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. * Deeply merge two JSON objects' common properties together.
* This copies all values from `source` that match properties inside `dest`, * 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]; const sourceVal = source[key];
return ( 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 key in source && Array.isArray(sourceVal) === Array.isArray(destVal) && typeof sourceVal === typeof destVal
); );
}); });
for (const key of matchingKeys) { for (const key of matchingKeys) {
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) { // Pure objects get recursed into; everything else gets overwritten
deepMergeSpriteData(dest[key], source[key]); if (typeof source[key] !== "object" || source[key] === null || Array.isArray(source[key])) {
} else {
dest[key] = source[key]; dest[key] = source[key];
} else {
deepMergeSpriteData(dest[key], source[key]);
} }
} }
} }

View File

@ -7,6 +7,7 @@ import type Move from "#app/data/moves/move";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { BattleType } from "#enums/battle-type";
describe("Moves - Rage Fist", () => { describe("Moves - Rage Fist", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -126,18 +127,31 @@ describe("Moves - Rage Fist", () => {
expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(4); expect(game.scene.getPlayerPokemon()?.battleData.hitCount).toBe(4);
}); });
it("should reset hits recieved during trainer battles", async () => { it("should reset hits recieved before trainer battles", async () => {
game.override.enemySpecies(Species.MAGIKARP).startingWave(19); 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); game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); 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(); await game.toNextWave();
expect(game.scene.lastEnemyTrainer).not.toBeNull();
expect(marshadow.battleData.hitCount).toBe(0);
game.move.select(Moves.RAGE_FIST); game.move.select(Moves.RAGE_FIST);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("TurnEndPhase");
expect(move.calculateBattlePower).toHaveLastReturnedWith(150); expect(move.calculateBattlePower).toHaveLastReturnedWith(150);
}); });

View File

@ -2,7 +2,7 @@ import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty";
import { getKeyWithKeycode, getKeyWithSettingName } from "#app/configs/inputs/configHandler"; import { getKeyWithKeycode, getKeyWithSettingName } from "#app/configs/inputs/configHandler";
import type { InterfaceConfig } from "#app/inputs-controller"; import type { InterfaceConfig } from "#app/inputs-controller";
import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; 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 { Button } from "#enums/buttons";
import { Device } from "#enums/devices"; import { Device } from "#enums/devices";
import { InGameManip } from "#test/settingMenu/helpers/inGameManip"; import { InGameManip } from "#test/settingMenu/helpers/inGameManip";