Fixed things

This commit is contained in:
Bertie690 2025-05-20 07:28:55 -04:00
parent d30995f040
commit 5a75169493
8 changed files with 33 additions and 41 deletions

View File

@ -398,13 +398,10 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
* @param pokemon - The {@linkcode Pokemon} to add the tag to * @param pokemon - The {@linkcode Pokemon} to add the tag to
*/ */
override onAdd(pokemon: Pokemon): void { override onAdd(pokemon: Pokemon): void {
const lastMove = pokemon.getLastNonVirtualMove();
if (!lastMove) {
return;
}
super.onAdd(pokemon); 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); pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
} }

View File

@ -123,7 +123,7 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MultiHitType } from "#enums/MultiHitType"; import { MultiHitType } from "#enums/MultiHitType";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves";
import { SelectBiomePhase } from "#app/phases/select-biome-phase"; 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 MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: 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)). * Used for [Instruct](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)).
*/ */
export class RepeatMoveAttr extends MoveEffectAttr { export class RepeatMoveAttr extends MoveEffectAttr {
private movesetMove: PokemonMove;
constructor() { constructor() {
super(false, { trigger: MoveEffectTrigger.POST_APPLY }); // needed to ensure correct protect interaction 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 { apply(user: Pokemon, target: Pokemon): boolean {
// get the last move used (excluding status based failures) as well as the corresponding moveset slot // 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? // TODO: How does instruct work when copying a move called via Copycat that the user itself knows?
const lastMove = target.getLastNonVirtualMove(); const lastMove = target.getLastNonVirtualMove()!;
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove?.move) 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;
}
// If the last move used can hit more than one target or has variable targets, // 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) // 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, // 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) // 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. // 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`), // 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 || uninstructableMoves.includes(lastMove.move)) { // called move is in the banlist
return false; return false;
} }
this.movesetMove = movesetMove;
return true; return true;
}; };
} }
@ -7816,7 +7814,7 @@ export class LastResortAttr extends MoveAttr {
const movesInHistory = new Set<Moves>( const movesInHistory = new Set<Moves>(
user.getMoveHistory() 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) .map(m => m.move)
); );

View File

@ -9,7 +9,7 @@ import type { PostDancingMoveAbAttr } from "#app/data/abilities/ability";
* Callers should refrain from performing non-equality checks on `MoveUseTypes` directly, * Callers should refrain from performing non-equality checks on `MoveUseTypes` directly,
* instead using the available helper functions * instead using the available helper functions
* ({@linkcode isFollowUp}, {@linkcode isIgnorePP} and {@linkcode isReflected}). * ({@linkcode isVirtual}, {@linkcode isIgnorePP} and {@linkcode isReflected}).
*/ */
export enum MoveUseType { export enum MoveUseType {
/** /**
@ -72,10 +72,10 @@ export enum MoveUseType {
// Please update the markdown tables if any new `MoveUseType`s get added. // 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). * Check if a given {@linkcode MoveUseType} is virtual (i.e. called by another move or effect).
* Follow-up moves are ignored by most moveset-related effects and pre-move cancellation checks. * Virtual moves are ignored by most moveset-related effects and pre-move cancellation checks.
* @param useType - The {@linkcode MoveUseType} to check. * @param useType - The {@linkcode MoveUseType} to check.
* @returns Whether {@linkcode useType} is follow-up. * @returns Whether {@linkcode useType} is virtual.
* @remarks * @remarks
* This function is equivalent to the following truth table: * This function is equivalent to the following truth table:
@ -87,7 +87,7 @@ export enum MoveUseType {
* | {@linkcode MoveUseType.FOLLOW_UP} | `true` | * | {@linkcode MoveUseType.FOLLOW_UP} | `true` |
* | {@linkcode MoveUseType.REFLECTED} | `true` | * | {@linkcode MoveUseType.REFLECTED} | `true` |
*/ */
export function isFollowUp(useType: MoveUseType): boolean { export function isVirtual(useType: MoveUseType): boolean {
return useType >= MoveUseType.INDIRECT return useType >= MoveUseType.INDIRECT
} }

View File

@ -260,7 +260,7 @@ import { MoveFlags } from "#enums/MoveFlags";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
import { ResetStatusPhase } from "#app/phases/reset-status-phase"; 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 { export enum LearnMoveSituation {
MISC, MISC,
@ -5172,7 +5172,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.getLastXMoves(-1).find(m => return this.getLastXMoves(-1).find(m =>
m.move !== Moves.NONE m.move !== Moves.NONE
&& (!ignoreStruggle || m.move !== Moves.STRUGGLE) && (!ignoreStruggle || m.move !== Moves.STRUGGLE)
&& (m.useType < MoveUseType.INDIRECT || && (!isVirtual(m.useType) ||
(!ignoreFollowUp && m.useType === MoveUseType.FOLLOW_UP)) (!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 // Otherwise, ensure that the move being used is actually usable
// TODO: Virtual moves shouldn't use the move queue // TODO: Virtual moves shouldn't use the move queue
if ( if (
isFollowUp(queuedMove.useType) || isVirtual(queuedMove.useType) ||
(moveIndex > -1 && (moveIndex > -1 &&
this.getMoveset()[moveIndex].isUsable( this.getMoveset()[moveIndex].isUsable(
this, this,

View File

@ -23,7 +23,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagSide } from "#app/data/arena-tag";
import { ArenaTagType } from "#app/enums/arena-tag-type"; 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 { export class CommandPhase extends FieldPhase {
protected fieldIndex: number; protected fieldIndex: number;
@ -104,7 +104,7 @@ export class CommandPhase extends FieldPhase {
moveQueue.length && moveQueue.length &&
moveQueue[0] && moveQueue[0] &&
moveQueue[0].move && moveQueue[0].move &&
!isFollowUp(moveQueue[0].useType) && !isVirtual(moveQueue[0].useType) &&
(!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) || (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) ||
!playerPokemon !playerPokemon
.getMoveset() .getMoveset()
@ -126,7 +126,7 @@ export class CommandPhase extends FieldPhase {
if ( if (
(moveIndex > -1 && (moveIndex > -1 &&
playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useType))) || playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useType))) ||
isFollowUp(queuedMove.useType) isVirtual(queuedMove.useType)
) { ) {
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useType, queuedMove); this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useType, queuedMove);
} else { } else {

View File

@ -78,7 +78,7 @@ import type Move from "#app/data/moves/move";
import { isFieldTargeted } from "#app/data/moves/move-utils"; import { isFieldTargeted } from "#app/data/moves/move-utils";
import { FaintPhase } from "./faint-phase"; import { FaintPhase } from "./faint-phase";
import { DamageAchv } from "#app/system/achv"; 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]; export type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier];
@ -302,7 +302,7 @@ export class MoveEffectPhase extends PokemonPhase {
this.getFirstTarget() ?? null, this.getFirstTarget() ?? null,
move, move,
overridden, overridden,
isFollowUp(this.useType), isVirtual(this.useType),
); );
// If other effects were overriden, stop this phase before they can be applied // 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 // Reflected moves cannot be reflected again
if ( if (!isReflected(this.useType) && move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })) {
this.useType < MoveUseType.REFLECTED &&
move.doesFlagEffectApply({ flag: MoveFlags.REFLECTABLE, user, target })
) {
return [HitCheckResult.REFLECTED, 0]; return [HitCheckResult.REFLECTED, 0];
} }

View File

@ -50,7 +50,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; 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 { export class MovePhase extends BattlePhase {
protected _pokemon: Pokemon; protected _pokemon: Pokemon;
@ -171,7 +171,7 @@ export class MovePhase extends BattlePhase {
this.pokemon.turnData.acted = true; this.pokemon.turnData.acted = true;
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats) // 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.hitsLeft = -1;
this.pokemon.turnData.hitCount = 0; this.pokemon.turnData.hitCount = 0;
} }
@ -181,7 +181,7 @@ export class MovePhase extends BattlePhase {
this.move.getMove().doesFlagEffectApply({ this.move.getMove().doesFlagEffectApply({
flag: MoveFlags.IGNORE_ABILITIES, flag: MoveFlags.IGNORE_ABILITIES,
user: this.pokemon, 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()); 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? // 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) // (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); this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
} }
} }
@ -511,7 +511,7 @@ export class MovePhase extends BattlePhase {
*/ */
public end(): void { public end(): void {
globalScene.unshiftPhase( globalScene.unshiftPhase(
new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), isFollowUp(this.useType)), new MoveEndPhase(this.pokemon.getBattlerIndex(), this.getActiveTargetPokemon(), isVirtual(this.useType)),
); );
super.end(); super.end();
@ -641,7 +641,7 @@ export class MovePhase extends BattlePhase {
protected handlePreMoveFailures(): void { protected handlePreMoveFailures(): void {
if (this.cancelled || this.failed) { if (this.cancelled || this.failed) {
if (this.failed) { if (this.failed) {
const ppUsed = this.useType > MoveUseType.IGNORE_PP ? 1 : 0; const ppUsed = isIgnorePP(this.useType) ? 0 : 1;
if (ppUsed) { if (ppUsed) {
this.move.usePp(); this.move.usePp();

View File

@ -5,7 +5,7 @@ import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { BerryPhase } from "#app/phases/berry-phase"; 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 { PokemonType } from "#enums/pokemon-type";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
@ -43,7 +43,7 @@ describe("Moves - Powder", () => {
await game.classicMode.startBattle([Species.CHARIZARD]); await game.classicMode.startBattle([Species.CHARIZARD]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
enemyPokemon.moveset = [new PokemonMove(Moves.EMBER)]; game.move.changeMoveset(enemyPokemon, Moves.EMBER);
game.move.select(Moves.POWDER); game.move.select(Moves.POWDER);