From 5a751694932cba76a09d91196b75223023d91414 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Tue, 20 May 2025 07:28:55 -0400 Subject: [PATCH] Fixed things --- src/data/battler-tags.ts | 9 +++------ src/data/moves/move.ts | 18 ++++++++---------- src/enums/move-use-type.ts | 10 +++++----- src/field/pokemon.ts | 6 +++--- src/phases/command-phase.ts | 6 +++--- src/phases/move-effect-phase.ts | 9 +++------ src/phases/move-phase.ts | 12 ++++++------ test/moves/powder.test.ts | 4 ++-- 8 files changed, 33 insertions(+), 41 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 45f04f88d87..8fe27a99f61 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -398,13 +398,10 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { * @param pokemon - The {@linkcode Pokemon} to add the tag to */ override onAdd(pokemon: Pokemon): void { - const lastMove = pokemon.getLastNonVirtualMove(); - if (!lastMove) { - return; - } - super.onAdd(pokemon); - this.moveId = lastMove.move; + + // Bang is justified as tag is not added if prior move doesn't exist + this.moveId = pokemon.getLastNonVirtualMove()!.move; pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 9fbbda194ec..bd2fd0332b2 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -123,7 +123,7 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MultiHitType } from "#enums/MultiHitType"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; -import { isFollowUp, MoveUseType } from "#enums/move-use-type"; +import { isVirtual, MoveUseType } from "#enums/move-use-type"; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; @@ -7022,6 +7022,7 @@ export class CopyMoveAttr extends CallMoveAttr { * Used for [Instruct](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)). */ export class RepeatMoveAttr extends MoveEffectAttr { + private movesetMove: PokemonMove; constructor() { super(false, { trigger: MoveEffectTrigger.POST_APPLY }); // needed to ensure correct protect interaction } @@ -7034,20 +7035,16 @@ export class RepeatMoveAttr extends MoveEffectAttr { */ apply(user: Pokemon, target: Pokemon): boolean { // get the last move used (excluding status based failures) as well as the corresponding moveset slot + // bangs are justified as Instruct fails if no prior move or moveset move exists // TODO: How does instruct work when copying a move called via Copycat that the user itself knows? - const lastMove = target.getLastNonVirtualMove(); - const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move) - - // never happens due to condition func, but makes TS compiler not sad about nullishness - if (!lastMove || !movesetMove) { - return false; - } + const lastMove = target.getLastNonVirtualMove()!; + const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move)! // If the last move used can hit more than one target or has variable targets, // re-compute the targets for the attack (mainly for alternating double/single battles) // Rampaging moves (e.g. Outrage) are not included due to being incompatible with Instruct, // nor is Dragon Darts (due to its smart targeting bypassing normal target selection) - let moveTargets = movesetMove.getMove().isMultiTarget() ? getMoveTargets(target, lastMove.move).targets : lastMove.targets; + let moveTargets = this.movesetMove.getMove().isMultiTarget() ? getMoveTargets(target, this.movesetMove.moveId).targets : lastMove.targets; // In the event the instructed move's only target is a fainted opponent, redirect it to an alive ally if possible. // Normally, all yet-unexecuted move phases would swap targets after any foe faints or flees (see `redirectPokemonMoves` in `battle-scene.ts`), @@ -7153,6 +7150,7 @@ export class RepeatMoveAttr extends MoveEffectAttr { || uninstructableMoves.includes(lastMove.move)) { // called move is in the banlist return false; } + this.movesetMove = movesetMove; return true; }; } @@ -7816,7 +7814,7 @@ export class LastResortAttr extends MoveAttr { const movesInHistory = new Set( user.getMoveHistory() - .filter(m => !isFollowUp(m.useType)) // Last resort ignores virtual moves + .filter(m => !isVirtual(m.useType)) // Last resort ignores virtual moves .map(m => m.move) ); diff --git a/src/enums/move-use-type.ts b/src/enums/move-use-type.ts index 12d7eb8bff9..6c688a7eab9 100644 --- a/src/enums/move-use-type.ts +++ b/src/enums/move-use-type.ts @@ -9,7 +9,7 @@ import type { PostDancingMoveAbAttr } from "#app/data/abilities/ability"; * Callers should refrain from performing non-equality checks on `MoveUseTypes` directly, * instead using the available helper functions - * ({@linkcode isFollowUp}, {@linkcode isIgnorePP} and {@linkcode isReflected}). + * ({@linkcode isVirtual}, {@linkcode isIgnorePP} and {@linkcode isReflected}). */ export enum MoveUseType { /** @@ -72,10 +72,10 @@ export enum MoveUseType { // Please update the markdown tables if any new `MoveUseType`s get added. /** - * Check if a given {@linkcode MoveUseType} is follow-up (i.e. called by another move). - * Follow-up moves are ignored by most moveset-related effects and pre-move cancellation checks. + * Check if a given {@linkcode MoveUseType} is virtual (i.e. called by another move or effect). + * Virtual moves are ignored by most moveset-related effects and pre-move cancellation checks. * @param useType - The {@linkcode MoveUseType} to check. - * @returns Whether {@linkcode useType} is follow-up. + * @returns Whether {@linkcode useType} is virtual. * @remarks * This function is equivalent to the following truth table: @@ -87,7 +87,7 @@ export enum MoveUseType { * | {@linkcode MoveUseType.FOLLOW_UP} | `true` | * | {@linkcode MoveUseType.REFLECTED} | `true` | */ -export function isFollowUp(useType: MoveUseType): boolean { +export function isVirtual(useType: MoveUseType): boolean { return useType >= MoveUseType.INDIRECT } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 15c23e96500..b31ab181e50 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -260,7 +260,7 @@ import { MoveFlags } from "#enums/MoveFlags"; import { timedEventManager } from "#app/global-event-manager"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { ResetStatusPhase } from "#app/phases/reset-status-phase"; -import { isFollowUp, isIgnorePP, MoveUseType } from "#enums/move-use-type"; +import { isVirtual, isIgnorePP, MoveUseType } from "#enums/move-use-type"; export enum LearnMoveSituation { MISC, @@ -5172,7 +5172,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.getLastXMoves(-1).find(m => m.move !== Moves.NONE && (!ignoreStruggle || m.move !== Moves.STRUGGLE) - && (m.useType < MoveUseType.INDIRECT || + && (!isVirtual(m.useType) || (!ignoreFollowUp && m.useType === MoveUseType.FOLLOW_UP)) ); } @@ -7270,7 +7270,7 @@ export class EnemyPokemon extends Pokemon { // Otherwise, ensure that the move being used is actually usable // TODO: Virtual moves shouldn't use the move queue if ( - isFollowUp(queuedMove.useType) || + isVirtual(queuedMove.useType) || (moveIndex > -1 && this.getMoveset()[moveIndex].isUsable( this, diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index d03e2b84afb..a60fa037020 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -23,7 +23,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { isNullOrUndefined } from "#app/utils/common"; import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import { isFollowUp, isIgnorePP, MoveUseType } from "#enums/move-use-type"; +import { isVirtual, isIgnorePP, MoveUseType } from "#enums/move-use-type"; export class CommandPhase extends FieldPhase { protected fieldIndex: number; @@ -104,7 +104,7 @@ export class CommandPhase extends FieldPhase { moveQueue.length && moveQueue[0] && moveQueue[0].move && - !isFollowUp(moveQueue[0].useType) && + !isVirtual(moveQueue[0].useType) && (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) || !playerPokemon .getMoveset() @@ -126,7 +126,7 @@ export class CommandPhase extends FieldPhase { if ( (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useType))) || - isFollowUp(queuedMove.useType) + isVirtual(queuedMove.useType) ) { this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useType, queuedMove); } else { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index af522405a69..52f527f69f7 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -78,7 +78,7 @@ import type Move from "#app/data/moves/move"; import { isFieldTargeted } from "#app/data/moves/move-utils"; import { FaintPhase } from "./faint-phase"; import { DamageAchv } from "#app/system/achv"; -import { isFollowUp, MoveUseType } from "#enums/move-use-type"; +import { isVirtual, isReflected, MoveUseType } from "#enums/move-use-type"; export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier]; @@ -302,7 +302,7 @@ export class MoveEffectPhase extends PokemonPhase { this.getFirstTarget() ?? null, move, overridden, - isFollowUp(this.useType), + isVirtual(this.useType), ); // If other effects were overriden, stop this phase before they can be applied @@ -567,10 +567,7 @@ export class MoveEffectPhase extends PokemonPhase { } // Reflected moves cannot be reflected again - if ( - this.useType < MoveUseType.REFLECTED && - move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target }) - ) { + if (!isReflected(this.useType) && move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })) { return [HitCheckResult.REFLECTED, 0]; } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 8b547a260e1..9614667ba6b 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -50,7 +50,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; -import { isFollowUp, isIgnorePP, isReflected, MoveUseType } from "#enums/move-use-type"; +import { isVirtual, isIgnorePP, isReflected, MoveUseType } from "#enums/move-use-type"; export class MovePhase extends BattlePhase { protected _pokemon: Pokemon; @@ -171,7 +171,7 @@ export class MovePhase extends BattlePhase { this.pokemon.turnData.acted = true; // Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) - if (isFollowUp(this.useType)) { + if (isVirtual(this.useType)) { this.pokemon.turnData.hitsLeft = -1; this.pokemon.turnData.hitCount = 0; } @@ -181,7 +181,7 @@ export class MovePhase extends BattlePhase { this.move.getMove().doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user: this.pokemon, - isFollowUp: isFollowUp(this.useType), // Sunsteel strike and co. don't work when called indirectly + isFollowUp: isVirtual(this.useType), // Sunsteel strike and co. don't work when called indirectly }) ) { globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); @@ -321,7 +321,7 @@ export class MovePhase extends BattlePhase { // TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked? // (In other words, check if truant can proc on a move w/o targets) - if (this.useType < MoveUseType.FOLLOW_UP && this.canMove() && !this.cancelled) { + if (!isVirtual(this.useType) && this.canMove() && !this.cancelled) { this.pokemon.lapseTags(BattlerTagLapseType.MOVE); } } @@ -511,7 +511,7 @@ export class MovePhase extends BattlePhase { */ public end(): void { globalScene.unshiftPhase( - new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), isFollowUp(this.useType)), + new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), isVirtual(this.useType)), ); super.end(); @@ -641,7 +641,7 @@ export class MovePhase extends BattlePhase { protected handlePreMoveFailures(): void { if (this.cancelled || this.failed) { if (this.failed) { - const ppUsed = this.useType > MoveUseType.IGNORE_PP ? 1 : 0; + const ppUsed = isIgnorePP(this.useType) ? 0 : 1; if (ppUsed) { this.move.usePp(); diff --git a/test/moves/powder.test.ts b/test/moves/powder.test.ts index 40d7180d94d..ce929a0ef7f 100644 --- a/test/moves/powder.test.ts +++ b/test/moves/powder.test.ts @@ -5,7 +5,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { BerryPhase } from "#app/phases/berry-phase"; -import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import { PokemonType } from "#enums/pokemon-type"; import { StatusEffect } from "#enums/status-effect"; import { BattlerIndex } from "#app/battle"; @@ -43,7 +43,7 @@ describe("Moves - Powder", () => { await game.classicMode.startBattle([Species.CHARIZARD]); const enemyPokemon = game.scene.getEnemyPokemon()!; - enemyPokemon.moveset = [new PokemonMove(Moves.EMBER)]; + game.move.changeMoveset(enemyPokemon, Moves.EMBER); game.move.select(Moves.POWDER);