PR review changes

Fix type hints test name

Update Dig/Dive test name

Separate TSDoc imports in `pokemon-utils.ts`

Add missing `@returns` in `move-phase.ts`

Fix comment typos

Separate TSDoc imports in `move-phase.ts`

Add return hints to `trySelectMove`

Minor formatting

Remove duplicate `.affectedByGravity()` on Telekinesis

Fix docs for `checkRestrictions`

Manually format method definition

Fix comment spacing

Fix variable naming
This commit is contained in:
NightKev 2025-08-19 17:23:11 -07:00 committed by Sirz Benjie
parent 2eed38dc55
commit 9e49162012
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
8 changed files with 50 additions and 49 deletions

View File

@ -156,7 +156,7 @@ export const failAgainstFinalBossCondition = new MoveCondition((_user, target) =
* a high-priority attack (after factoring in priority-boosting effects) and
* hasn't moved yet this turn.
*/
export const UpperHandCondition = new MoveCondition((_user, target) => {
export const upperHandCondition = new MoveCondition((_user, target) => {
const targetCommand = globalScene.currentBattle.turnCommands[target.getBattlerIndex()];
return (
targetCommand?.command === Command.FIGHT &&
@ -252,13 +252,13 @@ export class MoveRestriction {
* @remarks
* Used by {@link https://bulbapedia.bulbagarden.net/wiki/Blood_Moon_(move) | Blood Moon} and {@link https://bulbapedia.bulbagarden.net/wiki/Gigaton_Hammer_(move) | Gigaton Hammer}
*/
export const ConsecutiveUseRestriction = new MoveRestriction(
export const consecutiveUseRestriction = new MoveRestriction(
(user, move) => user.getLastXMoves(1)[0]?.move === move.id,
"battle:moveDisabledConsecutive",
);
/** Prevents a move from being selected if Gravity is in effect */
export const GravityUseRestriction = new MoveRestriction(
export const gravityUseRestriction = new MoveRestriction(
() => globalScene.arena.hasTag(ArenaTagType.GRAVITY),
"battle:moveDisabledGravity",
);

View File

@ -82,7 +82,7 @@ import {
} from "#modifiers/modifier";
import { applyMoveAttrs } from "#moves/apply-attrs";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSketchMoves, invalidSleepTalkMoves } from "#moves/invalid-moves";
import { ConsecutiveUseRestriction, counterAttackConditionBoth, counterAttackConditionPhysical, counterAttackConditionSpecial, failAgainstFinalBossCondition, FailIfInsufficientHpCondition, failIfTargetNotAttackingCondition, failTeleportCondition, FirstMoveCondition, GravityUseRestriction, lastResortCondition, MoveCondition, MoveRestriction, UpperHandCondition } from "#moves/move-condition";
import { consecutiveUseRestriction, counterAttackConditionBoth, counterAttackConditionPhysical, counterAttackConditionSpecial, failAgainstFinalBossCondition, FailIfInsufficientHpCondition, failIfTargetNotAttackingCondition, failTeleportCondition, FirstMoveCondition, gravityUseRestriction, lastResortCondition, MoveCondition, MoveRestriction, upperHandCondition } from "#moves/move-condition";
import { frenzyMissFunc, getCounterAttackTarget, getMoveTargets } from "#moves/move-utils";
import { PokemonMove } from "#moves/pokemon-move";
import { MovePhase } from "#phases/move-phase";
@ -172,8 +172,14 @@ export abstract class Move implements Localizable {
* @see {@link https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-54#post-8548957}
*/
private conditionsSeq3: MoveCondition[] = [];
<<<<<<< HEAD
/** Conditions that must be false for a move to be able to be selected.
*
=======
/**
* Conditions that must be false for a move to be able to be selected.
*
>>>>>>> 02f941b3a05 (PR review changes)
* @remarks Different from {@linkcode conditions}, which is checked when the move is invoked
*/
private restrictions: MoveRestriction[] = [];
@ -452,7 +458,7 @@ export abstract class Move implements Localizable {
* @returns `this` for method chaining
*/
public restriction(restriction: MoveRestriction): this;
/**
/**
* Adds a restriction condition to this move.
* The move will not be selectable if at least 1 of its restrictions is met.
* @param restriction - The function or `MoveRestriction` that evaluates to `true` if the move is restricted from
@ -476,7 +482,12 @@ export abstract class Move implements Localizable {
* is false
* @returns `this` for method chaining
*/
public restriction<T extends UserMoveConditionFunc | MoveRestriction>(restriction: T, i18nkey?: string, alsoCondition: typeof restriction extends MoveRestriction ? false : boolean = false, conditionSeq = 4): this {
public restriction<T extends UserMoveConditionFunc | MoveRestriction>(
restriction: T,
i18nkey?: string,
alsoCondition: typeof restriction extends MoveRestriction ? false : boolean = false,
conditionSeq = 4,
): this {
if (typeof restriction === "function") {
this.restrictions.push(new MoveRestriction(restriction));
if (alsoCondition) {
@ -700,7 +711,7 @@ export abstract class Move implements Localizable {
}
/**
* Sets the {@linkcode MoveFlags.GRAVITY} flag for the calling Move and adds {@linkcode GravityUseRestriction} to the
* Sets the {@linkcode MoveFlags.GRAVITY} flag for the calling Move and adds {@linkcode gravityUseRestriction} to the
* move's restrictions.
*
* @returns `this`
@ -713,7 +724,7 @@ export abstract class Move implements Localizable {
*/
affectedByGravity(): this {
this.setFlag(MoveFlags.GRAVITY, true);
this.restrictions.push(GravityUseRestriction);
this.restrictions.push(gravityUseRestriction);
return this;
}
@ -859,9 +870,9 @@ export abstract class Move implements Localizable {
*
* @param user - The Pokemon using the move
* @returns - An array whose first element is `false` if the move is restricted, and the second element is a string
* with the reason for the restriction, otherwise, `false` and the empty string.
* with the reason for the restriction, otherwise, `true` and the empty string.
*/
public checkRestrictions(user: Pokemon): [boolean, string] {
public checkRestrictions(user: Pokemon): [isUsable: boolean, restrictionMessage: string] {
for (const restriction of this.restrictions) {
if (restriction.apply(user, this)) {
return [false, restriction.getSelectionDeniedText(user, this)];
@ -10034,7 +10045,6 @@ export function initMoves() {
.condition((_user, target, _move) => target.getTag(BattlerTagType.INGRAIN) == null && target.getTag(BattlerTagType.IGNORE_FLYING) == null)
.attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3)
.attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3)
.affectedByGravity()
.reflectable(),
new StatusMove(MoveId.MAGIC_ROOM, PokemonType.PSYCHIC, -1, 10, -1, 0, 5)
.ignoresProtect()
@ -10677,10 +10687,7 @@ export function initMoves() {
.attr(HealOnAllyAttr, 0.5, true, false)
.ballBombMove()
// Fail if used against an ally that is affected by heal block, during the second failure check
.condition(
(user, target) => target.isOpponent(user) || !!target.getTag(BattlerTagType.HEAL_BLOCK),
2
),
.condition((user, target) => target.isOpponent(user) || !!target.getTag(BattlerTagType.HEAL_BLOCK), 2),
new AttackMove(MoveId.ANCHOR_SHOT, PokemonType.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true),
new StatusMove(MoveId.PSYCHIC_TERRAIN, PokemonType.PSYCHIC, -1, 10, -1, 0, 7)
@ -10693,11 +10700,8 @@ export function initMoves() {
new AttackMove(MoveId.POWER_TRIP, PokemonType.DARK, MoveCategory.PHYSICAL, 20, 100, 10, -1, 0, 7)
.attr(PositiveStatStagePowerAttr),
new AttackMove(MoveId.BURN_UP, PokemonType.FIRE, MoveCategory.SPECIAL, 130, 100, 5, -1, 0, 7)
.condition(
// Pass `true` to `ForDefend` as it should fail if the user is terastallized to a type that is not FIRE
user => user.isOfType(PokemonType.FIRE, true, true),
2
)
// Pass `true` to `ForDefend` as it should fail if the user is terastallized to a type that is not FIRE
.condition(user => user.isOfType(PokemonType.FIRE, true, true), 2)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
.attr(AddBattlerTagAttr, BattlerTagType.BURNED_UP, true, false)
.attr(RemoveTypeAttr, PokemonType.FIRE, (user) => {
@ -10798,10 +10802,7 @@ export function initMoves() {
.attr(AddBattlerTagHeaderAttr, BattlerTagType.SHELL_TRAP)
.target(MoveTarget.ALL_NEAR_ENEMIES)
// Fails if the user was not hit by a physical attack during the turn
.condition(
user => user.getTag(ShellTrapTag)?.activated === true,
3
),
.condition(user => user.getTag(ShellTrapTag)?.activated === true, 3),
new AttackMove(MoveId.FLEUR_CANNON, PokemonType.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7)
.attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true),
new AttackMove(MoveId.PSYCHIC_FANGS, PokemonType.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
@ -10949,11 +10950,8 @@ export function initMoves() {
new SelfStatusMove(MoveId.NO_RETREAT, PokemonType.FIGHTING, -1, 5, -1, 0, 8)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, true /* NOT ADDED if already trapped */)
.condition(
// fails if the user is currently trapped specifically from no retreat
user => user.getTag(TrappedTag)?.tagType !== BattlerTagType.NO_RETREAT,
2
),
// fails if the user is currently trapped specifically from no retreat
.condition(user => user.getTag(TrappedTag)?.tagType !== BattlerTagType.NO_RETREAT, 2),
new StatusMove(MoveId.TAR_SHOT, PokemonType.ROCK, 100, 15, -1, 0, 8)
.attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.attr(AddBattlerTagAttr, BattlerTagType.TAR_SHOT, false)
@ -11515,18 +11513,15 @@ export function initMoves() {
.slicingMove()
.triageMove(),
new AttackMove(MoveId.DOUBLE_SHOCK, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 9)
.condition(
// Pass `true` to `isOfType` to fail if the user is terastallized to a type other than ELECTRIC
user => user.isOfType(PokemonType.ELECTRIC, true, true),
2
)
// Pass `true` to `isOfType` to fail if the user is terastallized to a type other than ELECTRIC
.condition(user => user.isOfType(PokemonType.ELECTRIC, true, true), 2)
.attr(AddBattlerTagAttr, BattlerTagType.DOUBLE_SHOCKED, true, false)
.attr(RemoveTypeAttr, PokemonType.ELECTRIC, (user) => {
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) }));
}),
new AttackMove(MoveId.GIGATON_HAMMER, PokemonType.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, -1, 0, 9)
.makesContact(false)
.restriction(ConsecutiveUseRestriction),
.restriction(consecutiveUseRestriction),
new AttackMove(MoveId.COMEUPPANCE, PokemonType.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
.attr(CounterDamageAttr, 1.5)
.attr(CounterRedirectAttr)
@ -11552,7 +11547,7 @@ export function initMoves() {
.attr(ConfuseAttr)
.makesContact(false),
new AttackMove(MoveId.BLOOD_MOON, PokemonType.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 9)
.restriction(ConsecutiveUseRestriction),
.restriction(consecutiveUseRestriction),
new AttackMove(MoveId.MATCHA_GOTCHA, PokemonType.GRASS, MoveCategory.SPECIAL, 80, 90, 15, 20, 0, 9)
.attr(HitHealAttr)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
@ -11613,7 +11608,7 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2),
new AttackMove(MoveId.UPPER_HAND, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
.attr(FlinchAttr)
.condition(UpperHandCondition, 3),
.condition(upperHandCondition, 3),
new AttackMove(MoveId.MALIGNANT_CHAIN, PokemonType.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9)
.attr(StatusEffectAttr, StatusEffect.TOXIC)
);

View File

@ -3138,7 +3138,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ignorePp - Whether to ignore PP when checking if the move is usable (defaults to false)
* @returns A tuple containing a boolean indicating if the move can be selected, and a string with the reason if it cannot be selected
*/
public trySelectMove(moveIndex: number, ignorePp?: boolean): [boolean, string] {
public trySelectMove(moveIndex: number, ignorePp?: boolean): [isUsable: boolean, failureMessage: string] {
const move = this.getMoveset().length > moveIndex ? this.getMoveset()[moveIndex] : null;
return move?.isUsable(this, ignorePp, true) ?? [false, ""];
}

View File

@ -1,3 +1,7 @@
// biome-ignore-start lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { Move, PreUseInterruptAttr } from "#types/move-types";
// biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment
import { applyAbAttrs } from "#abilities/apply-ab-attrs";
import { MOVE_COLOR } from "#app/constants/colors";
import { globalScene } from "#app/global-scene";
@ -28,8 +32,6 @@ import type { Pokemon } from "#field/pokemon";
import { applyMoveAttrs } from "#moves/apply-attrs";
import { frenzyMissFunc } from "#moves/move-utils";
import type { PokemonMove } from "#moves/pokemon-move";
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { Move, PreUseInterruptAttr } from "#types/move-types";
import { applyChallenges } from "#utils/challenge-utils";
import { BooleanHolder, NumberHolder } from "#utils/common";
import { enumValueToKey } from "#utils/enums";
@ -205,7 +207,7 @@ export class MovePhase extends PokemonPhase {
*
* @remarks
* Other than powder, each failure condition is mutually exclusive (as they are tied to specific moves), so order does not matter.
* Notably, this failure check only includes failure conditions intrinsic to the move itself, ther than Powder (which marks the end of this failure check)
* Notably, this failure check only includes failure conditions intrinsic to the move itself, other than Powder (which marks the end of this failure check)
*
*
* - Pollen puff used on an ally that is under effect of heal block
@ -512,13 +514,14 @@ export class MovePhase extends PokemonPhase {
* - Checking if the pokemon will thaw from random chance, OR from a thawing move.
* Thawing from a freeze move is not applied until AFTER all other failure checks.
* - Activating the freeze status effect (cancelling the move, playing the message, and displaying the animation)
* @returns Whether the move was cancelled due to the pokemon being frozen
*/
protected checkFreeze(): boolean {
if (this.pokemon.status?.effect !== StatusEffect.FREEZE) {
return false;
}
// For some reason, dancer will immediately its user
// For some reason, dancer will immediately thaw its user
if (this.useMode === MoveUseMode.INDIRECT) {
this.pokemon.resetStatus(false);
return false;
@ -644,6 +647,7 @@ export class MovePhase extends PokemonPhase {
* Lapse the tag type and check if the move is cancelled from it. Meant to be used during the first failure check
* @param tag - The tag type whose lapse method will be called with {@linkcode BattlerTagLapseType.PRE_MOVE}
* @param checkIgnoreStatus - Whether to check {@link isIgnoreStatus} for the current {@linkcode MoveUseMode} to skip this check
* @returns Whether the move was cancelled due to a `BattlerTag` effect
*/
private checkTagCancel(tag: BattlerTagType): boolean {
this.pokemon.lapseTag(tag, BattlerTagLapseType.PRE_MOVE);
@ -652,7 +656,7 @@ export class MovePhase extends PokemonPhase {
/**
* Handle move failures due to Gravity, cancelling the move and showing the failure text
* @returns - Whether the move was cancelled due to Gravity
* @returns Whether the move was cancelled due to Gravity
*/
private checkGravity(): boolean {
const move = this.move.getMove();
@ -721,7 +725,7 @@ export class MovePhase extends PokemonPhase {
/*
At this point, delayed moves (future sight, wish, doom desire) are issued, and if they occur, are
Then, combined pledge moves are checked for. Interestingly, the "wasMoveEffective" flag is set to false if the delay occurs
In either case, the phase should end here without proceeding
In either case, the phase should end here without proceeding
*/
const move = this.move.getMove();

View File

@ -1,11 +1,13 @@
// biome-ignore-start lint/correctness/noUnusedImports: Used in a TSDoc comment
import type { Pokemon } from "#field/pokemon";
// biome-ignore-end lint/correctness/noUnusedImports: Used in a TSDoc comment
import { globalScene } from "#app/global-scene";
import { POKERUS_STARTER_COUNT, speciesStarterCosts } from "#balance/starters";
import { allSpecies } from "#data/data-lists";
import type { PokemonSpecies, PokemonSpeciesForm } from "#data/pokemon-species";
import { BattlerIndex } from "#enums/battler-index";
import type { SpeciesId } from "#enums/species-id";
// biome-ignore lint/correctness/noUnusedImports: Used in a TSDoc comment
import type { Pokemon } from "#field/pokemon";
import { randSeedItem } from "./common";
/**

View File

@ -90,7 +90,7 @@ describe("Moves - Dig", () => {
expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
});
it("should expend PP when the attack phase is cancelled", async () => {
it("should expend PP when the attack phase is cancelled by sleep", async () => {
game.override.enemyAbility(AbilityId.NO_GUARD).enemyMoveset(MoveId.SPORE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -74,7 +74,7 @@ describe("Moves - Dive", () => {
expect(enemyPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
});
it("should expend PP when the attack phase is cancelled", async () => {
it("should expend PP when the attack phase is cancelled by sleep", async () => {
game.override.enemyAbility(AbilityId.NO_GUARD).enemyMoveset(MoveId.SPORE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -84,7 +84,7 @@ describe("UI - Type Hints", () => {
await game.phaseInterceptor.to("CommandPhase");
});
it("should show the proper hint for a move in doubles after one of the enemy pokemon flees", async () => {
it("should show the proper hint for a move in doubles after one of the enemy pokemon faints", async () => {
game.override
.enemySpecies(SpeciesId.ABRA)
.moveset([MoveId.SPLASH, MoveId.SHADOW_BALL, MoveId.SOAK])