This commit is contained in:
Sirz Benjie 2025-07-31 23:29:35 -06:00
parent 2d3002ce2d
commit c690b19d74
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
4 changed files with 115 additions and 91 deletions

View File

@ -1,8 +1,16 @@
// biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment // biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment
import type { AbilityBattlerTag, BattlerTagTypeMap, SerializableBattlerTag, TypeBoostTag } from "#data/battler-tags"; import type {
AbilityBattlerTag,
BattlerTag,
BattlerTagTypeMap,
SerializableBattlerTag,
TypeBoostTag,
} from "#data/battler-tags";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
// biome-ignore-end lint/correctness/noUnusedImports: end // biome-ignore-end lint/correctness/noUnusedImports: end
import type { BattlerTagType } from "#enums/battler-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type";
import type { MoveId } from "#enums/move-id";
import type { MatchShape } from "#types/type-helpers";
/** /**
* Subset of {@linkcode BattlerTagType}s that restrict the use of moves. * Subset of {@linkcode BattlerTagType}s that restrict the use of moves.
@ -97,6 +105,17 @@ export type CritStageBoostTagType = BattlerTagType.CRIT_BOOST | BattlerTagType.D
/** Subset of {@linkcode BattlerTagType}s that remove one of the users' types */ /** Subset of {@linkcode BattlerTagType}s that remove one of the users' types */
export type RemovedTypeTagType = BattlerTagType.DOUBLE_SHOCKED | BattlerTagType.BURNED_UP; export type RemovedTypeTagType = BattlerTagType.DOUBLE_SHOCKED | BattlerTagType.BURNED_UP;
/**
* Subset of {@linkcode BattlerTagType}s that provide type boosts
*/
export type TypeBoostTagType = BattlerTagType.FIRE_BOOST | BattlerTagType.CHARGED;
/** Subset of {@linkcode BattlerTagType}s that boost the user's critical stage */
export type CritStageBoostTagType = BattlerTagType.CRIT_BOOST | BattlerTagType.DRAGON_CHEER;
/** Subset of {@linkcode BattlerTagType}s that remove one of the users' types */
export type RemovedTypeTagType = BattlerTagType.DOUBLE_SHOCKED | BattlerTagType.BURNED_UP;
/** /**
* Subset of {@linkcode BattlerTagType}s related to abilities that boost the highest stat. * Subset of {@linkcode BattlerTagType}s related to abilities that boost the highest stat.
*/ */
@ -122,14 +141,38 @@ export type NonSerializableBattlerTagType = Exclude<BattlerTagType, Serializable
*/ */
export type BattlerTagTypeData = Parameters< export type BattlerTagTypeData = Parameters<
BattlerTagTypeMap[keyof { BattlerTagTypeMap[keyof {
[K in keyof BattlerTagTypeMap as K extends SerializableBattlerTagType ? K : never]: BattlerTagTypeMap[K]; [K in SerializableBattlerTagType]: BattlerTagTypeMap[K];
}]["loadTag"] }]["loadTag"]
>[0]; >[0];
/** /**
* Subset of {@linkcode BattlerTagType}s whose associated `BattlerTag` adds a serializable `moveId` field * Subset of {@linkcode BattlerTagType}s whose associated `BattlerTag`
* adds no additional fields that are serialized.
*/ */
export type BattlerTagTypeWithMoveId = BattlerTagType.DISABLED | BattlerTagType.GORILLA_TACTICS | BattlerTagType.ENCORE; export type BasicBattlerTag = keyof {
[K in SerializableBattlerTagType as MatchShape<
Parameters<BattlerTagTypeMap[K]["loadTag"]>[0],
Parameters<BattlerTag["loadTag"]>[0]
> extends never
? never
: K]: any;
};
/**
* Subset of {@linkcode BattlerTagType}s whose associated `BattlerTag` adds a serializable `moveId` field
* (and no other serialized fields).
*
* @remarks
* Intended to be used to simplify type safety in the zod schemas.
*/
export type BattlerTagTypeWithMoveId = keyof {
[K in SerializableBattlerTagType as MatchShape<
Parameters<BattlerTagTypeMap[K]["loadTag"]>[0],
Parameters<BattlerTag["loadTag"]>[0] & { moveId: MoveId }
> extends never
? never
: K]: any;
};
/** /**
* Dummy, typescript-only declaration to ensure that * Dummy, typescript-only declaration to ensure that

View File

@ -44,20 +44,12 @@ export type InferKeys<O extends object, V extends ObjectValues<O>> = {
}[keyof O]; }[keyof O];
/** /**
* Type helper to construct a union type of the type of each field in an object. * Utility type to obtain the values of a given object. \
* * Functions similar to `keyof E`, except producing the values instead of the keys.
* @typeParam T - The type of the object to extract values from.
*
* @remarks * @remarks
* Can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members. * This can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members.
*
* @example
* ```ts
* type oneThruThree = ObjectValues<{ a: 1, b: 2, c: 3 }>
* // ^? 1 | 2 | 3
* ```
*/ */
export type ObjectValues<T extends object> = T[keyof T]; export type ObjectValues<E extends object> = E[keyof E];
/** /**
* Type helper that matches any `Function` type. * Type helper that matches any `Function` type.
@ -72,7 +64,6 @@ export type AnyFn = (...args: any[]) => any;
* Useful to produce a type that is roughly the same as the type of `{... obj}`, where `obj` is an instance of `T`. * Useful to produce a type that is roughly the same as the type of `{... obj}`, where `obj` is an instance of `T`.
* A couple of differences: * A couple of differences:
* - Private and protected properties are not included. * - Private and protected properties are not included.
* - Accessors with getters *are* included
* - Nested properties are not recursively extracted. For this, use {@linkcode NonFunctionPropertiesRecursive} * - Nested properties are not recursively extracted. For this, use {@linkcode NonFunctionPropertiesRecursive}
*/ */
export type NonFunctionProperties<T> = { export type NonFunctionProperties<T> = {
@ -103,3 +94,16 @@ export type AbstractConstructor<T> = abstract new (...args: any[]) => T;
export type CoerceNullPropertiesToUndefined<T extends object> = { export type CoerceNullPropertiesToUndefined<T extends object> = {
[K in keyof T]: null extends T[K] ? Exclude<T[K], null> | undefined : T[K]; [K in keyof T]: null extends T[K] ? Exclude<T[K], null> | undefined : T[K];
}; };
/**
* Type helper that takes two types and ensures that their shapes match *exactly*.
* Resolves to T1 if T1 and T2 have the same shape, otherwise resolves to `never`.
*
* @remarks
* Meant to be used in the `as` clause of a mapped type
*/
export type MatchShape<T1 extends object, T2 extends object> = T1 extends T2
? Exclude<keyof T1, keyof T2> extends never
? T1
: never
: never;

View File

@ -5,29 +5,16 @@ Schemas for commonly used primitive types, to avoid repeated instantiations.
*/ */
/** Reusable schema for a positive integer, equivalent to `z.int().positive()`.*/ /** Reusable schema for a positive integer, equivalent to `z.int().positive()`.*/
export const Z$PositiveInt = /*@__PURE__*/ z export const Z$PositiveInt = z.int().positive();
.int()
.positive();
/** Reusable schema for a non-negative integer, equivalent to `z.int().nonnegative()`.*/ /** Reusable schema for a non-negative integer, equivalent to `z.int().nonnegative()`.*/
export const Z$NonNegativeInt = /*@__PURE__*/ z export const Z$NonNegativeInt = z.int().nonnegative();
.int()
.nonnegative();
/** Reusable schema for a boolean that coerces non-boolean inputs to `false` */ /** Reusable schema for a boolean that coerces non-boolean inputs to `false` */
export const Z$BoolCatchToFalse = /*@__PURE__*/ z export const Z$BoolCatchToFalse = z.boolean().catch(false);
.boolean()
.catch(false);
/** Reusable schema for a positive number, equivalent to `z.number().positive()`. */ /** Reusable schema for a positive number, equivalent to `z.number().positive()`. */
export const Z$PositiveNumber = /*@__PURE__*/ z export const Z$PositiveNumber = z.number().positive().catch(0);
.number()
.positive()
.catch(0);
/** Reusable schema for an optional non-negative integer that coerces invalid inputs to `undefined` */ /** Reusable schema for an optional non-negative integer that coerces invalid inputs to `undefined` */
export const Z$OptionalNonNegativeIntCatchToUndef = /*@__PURE__*/ z export const Z$OptionalNonNegativeIntCatchToUndef = z.int().nonnegative().optional().catch(undefined);
.int()
.nonnegative()
.optional()
.catch(undefined);

View File

@ -1,12 +1,9 @@
import { loadBattlerTag } from "#data/battler-tags";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Z$NonNegativeInt, Z$PositiveInt } from "#system/schemas/common"; import { Z$NonNegativeInt, Z$PositiveInt } from "#system/schemas/common";
import { Z$BattlerIndex } from "#system/schemas/pokemon/battler-index"; import { Z$BattlerIndex } from "#system/schemas/pokemon/battler-index";
import { Z$Stat } from "#system/schemas/pokemon/pokemon-stats"; import { Z$Stat } from "#system/schemas/pokemon/pokemon-stats";
import type { import type { BasicBattlerTag, BattlerTagTypeWithMoveId, HighestStatBoostTagType } from "#types/battler-tags";
BattlerTagTypeWithMoveId,
HighestStatBoostTagType,
SerializableBattlerTagType,
} from "#types/battler-tags";
import type { DiscriminatedUnionFake } from "#types/schema-helpers"; import type { DiscriminatedUnionFake } from "#types/schema-helpers";
import { z } from "zod"; import { z } from "zod";
@ -15,22 +12,17 @@ Schemas for battler tags are a bit more cumbersome,
as we need to have schemas for each subclass that has a different shape. as we need to have schemas for each subclass that has a different shape.
*/ */
type BasicBattlerTag = Exclude<SerializableBattlerTagType, BattlerTagTypeWithMoveId | HighestStatBoostTagType>;
/** /**
* Zod enum of {@linkcode BattlerTagType}s whose associated `BattlerTag` adds no * Zod enum of {@linkcode BattlerTagType}s whose associated `BattlerTag` adds no
* additional fields that are serialized. * additional fields that are serialized.
*
*/ */
const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([ const BasicBattlerTag = z.literal([
BattlerTagType.RECHARGING, BattlerTagType.RECHARGING,
BattlerTagType.CONFUSED, BattlerTagType.CONFUSED,
BattlerTagType.INFATUATED, BattlerTagType.INFATUATED,
BattlerTagType.SEEDED,
BattlerTagType.NIGHTMARE, BattlerTagType.NIGHTMARE,
BattlerTagType.FRENZY, BattlerTagType.FRENZY,
BattlerTagType.CHARGING, BattlerTagType.CHARGING,
BattlerTagType.ENCORE,
BattlerTagType.INGRAIN, BattlerTagType.INGRAIN,
BattlerTagType.OCTOLOCK, BattlerTagType.OCTOLOCK,
BattlerTagType.AQUA_RING, BattlerTagType.AQUA_RING,
@ -46,12 +38,9 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.SNAP_TRAP, BattlerTagType.SNAP_TRAP,
BattlerTagType.THUNDER_CAGE, BattlerTagType.THUNDER_CAGE,
BattlerTagType.INFESTATION, BattlerTagType.INFESTATION,
BattlerTagType.STURDY,
BattlerTagType.PERISH_SONG, BattlerTagType.PERISH_SONG,
BattlerTagType.TRUANT, BattlerTagType.TRUANT,
BattlerTagType.SLOW_START, BattlerTagType.SLOW_START,
BattlerTagType.PROTOSYNTHESIS,
BattlerTagType.QUARK_DRIVE,
BattlerTagType.FLYING, BattlerTagType.FLYING,
BattlerTagType.UNDERGROUND, BattlerTagType.UNDERGROUND,
BattlerTagType.UNDERWATER, BattlerTagType.UNDERWATER,
@ -60,7 +49,6 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.CRIT_BOOST, BattlerTagType.CRIT_BOOST,
BattlerTagType.ALWAYS_CRIT, BattlerTagType.ALWAYS_CRIT,
BattlerTagType.IGNORE_ACCURACY, BattlerTagType.IGNORE_ACCURACY,
BattlerTagType.BYPASS_SLEEP,
BattlerTagType.IGNORE_FLYING, BattlerTagType.IGNORE_FLYING,
BattlerTagType.SALT_CURED, BattlerTagType.SALT_CURED,
BattlerTagType.CURSED, BattlerTagType.CURSED,
@ -70,24 +58,19 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.DESTINY_BOND, BattlerTagType.DESTINY_BOND,
BattlerTagType.ICE_FACE, BattlerTagType.ICE_FACE,
BattlerTagType.DISGUISE, BattlerTagType.DISGUISE,
BattlerTagType.STOCKPILING,
BattlerTagType.RECEIVE_DOUBLE_DAMAGE, BattlerTagType.RECEIVE_DOUBLE_DAMAGE,
BattlerTagType.ALWAYS_GET_HIT, BattlerTagType.ALWAYS_GET_HIT,
BattlerTagType.DISABLED,
BattlerTagType.SUBSTITUTE,
BattlerTagType.IGNORE_GHOST, BattlerTagType.IGNORE_GHOST,
BattlerTagType.IGNORE_DARK, BattlerTagType.IGNORE_DARK,
BattlerTagType.GULP_MISSILE_ARROKUDA, BattlerTagType.GULP_MISSILE_ARROKUDA,
BattlerTagType.GULP_MISSILE_PIKACHU, BattlerTagType.GULP_MISSILE_PIKACHU,
BattlerTagType.DRAGON_CHEER, BattlerTagType.DRAGON_CHEER,
BattlerTagType.NO_RETREAT, BattlerTagType.NO_RETREAT,
BattlerTagType.GORILLA_TACTICS,
BattlerTagType.UNBURDEN, BattlerTagType.UNBURDEN,
BattlerTagType.THROAT_CHOPPED, BattlerTagType.THROAT_CHOPPED,
BattlerTagType.TAR_SHOT, BattlerTagType.TAR_SHOT,
BattlerTagType.BURNED_UP, BattlerTagType.BURNED_UP,
BattlerTagType.DOUBLE_SHOCKED, BattlerTagType.DOUBLE_SHOCKED,
BattlerTagType.AUTOTOMIZED,
BattlerTagType.POWER_TRICK, BattlerTagType.POWER_TRICK,
BattlerTagType.HEAL_BLOCK, BattlerTagType.HEAL_BLOCK,
BattlerTagType.TORMENT, BattlerTagType.TORMENT,
@ -95,26 +78,10 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.IMPRISON, BattlerTagType.IMPRISON,
BattlerTagType.SYRUP_BOMB, BattlerTagType.SYRUP_BOMB,
BattlerTagType.TELEKINESIS, BattlerTagType.TELEKINESIS,
BattlerTagType.COMMANDED,
BattlerTagType.GRUDGE, BattlerTagType.GRUDGE,
] satisfies SerializableBattlerTagType[]); ] satisfies BasicBattlerTag[]);
/** const Z$BaseBattlerTag = z.object({
* Zod schema for {@linkcode BattlerTagLapseType} as of version 1.10
* @remarks
* - `0`: Faint
* - `1`: Move
* - `2`: Pre-Move
* - `3`: After Move
* - `4`: Move Effect
* - `5`: Turn End
* - `6`: Hit
* - `7`: After Hit
* - `8`: Custom
*/
const Z$BattlerTagLapseType = /** @__PURE__ */ z.literal([0, 1, 2, 3, 4, 5, 6, 7, 8]);
const Z$BaseBattlerTag = /** @__PURE__ */ z.object({
turnCount: Z$PositiveInt, turnCount: Z$PositiveInt,
// Source move can be `none` for tags not applied by move, so allow `0` here. // Source move can be `none` for tags not applied by move, so allow `0` here.
sourceMove: Z$NonNegativeInt.optional().catch(undefined), sourceMove: Z$NonNegativeInt.optional().catch(undefined),
@ -126,41 +93,56 @@ const Z$BaseTagWithMoveId = z.object({
moveId: Z$PositiveInt, moveId: Z$PositiveInt,
}); });
/** Subset of battler tags that have a moveID field */ /**
const Z$TagWithMoveId = /** @__PURE__ */ z.object({ * Zod schema for a basic {@linkcode BattlerTag} (i.e., one that does not have
...Z$BaseTagWithMoveId.shape, * additional fields beyond the base `BattlerTag`).
tagType: z.literal([BattlerTagType.DISABLED, BattlerTagType.GORILLA_TACTICS, BattlerTagType.ENCORE]), */
moveId: Z$PositiveInt, const Z$PlainBattlerTag = z.object({
}) as DiscriminatedUnionFake< ...Z$BaseBattlerTag.shape,
BattlerTagType.DISABLED | BattlerTagType.GORILLA_TACTICS | BattlerTagType.ENCORE, tagType: BasicBattlerTag,
typeof Z$TagWithMoveId.shape, }) as DiscriminatedUnionFake<BasicBattlerTag, typeof Z$PlainBattlerTag.shape, "tagType">;
"tagType"
>;
const Z$SeedTag = /** @__PURE__ */ z.object({ /** Subset of battler tags that have a moveID field */
const Z$TagWithMoveId = z.object({
...Z$BaseTagWithMoveId.shape,
tagType: z.literal([
BattlerTagType.DISABLED,
BattlerTagType.GORILLA_TACTICS,
BattlerTagType.ENCORE,
] satisfies BattlerTagTypeWithMoveId[]),
moveId: Z$PositiveInt,
}) as DiscriminatedUnionFake<BattlerTagTypeWithMoveId, typeof Z$TagWithMoveId.shape, "tagType">;
/**
* Zod schema for {@linkcode SeedTag} as of version 1.10.
*/
const Z$SeedTag = z.object({
...Z$BaseBattlerTag.shape, ...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.SEEDED), tagType: z.literal(BattlerTagType.SEEDED),
sourceIndex: Z$BattlerIndex, sourceIndex: Z$BattlerIndex,
}); });
const Z$BaseHighestStatBoostTag = /** @__PURE__ */ z.object({ /**
* Zod schema for {@linkcode HighestStatBoostTag} as of version 1.10.
*/
const Z$BaseHighestStatBoostTag = z.object({
...Z$BaseBattlerTag.shape, ...Z$BaseBattlerTag.shape,
stat: Z$Stat, stat: Z$Stat,
multiplier: z.number(), multiplier: z.number(),
}); });
const Z$HighestStatBoostTag = /** @__PURE__ */ z.object({ const Z$HighestStatBoostTag = z.object({
...Z$BaseHighestStatBoostTag.shape, ...Z$BaseHighestStatBoostTag.shape,
tagType: z.literal([BattlerTagType.QUARK_DRIVE, BattlerTagType.PROTOSYNTHESIS] satisfies HighestStatBoostTagType[]), tagType: z.literal([BattlerTagType.QUARK_DRIVE, BattlerTagType.PROTOSYNTHESIS] satisfies HighestStatBoostTagType[]),
}) as DiscriminatedUnionFake<HighestStatBoostTagType, typeof Z$HighestStatBoostTag.shape, "tagType">; }) as DiscriminatedUnionFake<HighestStatBoostTagType, typeof Z$HighestStatBoostTag.shape, "tagType">;
const Z$CommandedTag = /** @__PURE__ */ z.object({ const Z$CommandedTag = z.object({
...Z$BaseBattlerTag.shape, ...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.COMMANDED), tagType: z.literal(BattlerTagType.COMMANDED),
tatsugiriFormKey: z.string().catch("curly"), tatsugiriFormKey: z.string().catch("curly"),
}); });
const Z$StockpilingTag = /** @__PURE__ */ z.object({ const Z$StockpilingTag = z.object({
...Z$BaseBattlerTag.shape, ...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.STOCKPILING), tagType: z.literal(BattlerTagType.STOCKPILING),
stockpiledCount: Z$PositiveInt.catch(1), stockpiledCount: Z$PositiveInt.catch(1),
@ -170,14 +152,22 @@ const Z$StockpilingTag = /** @__PURE__ */ z.object({
}), }),
}); });
const Z$AutotomizedTag = /** @__PURE__ */ z.object({ const Z$AutotomizedTag = z.object({
...Z$BaseBattlerTag.shape, ...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.AUTOTOMIZED), tagType: z.literal(BattlerTagType.AUTOTOMIZED),
autotomizeCount: Z$PositiveInt.catch(1), autotomizeCount: Z$PositiveInt.catch(1),
}); });
const Z$SubstituteTag = /** @__PURE__ */ z.object({ const Z$SubstituteTag = z.object({
...Z$BaseBattlerTag.shape, ...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.SUBSTITUTE), tagType: z.literal(BattlerTagType.SUBSTITUTE),
hp: Z$PositiveInt, // hp: Z$PositiveInt,
}); });
export const Z$BattlerTag = z.discriminatedUnion("tagType", [Z$SubstituteTag]);
declare const t: any;
const o = Z$BattlerTag.parse(t);
const r = loadBattlerTag(Z$SubstituteTag.parse(t));