From fb19b2eea7c3804fb43c55cde40ff7acf2061a32 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Wed, 30 Jul 2025 22:05:20 -0400 Subject: [PATCH] Applied review suggestions and added a _wee_ bit more docs --- src/data/moves/move.ts | 2 +- .../positional-tags/load-positional-tag.ts | 8 +-- .../positional-tags/positional-tag-manager.ts | 11 +-- src/data/positional-tags/positional-tag.ts | 43 ++++++++---- src/enums/move-use-mode.ts | 70 ++++++++++--------- src/field/arena.ts | 3 +- src/phases/move-effect-phase.ts | 2 +- src/phases/positional-tag-phase.ts | 2 +- 8 files changed, 81 insertions(+), 60 deletions(-) diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index df4b5a350e8..fcb1711d733 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -3173,7 +3173,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { const useMode = args[1]; - if (useMode === MoveUseMode.TRANSPARENT) { + if (useMode === MoveUseMode.DELAYED_ATTACK) { // don't trigger if already queueing an indirect attack return false; } diff --git a/src/data/positional-tags/load-positional-tag.ts b/src/data/positional-tags/load-positional-tag.ts index 93540640981..ef3609d93e7 100644 --- a/src/data/positional-tags/load-positional-tag.ts +++ b/src/data/positional-tags/load-positional-tag.ts @@ -1,10 +1,10 @@ import { DelayedAttackTag, type PositionalTag, WishTag } from "#data/positional-tags/positional-tag"; import { PositionalTagType } from "#enums/positional-tag-type"; -import type { EnumValues } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; import type { Constructor } from "#utils/common"; /** - * Add a new {@linkcode PositionalTag} to the arena. + * Load the attributes of a {@linkcode PositionalTag}. * @param tagType - The {@linkcode PositionalTagType} to create * @param args - The arguments needed to instantize the given tag * @returns The newly created tag. @@ -16,7 +16,7 @@ export function loadPositionalTag({ ...args }: serializedPosTagMap[T]): posTagInstanceMap[T]; /** - * Add a new {@linkcode PositionalTag} to the arena. + * Load the attributes of a {@linkcode PositionalTag}. * @param tag - The {@linkcode SerializedPositionalTag} to instantiate * @returns The newly created tag. * @remarks @@ -67,4 +67,4 @@ export type serializedPosTagMap = { }; /** Union type containing all serialized {@linkcode PositionalTag}s. */ -export type SerializedPositionalTag = EnumValues; +export type SerializedPositionalTag = ObjectValues; diff --git a/src/data/positional-tags/positional-tag-manager.ts b/src/data/positional-tags/positional-tag-manager.ts index 7b8486e0a6a..7bf4d4995c6 100644 --- a/src/data/positional-tags/positional-tag-manager.ts +++ b/src/data/positional-tags/positional-tag-manager.ts @@ -7,7 +7,8 @@ import type { PositionalTagType } from "#enums/positional-tag-type"; export class PositionalTagManager { /** * Array containing all pending unactivated {@linkcode PositionalTag}s, - * sorted by order of creation (oldest first). */ + * sorted by order of creation (oldest first). + */ public tags: PositionalTag[] = []; /** @@ -30,12 +31,12 @@ export class PositionalTagManager { } /** - * Decrement turn counts of and activate all pending {@linkcode PositionalTag}s on field. + * Decrement turn counts of and trigger all pending {@linkcode PositionalTag}s on field. * @remarks - * If multiple tags trigger simultaneously, they will activate **in order of initial creation**, regardless of speed order. + * If multiple tags trigger simultaneously, they will activate in order of **initial creation**, regardless of current speed order. * (Source: [Smogon]()) */ - public triggerAllTags(): void { + public activateAllTags(): void { const leftoverTags: PositionalTag[] = []; for (const tag of this.tags) { // Check for silent removal, immediately removing invalid tags. @@ -45,7 +46,7 @@ export class PositionalTagManager { continue; } - if (!tag.shouldDisappear()) { + if (tag.shouldTrigger()) { tag.trigger(); } } diff --git a/src/data/positional-tags/positional-tag.ts b/src/data/positional-tags/positional-tag.ts index c5636e49ac4..4b6fbd036a1 100644 --- a/src/data/positional-tags/positional-tag.ts +++ b/src/data/positional-tags/positional-tag.ts @@ -14,19 +14,21 @@ import i18next from "i18next"; /** * Baseline arguments used to construct all {@linkcode PositionalTag}s, * the contents of which are serialized and used to construct new tags. \ - * Does not contain the `tagType` parameter (which is used to select the proper class constructor to use). + * Does not contain the `tagType` parameter (which is used to select the proper class constructor during tag loading). + * @privateRemarks + * All {@linkcode PositionalTag}s are intended to implement a sub-interface of this containing their respective parameters, + * and should refrain from adding extra serializable fields not contained in said interface. + * This ensures that all tags truly "become" their respective interfaces when converted to and from JSON. */ export interface PositionalTagBaseArgs { /** - * The number of turns remaining until activation. \ - * Decremented by 1 at the end of each turn until reaching 0, at which point it will {@linkcode trigger} and be removed. - * @remarks - * If this is set to any number `<0` manually (such as through the effects of {@linkcode PositionalTag.shouldDisappear | shouldDisappear}), - * this tag will be silently removed at the end of the next turn _without activating any effects_. + * The number of turns remaining until this tag's activation. \ + * Decremented by 1 at the end of each turn until reaching 0, at which point it will + * {@linkcode PositionalTag.trigger | trigger} the tag's effects and be removed. */ turnCount: number; /** - * The {@linkcode BattlerIndex} of the Pokemon targeted by the effect. + * The {@linkcode BattlerIndex} targeted by this effect. */ targetIndex: BattlerIndex; } @@ -37,8 +39,10 @@ export interface PositionalTagBaseArgs { * Multiple tags of the same kind can stack with one another, provided they are affecting different targets. */ export abstract class PositionalTag implements PositionalTagBaseArgs { + /** This tag's {@linkcode PositionalTagType | type} */ public abstract readonly tagType: PositionalTagType; // These arguments have to be public to implement the interface, but are functionally private. + // Left undocumented to inherit doc comments from the interface public turnCount: number; public targetIndex: BattlerIndex; @@ -51,16 +55,22 @@ export abstract class PositionalTag implements PositionalTagBaseArgs { public abstract trigger(): void; /** - * Check whether this tag should be removed without calling {@linkcode trigger} and triggering effects. - * @returns Whether this tag should disappear without triggering. + * Check whether this tag should be allowed to {@linkcode trigger} and activate its effects + * upon its duration elapsing. + * @returns Whether this tag should be allowed to trigger prior to being removed. */ - abstract shouldDisappear(): boolean; + public abstract shouldTrigger(): boolean; + /** + * Get the {@linkcode Pokemon} currently targeted by this tag. + * @returns The {@linkcode Pokemon} located in this tag's target position, or `undefined` if none exist in it. + */ protected getTarget(): Pokemon | undefined { return globalScene.getField()[this.targetIndex]; } } +/** Interface containing additional properties used to construct a {@linkcode DelayedAttackTag}. */ interface DelayedAttackArgs extends PositionalTagBaseArgs { /** * The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created this effect. @@ -87,7 +97,8 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs } override trigger(): void { - // Bangs are justified as the `shouldDisappear` method will queue the tag for removal if the source or target leave the field + // Bangs are justified as the `shouldTrigger` method will queue the tag for removal + // if the source or target no longer exist const source = globalScene.getPokemonById(this.sourceId)!; const target = this.getTarget()!; @@ -104,19 +115,21 @@ export class DelayedAttackTag extends PositionalTag implements DelayedAttackArgs this.sourceId, // TODO: Find an alternate method of passing the source pokemon without a source ID [this.targetIndex], allMoves[this.sourceMove], - MoveUseMode.TRANSPARENT, + MoveUseMode.DELAYED_ATTACK, ); } - override shouldDisappear(): boolean { + override shouldTrigger(): boolean { const source = globalScene.getPokemonById(this.sourceId); const target = this.getTarget(); // Silently disappear if either source or target are missing or happen to be the same pokemon // (i.e. targeting oneself) - return !source || !target || source === target || target.isFainted(); + // We also need to check for fainted targets as they don't technically leave the field until _after_ the turn ends + return !!source && !!target && source !== target && !target.isFainted(); } } +/** Interface containing arguments used to construct a {@linkcode WishTag}. */ interface WishArgs extends PositionalTagBaseArgs { /** The amount of {@linkcode Stat.HP | HP} to heal; set to 50% of the user's max HP during move usage. */ healHp: number; @@ -150,7 +163,7 @@ export class WishTag extends PositionalTag implements WishArgs { globalScene.phaseManager.unshiftNew("PokemonHealPhase", this.targetIndex, this.healHp, null, true, false); } - public shouldDisappear(): boolean { + public shouldTrigger(): boolean { // Disappear if no target or target is fainted. // The source need not exist at the time of activation (since all we need is a simple message) // TODO: Verify whether Wish shows a message if the Pokemon it would affect is KO'd on the turn of activation diff --git a/src/enums/move-use-mode.ts b/src/enums/move-use-mode.ts index 2e46ccbb04e..13ea5248853 100644 --- a/src/enums/move-use-mode.ts +++ b/src/enums/move-use-mode.ts @@ -1,7 +1,7 @@ import type { PostDancingMoveAbAttr } from "#abilities/ability"; import type { DelayedAttackAttr } from "#app/@types/move-types"; import type { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; -import type { EnumValues } from "#types/enum-types"; +import type { ObjectValues } from "#types/type-helpers"; /** * Enum representing all the possible means through which a given move can be executed. @@ -68,11 +68,13 @@ export const MoveUseMode = { * * In addition to inheriting the cancellation ignores and copy prevention from {@linkcode MoveUseMode.REFLECTED}, * transparent moves are ignored by **all forms of move usage checks** due to **not pushing to move history**. + * @todo Consider other means of implementing FS/DD than this - we currently only use it + * to prevent pushing to move history and avoid re-delaying the attack portion */ - TRANSPARENT: 6 + DELAYED_ATTACK: 6 } as const; -export type MoveUseMode = EnumValues; +export type MoveUseMode = ObjectValues; // # HELPER FUNCTIONS // Please update the markdown tables if any new `MoveUseMode`s get added. @@ -84,13 +86,14 @@ export type MoveUseMode = EnumValues; * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | - * | {@linkcode MoveUseMode.INDIRECT} | `true` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `true` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` | */ export function isVirtual(useMode: MoveUseMode): boolean { return useMode >= MoveUseMode.INDIRECT @@ -104,13 +107,14 @@ export function isVirtual(useMode: MoveUseMode): boolean { * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | - * | {@linkcode MoveUseMode.INDIRECT} | `false` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `false` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` | */ export function isIgnoreStatus(useMode: MoveUseMode): boolean { return useMode >= MoveUseMode.FOLLOW_UP; @@ -124,13 +128,14 @@ export function isIgnoreStatus(useMode: MoveUseMode): boolean { * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `true` | - * | {@linkcode MoveUseMode.INDIRECT} | `true` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `true` | + * | {@linkcode MoveUseMode.INDIRECT} | `true` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` | */ export function isIgnorePP(useMode: MoveUseMode): boolean { return useMode >= MoveUseMode.IGNORE_PP; @@ -145,14 +150,15 @@ export function isIgnorePP(useMode: MoveUseMode): boolean { * @remarks * This function is equivalent to the following truth table: * - * | Use Type | Returns | - * |------------------------------------|---------| - * | {@linkcode MoveUseMode.NORMAL} | `false` | - * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | - * | {@linkcode MoveUseMode.INDIRECT} | `false` | - * | {@linkcode MoveUseMode.FOLLOW_UP} | `false` | - * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | Use Type | Returns | + * |----------------------------------------|---------| + * | {@linkcode MoveUseMode.NORMAL} | `false` | + * | {@linkcode MoveUseMode.IGNORE_PP} | `false` | + * | {@linkcode MoveUseMode.INDIRECT} | `false` | + * | {@linkcode MoveUseMode.FOLLOW_UP} | `false` | + * | {@linkcode MoveUseMode.REFLECTED} | `true` | + * | {@linkcode MoveUseMode.DELAYED_ATTACK} | `false` | */ export function isReflected(useMode: MoveUseMode): boolean { return useMode === MoveUseMode.REFLECTED; -} +} \ No newline at end of file diff --git a/src/field/arena.ts b/src/field/arena.ts index f36c02dc335..b43b6057f1d 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -52,7 +52,7 @@ export class Arena { public bgm: string; public ignoreAbilities: boolean; public ignoringEffectSource: BattlerIndex | null; - public playerTerasUsed = 0; + public playerTerasUsed: number; /** * Saves the number of times a party pokemon faints during a arena encounter. * {@linkcode globalScene.currentBattle.enemyFaints} is the corresponding faint counter for the enemy (this resets every wave). @@ -71,6 +71,7 @@ export class Arena { this.bgm = bgm; this.trainerPool = biomeTrainerPools[biome]; this.updatePoolsForTimeOfDay(); + this.playerTerasUsed = 0; this.playerFaints = playerFaints; } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 20c19df5121..c57e0f6cead 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -331,7 +331,7 @@ export class MoveEffectPhase extends PokemonPhase { */ private postAnimCallback(user: Pokemon, targets: Pokemon[]) { // Add to the move history entry - if (this.firstHit && this.useMode !== MoveUseMode.TRANSPARENT) { + if (this.firstHit && this.useMode !== MoveUseMode.DELAYED_ATTACK) { user.pushMoveHistory(this.moveHistoryEntry); applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user }); } diff --git a/src/phases/positional-tag-phase.ts b/src/phases/positional-tag-phase.ts index 21a5ff9381b..ee2a82efb22 100644 --- a/src/phases/positional-tag-phase.ts +++ b/src/phases/positional-tag-phase.ts @@ -13,7 +13,7 @@ export class PositionalTagPhase extends Phase { public readonly phaseName = "PositionalTagPhase"; public override start(): void { - globalScene.arena.positionalTagManager.triggerAllTags(); + globalScene.arena.positionalTagManager.activateAllTags(); super.end(); return; }