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
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";
// biome-ignore-end lint/correctness/noUnusedImports: end
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.
@ -97,6 +105,17 @@ export type CritStageBoostTagType = BattlerTagType.CRIT_BOOST | BattlerTagType.D
/** 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 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.
*/
@ -122,14 +141,38 @@ export type NonSerializableBattlerTagType = Exclude<BattlerTagType, Serializable
*/
export type BattlerTagTypeData = Parameters<
BattlerTagTypeMap[keyof {
[K in keyof BattlerTagTypeMap as K extends SerializableBattlerTagType ? K : never]: BattlerTagTypeMap[K];
[K in SerializableBattlerTagType]: BattlerTagTypeMap[K];
}]["loadTag"]
>[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

View File

@ -44,20 +44,12 @@ export type InferKeys<O extends object, V extends ObjectValues<O>> = {
}[keyof O];
/**
* Type helper to construct a union type of the type of each field in an object.
*
* @typeParam T - The type of the object to extract values from.
*
* Utility type to obtain the values of a given object. \
* Functions similar to `keyof E`, except producing the values instead of the keys.
* @remarks
* 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
* ```
* This can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members.
*/
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.
@ -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`.
* A couple of differences:
* - Private and protected properties are not included.
* - Accessors with getters *are* included
* - Nested properties are not recursively extracted. For this, use {@linkcode NonFunctionPropertiesRecursive}
*/
export type NonFunctionProperties<T> = {
@ -103,3 +94,16 @@ export type AbstractConstructor<T> = abstract new (...args: any[]) => T;
export type CoerceNullPropertiesToUndefined<T extends object> = {
[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()`.*/
export const Z$PositiveInt = /*@__PURE__*/ z
.int()
.positive();
export const Z$PositiveInt = z.int().positive();
/** Reusable schema for a non-negative integer, equivalent to `z.int().nonnegative()`.*/
export const Z$NonNegativeInt = /*@__PURE__*/ z
.int()
.nonnegative();
export const Z$NonNegativeInt = z.int().nonnegative();
/** Reusable schema for a boolean that coerces non-boolean inputs to `false` */
export const Z$BoolCatchToFalse = /*@__PURE__*/ z
.boolean()
.catch(false);
export const Z$BoolCatchToFalse = z.boolean().catch(false);
/** Reusable schema for a positive number, equivalent to `z.number().positive()`. */
export const Z$PositiveNumber = /*@__PURE__*/ z
.number()
.positive()
.catch(0);
export const Z$PositiveNumber = z.number().positive().catch(0);
/** Reusable schema for an optional non-negative integer that coerces invalid inputs to `undefined` */
export const Z$OptionalNonNegativeIntCatchToUndef = /*@__PURE__*/ z
.int()
.nonnegative()
.optional()
.catch(undefined);
export const Z$OptionalNonNegativeIntCatchToUndef = z.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 { Z$NonNegativeInt, Z$PositiveInt } from "#system/schemas/common";
import { Z$BattlerIndex } from "#system/schemas/pokemon/battler-index";
import { Z$Stat } from "#system/schemas/pokemon/pokemon-stats";
import type {
BattlerTagTypeWithMoveId,
HighestStatBoostTagType,
SerializableBattlerTagType,
} from "#types/battler-tags";
import type { BasicBattlerTag, BattlerTagTypeWithMoveId, HighestStatBoostTagType } from "#types/battler-tags";
import type { DiscriminatedUnionFake } from "#types/schema-helpers";
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.
*/
type BasicBattlerTag = Exclude<SerializableBattlerTagType, BattlerTagTypeWithMoveId | HighestStatBoostTagType>;
/**
* Zod enum of {@linkcode BattlerTagType}s whose associated `BattlerTag` adds no
* additional fields that are serialized.
*
*/
const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
const BasicBattlerTag = z.literal([
BattlerTagType.RECHARGING,
BattlerTagType.CONFUSED,
BattlerTagType.INFATUATED,
BattlerTagType.SEEDED,
BattlerTagType.NIGHTMARE,
BattlerTagType.FRENZY,
BattlerTagType.CHARGING,
BattlerTagType.ENCORE,
BattlerTagType.INGRAIN,
BattlerTagType.OCTOLOCK,
BattlerTagType.AQUA_RING,
@ -46,12 +38,9 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.SNAP_TRAP,
BattlerTagType.THUNDER_CAGE,
BattlerTagType.INFESTATION,
BattlerTagType.STURDY,
BattlerTagType.PERISH_SONG,
BattlerTagType.TRUANT,
BattlerTagType.SLOW_START,
BattlerTagType.PROTOSYNTHESIS,
BattlerTagType.QUARK_DRIVE,
BattlerTagType.FLYING,
BattlerTagType.UNDERGROUND,
BattlerTagType.UNDERWATER,
@ -60,7 +49,6 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.CRIT_BOOST,
BattlerTagType.ALWAYS_CRIT,
BattlerTagType.IGNORE_ACCURACY,
BattlerTagType.BYPASS_SLEEP,
BattlerTagType.IGNORE_FLYING,
BattlerTagType.SALT_CURED,
BattlerTagType.CURSED,
@ -70,24 +58,19 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.DESTINY_BOND,
BattlerTagType.ICE_FACE,
BattlerTagType.DISGUISE,
BattlerTagType.STOCKPILING,
BattlerTagType.RECEIVE_DOUBLE_DAMAGE,
BattlerTagType.ALWAYS_GET_HIT,
BattlerTagType.DISABLED,
BattlerTagType.SUBSTITUTE,
BattlerTagType.IGNORE_GHOST,
BattlerTagType.IGNORE_DARK,
BattlerTagType.GULP_MISSILE_ARROKUDA,
BattlerTagType.GULP_MISSILE_PIKACHU,
BattlerTagType.DRAGON_CHEER,
BattlerTagType.NO_RETREAT,
BattlerTagType.GORILLA_TACTICS,
BattlerTagType.UNBURDEN,
BattlerTagType.THROAT_CHOPPED,
BattlerTagType.TAR_SHOT,
BattlerTagType.BURNED_UP,
BattlerTagType.DOUBLE_SHOCKED,
BattlerTagType.AUTOTOMIZED,
BattlerTagType.POWER_TRICK,
BattlerTagType.HEAL_BLOCK,
BattlerTagType.TORMENT,
@ -95,26 +78,10 @@ const Z$BaseBattlerTags = /** @__PURE__ */ z.literal([
BattlerTagType.IMPRISON,
BattlerTagType.SYRUP_BOMB,
BattlerTagType.TELEKINESIS,
BattlerTagType.COMMANDED,
BattlerTagType.GRUDGE,
] satisfies SerializableBattlerTagType[]);
] satisfies BasicBattlerTag[]);
/**
* 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({
const Z$BaseBattlerTag = z.object({
turnCount: Z$PositiveInt,
// Source move can be `none` for tags not applied by move, so allow `0` here.
sourceMove: Z$NonNegativeInt.optional().catch(undefined),
@ -126,41 +93,56 @@ const Z$BaseTagWithMoveId = z.object({
moveId: Z$PositiveInt,
});
/** Subset of battler tags that have a moveID field */
const Z$TagWithMoveId = /** @__PURE__ */ z.object({
...Z$BaseTagWithMoveId.shape,
tagType: z.literal([BattlerTagType.DISABLED, BattlerTagType.GORILLA_TACTICS, BattlerTagType.ENCORE]),
moveId: Z$PositiveInt,
}) as DiscriminatedUnionFake<
BattlerTagType.DISABLED | BattlerTagType.GORILLA_TACTICS | BattlerTagType.ENCORE,
typeof Z$TagWithMoveId.shape,
"tagType"
>;
/**
* Zod schema for a basic {@linkcode BattlerTag} (i.e., one that does not have
* additional fields beyond the base `BattlerTag`).
*/
const Z$PlainBattlerTag = z.object({
...Z$BaseBattlerTag.shape,
tagType: BasicBattlerTag,
}) as DiscriminatedUnionFake<BasicBattlerTag, typeof Z$PlainBattlerTag.shape, "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,
tagType: z.literal(BattlerTagType.SEEDED),
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,
stat: Z$Stat,
multiplier: z.number(),
});
const Z$HighestStatBoostTag = /** @__PURE__ */ z.object({
const Z$HighestStatBoostTag = z.object({
...Z$BaseHighestStatBoostTag.shape,
tagType: z.literal([BattlerTagType.QUARK_DRIVE, BattlerTagType.PROTOSYNTHESIS] satisfies HighestStatBoostTagType[]),
}) as DiscriminatedUnionFake<HighestStatBoostTagType, typeof Z$HighestStatBoostTag.shape, "tagType">;
const Z$CommandedTag = /** @__PURE__ */ z.object({
const Z$CommandedTag = z.object({
...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.COMMANDED),
tatsugiriFormKey: z.string().catch("curly"),
});
const Z$StockpilingTag = /** @__PURE__ */ z.object({
const Z$StockpilingTag = z.object({
...Z$BaseBattlerTag.shape,
tagType: z.literal(BattlerTagType.STOCKPILING),
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,
tagType: z.literal(BattlerTagType.AUTOTOMIZED),
autotomizeCount: Z$PositiveInt.catch(1),
});
const Z$SubstituteTag = /** @__PURE__ */ z.object({
const Z$SubstituteTag = z.object({
...Z$BaseBattlerTag.shape,
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));