Applied review suggestions and added a _wee_ bit more docs

This commit is contained in:
Bertie690 2025-07-30 22:05:20 -04:00
parent 5d89e703f7
commit fb19b2eea7
8 changed files with 81 additions and 60 deletions

View File

@ -3173,7 +3173,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: [overridden: BooleanHolder, useMode: MoveUseMode]): boolean {
const useMode = args[1]; const useMode = args[1];
if (useMode === MoveUseMode.TRANSPARENT) { if (useMode === MoveUseMode.DELAYED_ATTACK) {
// don't trigger if already queueing an indirect attack // don't trigger if already queueing an indirect attack
return false; return false;
} }

View File

@ -1,10 +1,10 @@
import { DelayedAttackTag, type PositionalTag, WishTag } from "#data/positional-tags/positional-tag"; import { DelayedAttackTag, type PositionalTag, WishTag } from "#data/positional-tags/positional-tag";
import { PositionalTagType } from "#enums/positional-tag-type"; 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"; 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 tagType - The {@linkcode PositionalTagType} to create
* @param args - The arguments needed to instantize the given tag * @param args - The arguments needed to instantize the given tag
* @returns The newly created tag. * @returns The newly created tag.
@ -16,7 +16,7 @@ export function loadPositionalTag<T extends PositionalTagType>({
...args ...args
}: serializedPosTagMap[T]): posTagInstanceMap[T]; }: 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 * @param tag - The {@linkcode SerializedPositionalTag} to instantiate
* @returns The newly created tag. * @returns The newly created tag.
* @remarks * @remarks
@ -67,4 +67,4 @@ export type serializedPosTagMap = {
}; };
/** Union type containing all serialized {@linkcode PositionalTag}s. */ /** Union type containing all serialized {@linkcode PositionalTag}s. */
export type SerializedPositionalTag = EnumValues<serializedPosTagMap>; export type SerializedPositionalTag = ObjectValues<serializedPosTagMap>;

View File

@ -7,7 +7,8 @@ import type { PositionalTagType } from "#enums/positional-tag-type";
export class PositionalTagManager { export class PositionalTagManager {
/** /**
* Array containing all pending unactivated {@linkcode PositionalTag}s, * 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[] = []; 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 * @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](<https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179>)) * (Source: [Smogon](<https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179>))
*/ */
public triggerAllTags(): void { public activateAllTags(): void {
const leftoverTags: PositionalTag[] = []; const leftoverTags: PositionalTag[] = [];
for (const tag of this.tags) { for (const tag of this.tags) {
// Check for silent removal, immediately removing invalid tags. // Check for silent removal, immediately removing invalid tags.
@ -45,7 +46,7 @@ export class PositionalTagManager {
continue; continue;
} }
if (!tag.shouldDisappear()) { if (tag.shouldTrigger()) {
tag.trigger(); tag.trigger();
} }
} }

View File

@ -14,19 +14,21 @@ import i18next from "i18next";
/** /**
* Baseline arguments used to construct all {@linkcode PositionalTag}s, * Baseline arguments used to construct all {@linkcode PositionalTag}s,
* the contents of which are serialized and used to construct new tags. \ * 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 { export interface PositionalTagBaseArgs {
/** /**
* The number of turns remaining until activation. \ * 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 trigger} and be removed. * Decremented by 1 at the end of each turn until reaching 0, at which point it will
* @remarks * {@linkcode PositionalTag.trigger | trigger} the tag's effects and be removed.
* 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_.
*/ */
turnCount: number; turnCount: number;
/** /**
* The {@linkcode BattlerIndex} of the Pokemon targeted by the effect. * The {@linkcode BattlerIndex} targeted by this effect.
*/ */
targetIndex: BattlerIndex; 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. * Multiple tags of the same kind can stack with one another, provided they are affecting different targets.
*/ */
export abstract class PositionalTag implements PositionalTagBaseArgs { export abstract class PositionalTag implements PositionalTagBaseArgs {
/** This tag's {@linkcode PositionalTagType | type} */
public abstract readonly tagType: PositionalTagType; public abstract readonly tagType: PositionalTagType;
// These arguments have to be public to implement the interface, but are functionally private. // 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 turnCount: number;
public targetIndex: BattlerIndex; public targetIndex: BattlerIndex;
@ -51,16 +55,22 @@ export abstract class PositionalTag implements PositionalTagBaseArgs {
public abstract trigger(): void; public abstract trigger(): void;
/** /**
* Check whether this tag should be removed without calling {@linkcode trigger} and triggering effects. * Check whether this tag should be allowed to {@linkcode trigger} and activate its effects
* @returns Whether this tag should disappear without triggering. * 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 { protected getTarget(): Pokemon | undefined {
return globalScene.getField()[this.targetIndex]; return globalScene.getField()[this.targetIndex];
} }
} }
/** Interface containing additional properties used to construct a {@linkcode DelayedAttackTag}. */
interface DelayedAttackArgs extends PositionalTagBaseArgs { interface DelayedAttackArgs extends PositionalTagBaseArgs {
/** /**
* The {@linkcode Pokemon.id | PID} of the {@linkcode Pokemon} having created this effect. * 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 { 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 source = globalScene.getPokemonById(this.sourceId)!;
const target = this.getTarget()!; 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.sourceId, // TODO: Find an alternate method of passing the source pokemon without a source ID
[this.targetIndex], [this.targetIndex],
allMoves[this.sourceMove], allMoves[this.sourceMove],
MoveUseMode.TRANSPARENT, MoveUseMode.DELAYED_ATTACK,
); );
} }
override shouldDisappear(): boolean { override shouldTrigger(): boolean {
const source = globalScene.getPokemonById(this.sourceId); const source = globalScene.getPokemonById(this.sourceId);
const target = this.getTarget(); const target = this.getTarget();
// Silently disappear if either source or target are missing or happen to be the same pokemon // Silently disappear if either source or target are missing or happen to be the same pokemon
// (i.e. targeting oneself) // (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 { 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. */ /** The amount of {@linkcode Stat.HP | HP} to heal; set to 50% of the user's max HP during move usage. */
healHp: number; healHp: number;
@ -150,7 +163,7 @@ export class WishTag extends PositionalTag implements WishArgs {
globalScene.phaseManager.unshiftNew("PokemonHealPhase", this.targetIndex, this.healHp, null, true, false); 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. // 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) // 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 // TODO: Verify whether Wish shows a message if the Pokemon it would affect is KO'd on the turn of activation

View File

@ -1,7 +1,7 @@
import type { PostDancingMoveAbAttr } from "#abilities/ability"; import type { PostDancingMoveAbAttr } from "#abilities/ability";
import type { DelayedAttackAttr } from "#app/@types/move-types"; import type { DelayedAttackAttr } from "#app/@types/move-types";
import type { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; 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. * 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}, * 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**. * 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; } as const;
export type MoveUseMode = EnumValues<typeof MoveUseMode>; export type MoveUseMode = ObjectValues<typeof MoveUseMode>;
// # HELPER FUNCTIONS // # HELPER FUNCTIONS
// Please update the markdown tables if any new `MoveUseMode`s get added. // Please update the markdown tables if any new `MoveUseMode`s get added.
@ -84,13 +86,14 @@ export type MoveUseMode = EnumValues<typeof MoveUseMode>;
* @remarks * @remarks
* This function is equivalent to the following truth table: * This function is equivalent to the following truth table:
* *
* | Use Type | Returns | * | Use Type | Returns |
* |------------------------------------|---------| * |----------------------------------------|---------|
* | {@linkcode MoveUseMode.NORMAL} | `false` | * | {@linkcode MoveUseMode.NORMAL} | `false` |
* | {@linkcode MoveUseMode.IGNORE_PP} | `false` | * | {@linkcode MoveUseMode.IGNORE_PP} | `false` |
* | {@linkcode MoveUseMode.INDIRECT} | `true` | * | {@linkcode MoveUseMode.INDIRECT} | `true` |
* | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` |
* | {@linkcode MoveUseMode.REFLECTED} | `true` | * | {@linkcode MoveUseMode.REFLECTED} | `true` |
* | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` |
*/ */
export function isVirtual(useMode: MoveUseMode): boolean { export function isVirtual(useMode: MoveUseMode): boolean {
return useMode >= MoveUseMode.INDIRECT return useMode >= MoveUseMode.INDIRECT
@ -104,13 +107,14 @@ export function isVirtual(useMode: MoveUseMode): boolean {
* @remarks * @remarks
* This function is equivalent to the following truth table: * This function is equivalent to the following truth table:
* *
* | Use Type | Returns | * | Use Type | Returns |
* |------------------------------------|---------| * |----------------------------------------|---------|
* | {@linkcode MoveUseMode.NORMAL} | `false` | * | {@linkcode MoveUseMode.NORMAL} | `false` |
* | {@linkcode MoveUseMode.IGNORE_PP} | `false` | * | {@linkcode MoveUseMode.IGNORE_PP} | `false` |
* | {@linkcode MoveUseMode.INDIRECT} | `false` | * | {@linkcode MoveUseMode.INDIRECT} | `false` |
* | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` |
* | {@linkcode MoveUseMode.REFLECTED} | `true` | * | {@linkcode MoveUseMode.REFLECTED} | `true` |
* | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` |
*/ */
export function isIgnoreStatus(useMode: MoveUseMode): boolean { export function isIgnoreStatus(useMode: MoveUseMode): boolean {
return useMode >= MoveUseMode.FOLLOW_UP; return useMode >= MoveUseMode.FOLLOW_UP;
@ -124,13 +128,14 @@ export function isIgnoreStatus(useMode: MoveUseMode): boolean {
* @remarks * @remarks
* This function is equivalent to the following truth table: * This function is equivalent to the following truth table:
* *
* | Use Type | Returns | * | Use Type | Returns |
* |------------------------------------|---------| * |----------------------------------------|---------|
* | {@linkcode MoveUseMode.NORMAL} | `false` | * | {@linkcode MoveUseMode.NORMAL} | `false` |
* | {@linkcode MoveUseMode.IGNORE_PP} | `true` | * | {@linkcode MoveUseMode.IGNORE_PP} | `true` |
* | {@linkcode MoveUseMode.INDIRECT} | `true` | * | {@linkcode MoveUseMode.INDIRECT} | `true` |
* | {@linkcode MoveUseMode.FOLLOW_UP} | `true` | * | {@linkcode MoveUseMode.FOLLOW_UP} | `true` |
* | {@linkcode MoveUseMode.REFLECTED} | `true` | * | {@linkcode MoveUseMode.REFLECTED} | `true` |
* | {@linkcode MoveUseMode.DELAYED_ATTACK} | `true` |
*/ */
export function isIgnorePP(useMode: MoveUseMode): boolean { export function isIgnorePP(useMode: MoveUseMode): boolean {
return useMode >= MoveUseMode.IGNORE_PP; return useMode >= MoveUseMode.IGNORE_PP;
@ -145,14 +150,15 @@ export function isIgnorePP(useMode: MoveUseMode): boolean {
* @remarks * @remarks
* This function is equivalent to the following truth table: * This function is equivalent to the following truth table:
* *
* | Use Type | Returns | * | Use Type | Returns |
* |------------------------------------|---------| * |----------------------------------------|---------|
* | {@linkcode MoveUseMode.NORMAL} | `false` | * | {@linkcode MoveUseMode.NORMAL} | `false` |
* | {@linkcode MoveUseMode.IGNORE_PP} | `false` | * | {@linkcode MoveUseMode.IGNORE_PP} | `false` |
* | {@linkcode MoveUseMode.INDIRECT} | `false` | * | {@linkcode MoveUseMode.INDIRECT} | `false` |
* | {@linkcode MoveUseMode.FOLLOW_UP} | `false` | * | {@linkcode MoveUseMode.FOLLOW_UP} | `false` |
* | {@linkcode MoveUseMode.REFLECTED} | `true` | * | {@linkcode MoveUseMode.REFLECTED} | `true` |
* | {@linkcode MoveUseMode.DELAYED_ATTACK} | `false` |
*/ */
export function isReflected(useMode: MoveUseMode): boolean { export function isReflected(useMode: MoveUseMode): boolean {
return useMode === MoveUseMode.REFLECTED; return useMode === MoveUseMode.REFLECTED;
} }

View File

@ -52,7 +52,7 @@ export class Arena {
public bgm: string; public bgm: string;
public ignoreAbilities: boolean; public ignoreAbilities: boolean;
public ignoringEffectSource: BattlerIndex | null; public ignoringEffectSource: BattlerIndex | null;
public playerTerasUsed = 0; public playerTerasUsed: number;
/** /**
* Saves the number of times a party pokemon faints during a arena encounter. * 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). * {@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.bgm = bgm;
this.trainerPool = biomeTrainerPools[biome]; this.trainerPool = biomeTrainerPools[biome];
this.updatePoolsForTimeOfDay(); this.updatePoolsForTimeOfDay();
this.playerTerasUsed = 0;
this.playerFaints = playerFaints; this.playerFaints = playerFaints;
} }

View File

@ -331,7 +331,7 @@ export class MoveEffectPhase extends PokemonPhase {
*/ */
private postAnimCallback(user: Pokemon, targets: Pokemon[]) { private postAnimCallback(user: Pokemon, targets: Pokemon[]) {
// Add to the move history entry // 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); user.pushMoveHistory(this.moveHistoryEntry);
applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user }); applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user });
} }

View File

@ -13,7 +13,7 @@ export class PositionalTagPhase extends Phase {
public readonly phaseName = "PositionalTagPhase"; public readonly phaseName = "PositionalTagPhase";
public override start(): void { public override start(): void {
globalScene.arena.positionalTagManager.triggerAllTags(); globalScene.arena.positionalTagManager.activateAllTags();
super.end(); super.end();
return; return;
} }