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 {
const useMode = args[1];
if (useMode === MoveUseMode.TRANSPARENT) {
if (useMode === MoveUseMode.DELAYED_ATTACK) {
// don't trigger if already queueing an indirect attack
return false;
}

View File

@ -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<T extends PositionalTagType>({
...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<serializedPosTagMap>;
export type SerializedPositionalTag = ObjectValues<serializedPosTagMap>;

View File

@ -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](<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[] = [];
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();
}
}

View File

@ -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

View File

@ -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<typeof MoveUseMode>;
export type MoveUseMode = ObjectValues<typeof MoveUseMode>;
// # HELPER FUNCTIONS
// Please update the markdown tables if any new `MoveUseMode`s get added.
@ -84,13 +86,14 @@ export type MoveUseMode = EnumValues<typeof MoveUseMode>;
* @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;
}
}

View File

@ -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;
}

View File

@ -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 });
}

View File

@ -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;
}