mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-07 16:09:27 +02:00
[Refactor] Prevent serialization of full species in pokemon summon data
https://github.com/pagefaultgames/pokerogue/pull/6145 * Prevent serialization of entire species form in pokemon summon data * Apply suggestions from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Apply Kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
19730f9cf0
commit
c0e755c3c3
@ -75,3 +75,14 @@ export type NonFunctionPropertiesRecursive<Class> = {
|
||||
};
|
||||
|
||||
export type AbstractConstructor<T> = abstract new (...args: any[]) => T;
|
||||
|
||||
/**
|
||||
* Type helper that iterates through the fields of the type and coerces any `null` properties to `undefined` (including in union types).
|
||||
*
|
||||
* @remarks
|
||||
* This is primarily useful when an object with nullable properties wants to be serialized and have its `null`
|
||||
* properties coerced to `undefined`.
|
||||
*/
|
||||
export type CoerceNullPropertiesToUndefined<T extends object> = {
|
||||
[K in keyof T]: null extends T[K] ? Exclude<T[K], null> | undefined : T[K];
|
||||
};
|
||||
|
@ -1,18 +1,29 @@
|
||||
import { type BattlerTag, loadBattlerTag } from "#data/battler-tags";
|
||||
import { allSpecies } from "#data/data-lists";
|
||||
import type { Gender } from "#data/gender";
|
||||
import { PokemonMove } from "#data/moves/pokemon-move";
|
||||
import type { PokemonSpeciesForm } from "#data/pokemon-species";
|
||||
import { getPokemonSpeciesForm, type PokemonSpeciesForm } from "#data/pokemon-species";
|
||||
import type { TypeDamageMultiplier } from "#data/type";
|
||||
import type { AbilityId } from "#enums/ability-id";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { Nature } from "#enums/nature";
|
||||
import type { PokemonType } from "#enums/pokemon-type";
|
||||
import type { SpeciesId } from "#enums/species-id";
|
||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||
import type { IllusionData } from "#types/illusion-data";
|
||||
import type { TurnMove } from "#types/turn-move";
|
||||
import type { CoerceNullPropertiesToUndefined } from "#types/type-helpers";
|
||||
import { isNullOrUndefined } from "#utils/common";
|
||||
|
||||
/**
|
||||
* The type that {@linkcode PokemonSpeciesForm} is converted to when an object containing it serializes it.
|
||||
*/
|
||||
type SerializedSpeciesForm = {
|
||||
id: SpeciesId;
|
||||
formIdx: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Permanent data that can customize a Pokemon in non-standard ways from its Species.
|
||||
* Includes abilities, nature, changed types, etc.
|
||||
@ -41,9 +52,59 @@ export class CustomPokemonData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a pokemon species form from an object containing `id` and `formIdx` properties.
|
||||
* @param value - The value to deserialize
|
||||
* @returns The `PokemonSpeciesForm` or `null` if the fields could not be properly discerned
|
||||
*/
|
||||
function deserializePokemonSpeciesForm(value: SerializedSpeciesForm | PokemonSpeciesForm): PokemonSpeciesForm | null {
|
||||
// @ts-expect-error: We may be deserializing a PokemonSpeciesForm, but we catch later on
|
||||
let { id, formIdx } = value;
|
||||
|
||||
if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) {
|
||||
// @ts-expect-error: Typescript doesn't know that in block, `value` must be a PokemonSpeciesForm
|
||||
id = value.speciesId;
|
||||
// @ts-expect-error: Same as above (plus we are accessing a protected property)
|
||||
formIdx = value._formIndex;
|
||||
}
|
||||
// If for some reason either of these fields are null/undefined, we cannot reconstruct the species form
|
||||
if (isNullOrUndefined(id) || isNullOrUndefined(formIdx)) {
|
||||
return null;
|
||||
}
|
||||
return getPokemonSpeciesForm(id, formIdx);
|
||||
}
|
||||
|
||||
interface SerializedIllusionData extends Omit<IllusionData, "fusionSpecies"> {
|
||||
/** The id of the illusioned fusion species, or `undefined` if not a fusion */
|
||||
fusionSpecies?: SpeciesId;
|
||||
}
|
||||
|
||||
interface SerializedPokemonSummonData {
|
||||
statStages: number[];
|
||||
moveQueue: TurnMove[];
|
||||
tags: BattlerTag[];
|
||||
abilitySuppressed: boolean;
|
||||
speciesForm?: SerializedSpeciesForm;
|
||||
fusionSpeciesForm?: SerializedSpeciesForm;
|
||||
ability?: AbilityId;
|
||||
passiveAbility?: AbilityId;
|
||||
gender?: Gender;
|
||||
fusionGender?: Gender;
|
||||
stats: number[];
|
||||
moveset?: PokemonMove[];
|
||||
types: PokemonType[];
|
||||
addedType?: PokemonType;
|
||||
illusion?: SerializedIllusionData;
|
||||
illusionBroken: boolean;
|
||||
berriesEatenLast: BerryType[];
|
||||
moveHistory: TurnMove[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent in-battle data for a {@linkcode Pokemon}.
|
||||
* Resets on switch or new battle.
|
||||
*
|
||||
* @sealed
|
||||
*/
|
||||
export class PokemonSummonData {
|
||||
/** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
|
||||
@ -86,7 +147,7 @@ export class PokemonSummonData {
|
||||
*/
|
||||
public moveHistory: TurnMove[] = [];
|
||||
|
||||
constructor(source?: PokemonSummonData | Partial<PokemonSummonData>) {
|
||||
constructor(source?: PokemonSummonData | SerializedPokemonSummonData) {
|
||||
if (isNullOrUndefined(source)) {
|
||||
return;
|
||||
}
|
||||
@ -97,6 +158,30 @@ export class PokemonSummonData {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "speciesForm" || key === "fusionSpeciesForm") {
|
||||
this[key] = deserializePokemonSpeciesForm(value);
|
||||
}
|
||||
|
||||
if (key === "illusion" && typeof value === "object") {
|
||||
// Make a copy so as not to mutate provided value
|
||||
const illusionData = {
|
||||
...value,
|
||||
};
|
||||
if (!isNullOrUndefined(illusionData.fusionSpecies)) {
|
||||
switch (typeof illusionData.fusionSpecies) {
|
||||
case "object":
|
||||
illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies.speciesId];
|
||||
break;
|
||||
case "number":
|
||||
illusionData.fusionSpecies = allSpecies[illusionData.fusionSpecies];
|
||||
break;
|
||||
default:
|
||||
illusionData.fusionSpecies = undefined;
|
||||
}
|
||||
}
|
||||
this[key] = illusionData as IllusionData;
|
||||
}
|
||||
|
||||
if (key === "moveset") {
|
||||
this.moveset = value?.map((m: any) => PokemonMove.loadMove(m));
|
||||
continue;
|
||||
@ -110,6 +195,49 @@ export class PokemonSummonData {
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this PokemonSummonData to JSON, converting {@linkcode PokemonSpeciesForm} and {@linkcode IllusionData.fusionSpecies}
|
||||
* into simpler types instead of serializing all of their fields.
|
||||
*
|
||||
* @remarks
|
||||
* - `IllusionData.fusionSpecies` is serialized as just the species ID
|
||||
* - `PokemonSpeciesForm` and `PokemonSpeciesForm.fusionSpeciesForm` are converted into {@linkcode SerializedSpeciesForm} objects
|
||||
*/
|
||||
public toJSON(): SerializedPokemonSummonData {
|
||||
// Pokemon species forms are never saved, only the species ID.
|
||||
const illusion = this.illusion;
|
||||
const speciesForm = this.speciesForm;
|
||||
const fusionSpeciesForm = this.fusionSpeciesForm;
|
||||
const illusionSpeciesForm = illusion?.fusionSpecies;
|
||||
const t = {
|
||||
// the "as omit" is required to avoid TS resolving the overwritten properties to "never"
|
||||
// We coerce null to undefined in the type, as the for loop below replaces `null` with `undefined`
|
||||
...(this as Omit<
|
||||
CoerceNullPropertiesToUndefined<PokemonSummonData>,
|
||||
"speciesForm" | "fusionSpeciesForm" | "illusion"
|
||||
>),
|
||||
speciesForm: isNullOrUndefined(speciesForm)
|
||||
? undefined
|
||||
: { id: speciesForm.speciesId, formIdx: speciesForm.formIndex },
|
||||
fusionSpeciesForm: isNullOrUndefined(fusionSpeciesForm)
|
||||
? undefined
|
||||
: { id: fusionSpeciesForm.speciesId, formIdx: fusionSpeciesForm.formIndex },
|
||||
illusion: isNullOrUndefined(illusion)
|
||||
? undefined
|
||||
: {
|
||||
...(this.illusion as Omit<typeof illusion, "fusionSpecies">),
|
||||
fusionSpecies: illusionSpeciesForm?.speciesId,
|
||||
},
|
||||
};
|
||||
// Replace `null` with `undefined`, as `undefined` never gets serialized
|
||||
for (const [key, value] of Object.entries(t)) {
|
||||
if (value === null) {
|
||||
t[key] = undefined;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Merge this inside `summmonData` but exclude from save if/when a save data serializer is added
|
||||
|
Loading…
Reference in New Issue
Block a user