mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-06 23:49:26 +02:00
[Misc] Improve type signatures of serialized arena/battler tags (#6180)
* Improve type signatures of serialized arena/battler tags * Minor adjustments to tsdocs from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com>
This commit is contained in:
parent
1ae69a4183
commit
9475505cd2
@ -1,6 +1,5 @@
|
||||
import type { ArenaTagTypeMap } from "#data/arena-tag";
|
||||
import type { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import type { NonFunctionProperties } from "#types/type-helpers";
|
||||
|
||||
/** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */
|
||||
export type ArenaTrapTagType =
|
||||
@ -30,13 +29,13 @@ export type NonSerializableArenaTagType = ArenaTagType.NONE | TurnProtectArenaTa
|
||||
export type SerializableArenaTagType = Exclude<ArenaTagType, NonSerializableArenaTagType>;
|
||||
|
||||
/**
|
||||
* Type-safe representation of the serializable data of an ArenaTag
|
||||
* Type-safe representation of an arbitrary, serialized Arena Tag
|
||||
*/
|
||||
export type ArenaTagTypeData = NonFunctionProperties<
|
||||
export type ArenaTagTypeData = Parameters<
|
||||
ArenaTagTypeMap[keyof {
|
||||
[K in keyof ArenaTagTypeMap as K extends SerializableArenaTagType ? K : never]: ArenaTagTypeMap[K];
|
||||
}]
|
||||
>;
|
||||
}]["loadTag"]
|
||||
>[0];
|
||||
|
||||
/** Dummy, typescript-only declaration to ensure that
|
||||
* {@linkcode ArenaTagTypeMap} has a map for all ArenaTagTypes.
|
||||
|
@ -108,6 +108,15 @@ export type SerializableBattlerTagType = keyof {
|
||||
*/
|
||||
export type NonSerializableBattlerTagType = Exclude<BattlerTagType, SerializableBattlerTagType>;
|
||||
|
||||
/**
|
||||
* Type-safe representation of an arbitrary, serialized Battler Tag
|
||||
*/
|
||||
export type BattlerTagTypeData = Parameters<
|
||||
BattlerTagTypeMap[keyof {
|
||||
[K in keyof BattlerTagTypeMap as K extends SerializableBattlerTagType ? K : never]: BattlerTagTypeMap[K];
|
||||
}]["loadTag"]
|
||||
>[0];
|
||||
|
||||
/**
|
||||
* Dummy, typescript-only declaration to ensure that
|
||||
* {@linkcode BattlerTagTypeMap} has an entry for all `BattlerTagType`s.
|
||||
|
@ -26,38 +26,58 @@ import type {
|
||||
ArenaTrapTagType,
|
||||
SerializableArenaTagType,
|
||||
} from "#types/arena-tags";
|
||||
import type { Mutable, NonFunctionProperties } from "#types/type-helpers";
|
||||
import type { Mutable } from "#types/type-helpers";
|
||||
import { BooleanHolder, isNullOrUndefined, NumberHolder, toDmgValue } from "#utils/common";
|
||||
import i18next from "i18next";
|
||||
|
||||
/*
|
||||
ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon).
|
||||
Examples include (but are not limited to)
|
||||
- Cross-turn effects that persist even if the user/target switches out, such as Wish, Future Sight, and Happy Hour
|
||||
- Effects that are applied to a specific side of the field, such as Crafty Shield, Reflect, and Spikes
|
||||
- Field-Effects, like Gravity and Trick Room
|
||||
|
||||
Any arena tag that persists across turns *must* extend from `SerializableArenaTag` in the class definition signature.
|
||||
|
||||
Serializable ArenaTags have strict rules for their fields.
|
||||
These rules ensure that only the data necessary to reconstruct the tag is serialized, and that the
|
||||
session loader is able to deserialize saved tags correctly.
|
||||
|
||||
If the data is static (i.e. it is always the same for all instances of the class, such as the
|
||||
type that is weakened by Mud Sport/Water Sport), then it must not be defined as a field, and must
|
||||
instead be defined as a getter.
|
||||
A static property is also acceptable, though static properties are less ergonomic with inheritance.
|
||||
|
||||
If the data is mutable (i.e. it can change over the course of the tag's lifetime), then it *must*
|
||||
be defined as a field, and it must be set in the `loadTag` method.
|
||||
Such fields cannot be marked as `private/protected`, as if they were, typescript would omit them from
|
||||
types that are based off of the class, namely, `ArenaTagTypeData`. It is preferrable to trade the
|
||||
type-safety of private/protected fields for the type safety when deserializing arena tags from save data.
|
||||
|
||||
For data that is mutable only within a turn (e.g. SuppressAbilitiesTag's beingRemoved field),
|
||||
where it does not make sense to be serialized, the field should use ES2020's private field syntax (a `#` prepended to the field name).
|
||||
If the field should be accessible outside of the class, then a public getter should be used.
|
||||
*/
|
||||
/**
|
||||
* @module
|
||||
* ArenaTags are are meant for effects that are tied to the arena (as opposed to a specific pokemon).
|
||||
* Examples include (but are not limited to)
|
||||
* - Cross-turn effects that persist even if the user/target switches out, such as Wish, Future Sight, and Happy Hour
|
||||
* - Effects that are applied to a specific side of the field, such as Crafty Shield, Reflect, and Spikes
|
||||
* - Field-Effects, like Gravity and Trick Room
|
||||
*
|
||||
* Any arena tag that persists across turns *must* extend from `SerializableArenaTag` in the class definition signature.
|
||||
*
|
||||
* Serializable ArenaTags have strict rules for their fields.
|
||||
* These rules ensure that only the data necessary to reconstruct the tag is serialized, and that the
|
||||
* session loader is able to deserialize saved tags correctly.
|
||||
*
|
||||
* If the data is static (i.e. it is always the same for all instances of the class, such as the
|
||||
* type that is weakened by Mud Sport/Water Sport), then it must not be defined as a field, and must
|
||||
* instead be defined as a getter.
|
||||
* A static property is also acceptable, though static properties are less ergonomic with inheritance.
|
||||
*
|
||||
* If the data is mutable (i.e. it can change over the course of the tag's lifetime), then it *must*
|
||||
* be defined as a field, and it must be set in the `loadTag` method.
|
||||
* Such fields cannot be marked as `private`/`protected`; if they were, Typescript would omit them from
|
||||
* types that are based off of the class, namely, `ArenaTagTypeData`. It is preferrable to trade the
|
||||
* type-safety of private/protected fields for the type safety when deserializing arena tags from save data.
|
||||
*
|
||||
* For data that is mutable only within a turn (e.g. SuppressAbilitiesTag's beingRemoved field),
|
||||
* where it does not make sense to be serialized, the field should use ES2020's
|
||||
* [private field syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements#private_fields).
|
||||
* If the field should be accessible outside of the class, then a public getter should be used.
|
||||
*
|
||||
* If any new serializable fields *are* added, then the class *must* override the
|
||||
* `loadTag` method to set the new fields. Its signature *must* match the example below,
|
||||
* ```
|
||||
* class ExampleTag extends SerializableArenaTag {
|
||||
* // Example, if we add 2 new fields that should be serialized:
|
||||
* public a: string;
|
||||
* public b: number;
|
||||
* // Then we must also define a loadTag method with one of the following signatures
|
||||
* public override loadTag(source: BaseArenaTag & Pick<ExampleTag, "tagType" | "a" | "b"): void;
|
||||
* public override loadTag<const T extends this>(source: BaseArenaTag & Pick<T, "tagType" | "a" | "b">): void;
|
||||
* public override loadTag(source: NonFunctionProperties<ExampleTag>): void;
|
||||
* }
|
||||
* ```
|
||||
* Notes
|
||||
* - If the class has any subclasses, then the second form of `loadTag` *must* be used.
|
||||
* - The third form *must not* be used if the class has any getters, as typescript would expect such fields to be
|
||||
* present in `source`.
|
||||
*/
|
||||
|
||||
/** Interface containing the serializable fields of ArenaTagData. */
|
||||
interface BaseArenaTag {
|
||||
@ -141,9 +161,9 @@ export abstract class ArenaTag implements BaseArenaTag {
|
||||
/**
|
||||
* When given a arena tag or json representing one, load the data for it.
|
||||
* This is meant to be inherited from by any arena tag with custom attributes
|
||||
* @param source - The {@linkcode BaseArenaTag} being loaded
|
||||
* @param source - The arena tag being loaded
|
||||
*/
|
||||
loadTag(source: BaseArenaTag): void {
|
||||
loadTag<const T extends this>(source: BaseArenaTag & Pick<T, "tagType">): void {
|
||||
this.turnCount = source.turnCount;
|
||||
this.sourceMove = source.sourceMove;
|
||||
this.sourceId = source.sourceId;
|
||||
@ -646,7 +666,9 @@ class WishTag extends SerializableArenaTag {
|
||||
}
|
||||
}
|
||||
|
||||
override loadTag(source: NonFunctionProperties<WishTag>): void {
|
||||
public override loadTag<const T extends this>(
|
||||
source: BaseArenaTag & Pick<T, "tagType" | "healHp" | "sourceName" | "battlerIndex">,
|
||||
): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<this>).battlerIndex = source.battlerIndex;
|
||||
(this as Mutable<this>).healHp = source.healHp;
|
||||
@ -813,7 +835,7 @@ export abstract class ArenaTrapTag extends SerializableArenaTag {
|
||||
: Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
||||
}
|
||||
|
||||
loadTag(source: NonFunctionProperties<ArenaTrapTag>): void {
|
||||
public loadTag<T extends this>(source: BaseArenaTag & Pick<T, "tagType" | "layers" | "maxLayers">): void {
|
||||
super.loadTag(source);
|
||||
this.layers = source.layers;
|
||||
this.maxLayers = source.maxLayers;
|
||||
@ -1581,7 +1603,7 @@ export class SuppressAbilitiesTag extends SerializableArenaTag {
|
||||
this.#beingRemoved = false;
|
||||
}
|
||||
|
||||
public override loadTag(source: NonFunctionProperties<SuppressAbilitiesTag>): void {
|
||||
public override loadTag(source: BaseArenaTag & Pick<SuppressAbilitiesTag, "tagType" | "sourceCount">): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<this>).sourceCount = source.sourceCount;
|
||||
}
|
||||
|
@ -69,6 +69,24 @@ import { BooleanHolder, coerceArray, getFrameMs, isNullOrUndefined, NumberHolder
|
||||
* be declared as a public readonly propety. Then, in the `loadTag` method (or any method inside the class that needs to adjust the property)
|
||||
* use `(this as Mutable<this>).propertyName = value;`
|
||||
* These rules ensure that Typescript is aware of the shape of the serialized version of the class.
|
||||
*
|
||||
* If any new serializable fields *are* added, then the class *must* override the
|
||||
* `loadTag` method to set the new fields. Its signature *must* match the example below:
|
||||
* ```
|
||||
* class ExampleTag extends SerializableBattlerTag {
|
||||
* // Example, if we add 2 new fields that should be serialized:
|
||||
* public a: string;
|
||||
* public b: number;
|
||||
* // Then we must also define a loadTag method with one of the following signatures
|
||||
* public override loadTag(source: BaseBattlerTag & Pick<ExampleTag, "tagType" | "a" | "b"): void;
|
||||
* public override loadTag<const T extends this>(source: BaseBattlerTag & Pick<T, "tagType" | "a" | "b">): void;
|
||||
* public override loadTag(source: NonFunctionProperties<ExampleTag>): void;
|
||||
* }
|
||||
* ```
|
||||
* Notes
|
||||
* - If the class has any subclasses, then the second form of `loadTag` *must* be used.
|
||||
* - The third form *must not* be used if the class has any getters, as typescript would expect such fields to be
|
||||
* present in `source`.
|
||||
*/
|
||||
|
||||
/** Interface containing the serializable fields of BattlerTag */
|
||||
@ -168,7 +186,7 @@ export class BattlerTag implements BaseBattlerTag {
|
||||
* Should be inherited from by any battler tag with custom attributes.
|
||||
* @param source - An object containing the fields needed to reconstruct this tag.
|
||||
*/
|
||||
loadTag(source: BaseBattlerTag): void {
|
||||
public loadTag<const T extends this>(source: BaseBattlerTag & Pick<T, "tagType">): void {
|
||||
this.turnCount = source.turnCount;
|
||||
this.sourceMove = source.sourceMove;
|
||||
this.sourceId = source.sourceId;
|
||||
@ -386,7 +404,7 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
||||
});
|
||||
}
|
||||
|
||||
override loadTag(source: NonFunctionProperties<DisabledTag>): void {
|
||||
public override loadTag(source: BaseBattlerTag & Pick<DisabledTag, "tagType" | "moveId">): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<this>).moveId = source.moveId;
|
||||
}
|
||||
@ -437,7 +455,7 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
||||
* Loads the Gorilla Tactics Battler Tag along with its unique class variable moveId
|
||||
* @param source - Object containing the fields needed to reconstruct this tag.
|
||||
*/
|
||||
override loadTag(source: NonFunctionProperties<GorillaTacticsTag>): void {
|
||||
public override loadTag(source: BaseBattlerTag & Pick<GorillaTacticsTag, "tagType" | "moveId">): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<GorillaTacticsTag>).moveId = source.moveId;
|
||||
}
|
||||
@ -971,6 +989,12 @@ export class InfatuatedTag extends SerializableBattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Battler tag for the "Seeded" effect applied by {@linkcode MoveId.LEECH_SEED | Leech Seed} and
|
||||
* {@linkcode MoveId.SAPPY_SEED | Sappy Seed}
|
||||
*
|
||||
* @sealed
|
||||
*/
|
||||
export class SeedTag extends SerializableBattlerTag {
|
||||
public override readonly tagType = BattlerTagType.SEEDED;
|
||||
public readonly sourceIndex: BattlerIndex;
|
||||
@ -983,7 +1007,7 @@ export class SeedTag extends SerializableBattlerTag {
|
||||
* When given a battler tag or json representing one, load the data for it.
|
||||
* @param source - An object containing the fields needed to reconstruct this tag.
|
||||
*/
|
||||
override loadTag(source: NonFunctionProperties<SeedTag>): void {
|
||||
public override loadTag(source: BaseBattlerTag & Pick<SeedTag, "tagType" | "sourceIndex">): void {
|
||||
super.loadTag(source);
|
||||
(this as Mutable<this>).sourceIndex = source.sourceIndex;
|
||||
}
|
||||
@ -1194,6 +1218,7 @@ export class FrenzyTag extends SerializableBattlerTag {
|
||||
/**
|
||||
* Applies the effects of {@linkcode MoveId.ENCORE} onto the target Pokemon.
|
||||
* Encore forces the target Pokemon to use its most-recent move for 3 turns.
|
||||
* @sealed
|
||||
*/
|
||||
export class EncoreTag extends MoveRestrictionBattlerTag {
|
||||
public override readonly tagType = BattlerTagType.ENCORE;
|
||||
@ -1210,7 +1235,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
||||
);
|
||||
}
|
||||
|
||||
override loadTag(source: NonFunctionProperties<EncoreTag>): void {
|
||||
public override loadTag(source: NonFunctionProperties<EncoreTag>): void {
|
||||
super.loadTag(source);
|
||||
this.moveId = source.moveId;
|
||||
}
|
||||
@ -2085,7 +2110,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
|
||||
* When given a battler tag or json representing one, load the data for it.
|
||||
* @param source - An object containing the fields needed to reconstruct this tag.
|
||||
*/
|
||||
loadTag(source: NonFunctionProperties<HighestStatBoostTag>): void {
|
||||
public override loadTag<T extends this>(source: BaseBattlerTag & Pick<T, "tagType" | "stat" | "multiplier">): void {
|
||||
super.loadTag(source);
|
||||
this.stat = source.stat as Stat;
|
||||
this.multiplier = source.multiplier;
|
||||
@ -2564,6 +2589,7 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag {
|
||||
/**
|
||||
* Battler tag indicating a Tatsugiri with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander}
|
||||
* has entered the tagged Pokemon's mouth.
|
||||
* @sealed
|
||||
*/
|
||||
export class CommandedTag extends SerializableBattlerTag {
|
||||
public override readonly tagType = BattlerTagType.COMMANDED;
|
||||
@ -2607,6 +2633,7 @@ export class CommandedTag extends SerializableBattlerTag {
|
||||
* - Stat changes on removal of (all) stacks.
|
||||
* - Removing stacks decreases DEF and SPDEF, independently, by one stage for each stack that successfully changed
|
||||
* the stat when added.
|
||||
* @sealed
|
||||
*/
|
||||
export class StockpilingTag extends SerializableBattlerTag {
|
||||
public override readonly tagType = BattlerTagType.STOCKPILING;
|
||||
@ -2632,7 +2659,7 @@ export class StockpilingTag extends SerializableBattlerTag {
|
||||
}
|
||||
};
|
||||
|
||||
override loadTag(source: NonFunctionProperties<StockpilingTag>): void {
|
||||
public override loadTag(source: NonFunctionProperties<StockpilingTag>): void {
|
||||
super.loadTag(source);
|
||||
this.stockpiledCount = source.stockpiledCount || 0;
|
||||
this.statChangeCounts = {
|
||||
@ -2979,7 +3006,7 @@ export class AutotomizedTag extends SerializableBattlerTag {
|
||||
this.onAdd(pokemon);
|
||||
}
|
||||
|
||||
loadTag(source: NonFunctionProperties<AutotomizedTag>): void {
|
||||
public override loadTag(source: NonFunctionProperties<AutotomizedTag>): void {
|
||||
super.loadTag(source);
|
||||
this.autotomizeCount = source.autotomizeCount;
|
||||
}
|
||||
@ -3129,7 +3156,7 @@ export class SubstituteTag extends SerializableBattlerTag {
|
||||
* When given a battler tag or json representing one, load the data for it.
|
||||
* @param source - An object containing the necessary properties to load the tag
|
||||
*/
|
||||
override loadTag(source: NonFunctionProperties<SubstituteTag>): void {
|
||||
public override loadTag(source: BaseBattlerTag & Pick<SubstituteTag, "tagType" | "hp">): void {
|
||||
super.loadTag(source);
|
||||
this.hp = source.hp;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user