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
*/
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);
}

View File

@ -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<Moves>(
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)
);

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,
* 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
}

View File

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

View File

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

View File

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

View File

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

View File

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