Replace move attribute imports with string names

This commit is contained in:
Sirz Benjie 2025-06-08 13:42:16 -05:00
parent 179d192e53
commit c8e8b8b492
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
36 changed files with 631 additions and 384 deletions

28
src/@types/move-types.ts Normal file
View File

@ -0,0 +1,28 @@
import type { AttackMove, StatusMove, SelfStatusMove, MoveAttrConstructorMap, MoveAttr } from "#app/data/moves/move";
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
export type * from "#app/data/moves/move";
/**
* Map of move subclass names to their respective classes.
*/
export type MoveClassMap = {
AttackMove: typeof AttackMove;
StatusMove: typeof StatusMove;
SelfStatusMove: typeof SelfStatusMove;
};
/**
* Union type of all move subclass names
*/
export type MoveClass = keyof MoveClassMap;
export type MoveAttrMap = {
[K in keyof MoveAttrConstructorMap]: InstanceType<MoveAttrConstructorMap[K]>;
};
/**
* Union type of all move attribute names as strings.
*/
export type MoveAttrString = keyof MoveAttrMap;

View File

@ -19,22 +19,7 @@ import {
getStatusEffectHealText,
} from "#app/data/status-effect";
import { Gender } from "#app/data/gender";
import {
AttackMove,
FlinchAttr,
OneHitKOAttr,
HitHealAttr,
StatusMove,
SelfStatusMove,
VariablePowerAttr,
applyMoveAttrs,
RandomMovesetMoveAttr,
RandomMoveAttr,
NaturePowerAttr,
CopyMoveAttr,
NeutralDamageAgainstFlyingTypeMultiplierAttr,
FixedDamageAttr,
} from "#app/data/moves/move";
import { applyMoveAttrs } from "../moves/apply-attrs";
import { allMoves } from "../data-lists";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
@ -523,7 +508,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
): boolean {
return (
move.category !== MoveCategory.STATUS &&
!move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr) &&
!move.hasAttr("NeutralDamageAgainstFlyingTypeMultiplierAttr") &&
super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args)
);
}
@ -696,7 +681,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
args.length > 0
? (args[0] as NumberHolder).value
: pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move);
return move instanceof AttackMove && modifierValue < 2;
return move.is("AttackMove") && modifierValue < 2;
}
override applyPreDefend(
@ -738,7 +723,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
const typeMultiplier = args[0];
return (
typeMultiplier instanceof NumberHolder &&
!move?.hasAttr(FixedDamageAttr) &&
!move?.hasAttr("FixedDamageAttr") &&
pokemon.isFullHp() &&
typeMultiplier.value > 0.5
);
@ -983,7 +968,7 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
_hitResult: HitResult | null,
_args: any[],
): boolean {
return move.hasAttr(HitHealAttr);
return move.hasAttr("HitHealAttr");
}
/**
@ -2064,10 +2049,10 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
*/
!move.findAttr(
attr =>
attr instanceof RandomMovesetMoveAttr ||
attr instanceof RandomMoveAttr ||
attr instanceof NaturePowerAttr ||
attr instanceof CopyMoveAttr,
attr.is("RandomMovesetMoveAttr") ||
attr.is("RandomMoveAttr") ||
attr.is("NaturePowerAttr") ||
attr.is("CopyMoveAttr"),
)
) {
const moveType = pokemon.getMoveType(move);
@ -4929,13 +4914,13 @@ function getAnticipationCondition(): AbAttrCondition {
}
// the move's base type (not accounting for variable type changes) is super effective
if (
move.getMove() instanceof AttackMove &&
move.getMove().is("AttackMove") &&
pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2
) {
return true;
}
// move is a OHKO
if (move.getMove().hasAttr(OneHitKOAttr)) {
if (move.getMove().hasAttr("OneHitKOAttr")) {
return true;
}
// edge case for hidden power, type is computed
@ -5004,9 +4989,9 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
let movePower = 0;
for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) {
if (move?.getMove() instanceof StatusMove) {
if (move?.getMove().is("StatusMove")) {
movePower = 1;
} else if (move?.getMove().hasAttr(OneHitKOAttr)) {
} else if (move?.getMove().hasAttr("OneHitKOAttr")) {
movePower = 150;
} else if (
move?.getMove().id === MoveId.COUNTER ||
@ -5877,10 +5862,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
dancer.turnData.extraTurns++;
const phaseManager = globalScene.phaseManager;
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) {
if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) {
const target = this.getTarget(dancer, source, targets);
phaseManager.unshiftNew("MovePhase", dancer, target, move, true, true);
} else if (move.getMove() instanceof SelfStatusMove) {
} else if (move.getMove().is("SelfStatusMove")) {
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
phaseManager.unshiftNew("MovePhase", dancer, [dancer.getBattlerIndex()], move, true, true);
}
@ -8210,7 +8195,7 @@ export function initAbilities() {
allAbilities.push(
new Ability(AbilityId.NONE, 3),
new Ability(AbilityId.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr(FlinchAttr) && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr("FlinchAttr") && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(AbilityId.DRIZZLE, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
@ -8517,7 +8502,7 @@ export function initAbilities() {
new Ability(AbilityId.TECHNICIAN, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => {
const power = new NumberHolder(move.power);
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
applyMoveAttrs("VariablePowerAttr", user, target, move, power);
return power.value <= 60;
}, 1.5),
new Ability(AbilityId.LEAF_GUARD, 4)
@ -8638,7 +8623,7 @@ export function initAbilities() {
)
.edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented)
new Ability(AbilityId.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.is("AttackMove"))
.ignorable(),
new Ability(AbilityId.MOODY, 5)
.attr(MoodyAbAttr),

View File

@ -1,5 +1,4 @@
import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move";
import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon";
@ -436,7 +435,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
if (moveAnims.get(move) !== null) {
const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
: (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) {
return;
}
@ -448,9 +447,9 @@ export function initMoveAnim(move: MoveId): Promise<void> {
} else {
moveAnims.set(move, null);
const defaultMoveAnim =
allMoves[move] instanceof AttackMove
allMoves[move].is("AttackMove")
? MoveId.TACKLE
: allMoves[move] instanceof SelfStatusMove
: allMoves[move].is("SelfStatusMove")
? MoveId.FOCUS_ENERGY
: MoveId.TAIL_WHIP;
@ -475,7 +474,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
}
const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
: (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource) {
initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve());
} else {
@ -608,7 +607,7 @@ export function loadMoveAnimAssets(moveIds: MoveId[], startLoad?: boolean): Prom
for (const moveId of moveIds) {
const chargeAnimSource = allMoves[moveId].isChargingMove()
? allMoves[moveId]
: (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]);
: (allMoves[moveId].getAttrs("DelayedAttackAttr")[0] ?? allMoves[moveId].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource) {
const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?

View File

@ -12,12 +12,7 @@ import { allAbilities } from "./data-lists";
import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
import type Move from "#app/data/moves/move";
import {
applyMoveAttrs,
ConsecutiveUseDoublePowerAttr,
HealOnAllyAttr,
StatusCategoryOnAllyAttr,
} from "#app/data/moves/move";
import { applyMoveAttrs } from "./moves/apply-attrs";
import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags";
import { MoveCategory } from "#enums/MoveCategory";
@ -2794,8 +2789,8 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
*/
override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) {
const moveCategory = new NumberHolder(allMoves[move].category);
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS;
applyMoveAttrs("StatusCategoryOnAllyAttr", user, target, allMoves[move], moveCategory);
return allMoves[move].hasAttr("HealOnAllyAttr") && moveCategory.value === MoveCategory.STATUS;
}
/**
@ -3131,7 +3126,7 @@ export class TormentTag extends MoveRestrictionBattlerTag {
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
const moveObj = allMoves[lastMove.move];
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY);
const isUnaffected = moveObj.hasAttr("ConsecutiveUseDoublePowerAttr") || user.getTag(BattlerTagType.FRENZY);
const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS;
return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected;
}

View File

@ -0,0 +1,58 @@
/**
* Module holding functions to apply move attributes.
* Must not import anything that is not a type.
*/
import type { MoveAttrString } from "#app/@types/move-types";
import type Pokemon from "#app/field/pokemon";
import type { default as Move, ChargingMove, MoveAttr } from "./move";
import type { MoveAttrFilter } from "#app/@types/move-types";
function applyMoveAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
args: any[],
): void {
move.attrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args));
}
function applyMoveChargeAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
args: any[],
): void {
move.chargeAttrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args));
}
export function applyMoveAttrs(
attrType: MoveAttrString,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args);
}
export function applyFilteredMoveAttrs(
attrFilter: MoveAttrFilter,
user: Pokemon,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function applyMoveChargeAttrs(
attrType: MoveAttrString,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
...args: any[]
): void {
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args);
}

View File

@ -1,5 +1,14 @@
import { MoveTarget } from "#enums/MoveTarget";
import type Pokemon from "#app/field/pokemon";
import type { BattlerIndex } from "#enums/battler-index";
import type { MoveId } from "#enums/move-id";
import type { MoveTargetSet, UserMoveConditionFunc } from "./move";
import type Move from "./move";
import { NumberHolder, isNullOrUndefined } from "#app/utils/common";
import { MoveTarget } from "#enums/MoveTarget";
import { PokemonType } from "#enums/pokemon-type";
import { allMoves } from "#app/data/data-lists";
import { applyMoveAttrs } from "./apply-attrs";
import { BattlerTagType } from "#enums/battler-tag-type";
/**
* Return whether the move targets the field
@ -18,3 +27,88 @@ export function isFieldTargeted(move: Move): boolean {
}
return false;
}
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet {
const variableTarget = new NumberHolder(0);
user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget));
let moveTarget: MoveTarget | undefined;
if (allMoves[move].hasAttr("VariableTargetAttr")) {
moveTarget = variableTarget.value;
} else if (replaceTarget !== undefined) {
moveTarget = replaceTarget;
} else if (move) {
moveTarget = allMoves[move].moveTarget;
} else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY;
}
const opponents = user.getOpponents(false);
let set: Pokemon[] = [];
let multiple = false;
const ally: Pokemon | undefined = user.getAlly();
switch (moveTarget) {
case MoveTarget.USER:
case MoveTarget.PARTY:
set = [user];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = !isNullOrUndefined(ally) ? opponents.concat([ally]) : opponents;
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents;
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [opponents[user.randBattleSeedInt(opponents.length)]];
break;
case MoveTarget.ATTACKER:
return { targets: [-1 as BattlerIndex], multiple: false };
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = !isNullOrUndefined(ally) ? [ally] : [];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = !isNullOrUndefined(ally) ? [user, ally] : [user];
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = (!isNullOrUndefined(ally) ? [user, ally] : [user]).concat(opponents);
multiple = true;
break;
case MoveTarget.CURSE:
{
const extraTargets = !isNullOrUndefined(ally) ? [ally] : [];
set = user.getTypes(true).includes(PokemonType.GHOST) ? opponents.concat(extraTargets) : [user];
}
break;
}
return {
targets: set
.filter(p => p?.isActive(true))
.map(p => p.getBattlerIndex())
.filter(t => t !== undefined),
multiple,
};
}
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) {
user.getMoveQueue().shift();
}
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
return true;
};

View File

@ -15,10 +15,7 @@ import {
import { getPokemonNameWithAffix } from "../../messages";
import type { AttackMoveResult, TurnMove } from "../../field/pokemon";
import type Pokemon from "../../field/pokemon";
import {
EnemyPokemon,
PlayerPokemon,
} from "../../field/pokemon";
import type { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "./pokemon-move";
import { MoveResult } from "#enums/move-result";
import { HitResult } from "#enums/hit-result";
@ -126,9 +123,12 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MultiHitType } from "#enums/MultiHitType";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
import { MoveAttrMap, MoveAttrString, MoveClass, MoveClassMap } from "#app/@types/move-types";
import { applyMoveAttrs } from "./apply-attrs";
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
export default class Move implements Localizable {
public id: MoveId;
@ -150,6 +150,16 @@ export default class Move implements Localizable {
private flags: number = 0;
private nameAppend: string = "";
/**
* Check if the move is of the given subclass without requiring `instanceof`.
*
* @param moveSort - The string name of the move to check against
* @returns Whether this move is of the provided type.
*/
public is<K extends keyof MoveClassMap>(moveSort: K): this is MoveClassMap[K] {
return false;
}
constructor(id: MoveId, type: PokemonType, category: MoveCategory, defaultMoveTarget: MoveTarget, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
this.id = id;
this._type = type;
@ -191,8 +201,12 @@ export default class Move implements Localizable {
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends MoveAttr>(attrType: Constructor<T>): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
getAttrs<T extends MoveAttrString>(attrType: T): (MoveAttrMap[T])[] {
const targetAttr = MoveAttrs[attrType];
if (!targetAttr) {
return [];
}
return this.attrs.filter((a): a is MoveAttrMap[T] => a instanceof targetAttr);
}
/**
@ -200,8 +214,13 @@ export default class Move implements Localizable {
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns true if the move has attribute `attrType`
*/
hasAttr<T extends MoveAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some((attr) => attr instanceof attrType);
hasAttr(attrType: MoveAttrString): boolean {
const targetAttr = MoveAttrs[attrType];
// Guard against invalid attrType
if (!targetAttr) {
return false;
}
return this.attrs.some((attr) => attr instanceof targetAttr);
}
/**
@ -771,14 +790,14 @@ export default class Move implements Localizable {
calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) {
const moveAccuracy = new NumberHolder(this.accuracy);
applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy);
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) {
return moveAccuracy.value;
}
const isOhko = this.hasAttr(OneHitKOAccuracyAttr);
const isOhko = this.hasAttr("OneHitKOAccuracyAttr");
if (!isOhko) {
globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
@ -818,7 +837,7 @@ export default class Move implements Localizable {
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType();
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60;
}
@ -850,9 +869,9 @@ export default class Move implements Localizable {
power.value *= typeBoost.boostValue;
}
applyMoveAttrs(VariablePowerAttr, source, target, this, power);
applyMoveAttrs("VariablePowerAttr", source, target, this, power);
if (!this.hasAttr(TypelessAttr)) {
if (!this.hasAttr("TypelessAttr")) {
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power);
}
@ -867,7 +886,7 @@ export default class Move implements Localizable {
getPriority(user: Pokemon, simulated: boolean = true) {
const priority = new NumberHolder(this.priority);
applyMoveAttrs(IncrementMovePriorityAttr, user, null, this, priority);
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority);
return priority.value;
@ -888,7 +907,7 @@ export default class Move implements Localizable {
} else if (this.id === MoveId.TRIPLE_KICK) {
effectivePower = 47.07;
} else {
const multiHitAttr = this.getAttrs(MultiHitAttr)[0];
const multiHitAttr = this.getAttrs("MultiHitAttr")[0];
if (multiHitAttr) {
effectivePower = multiHitAttr.calculateExpectedHitCount(this) * this.power;
} else {
@ -901,10 +920,10 @@ export default class Move implements Localizable {
// These are intentionally not else-if statements even though there are no
// pokemon moves that have more than one of these attributes. This allows
// the function to future proof new moves / custom move behaviors.
if (this.hasAttr(DelayedAttackAttr)) {
if (this.hasAttr("DelayedAttackAttr")) {
numTurns += 2;
}
if (this.hasAttr(RechargeAttr)) {
if (this.hasAttr("RechargeAttr")) {
numTurns += 1;
}
if (this.isChargingMove()) {
@ -930,10 +949,10 @@ export default class Move implements Localizable {
const isMultiTarget = multiple && targets.length > 1;
// ...cannot enhance multi-hit or sacrificial moves
const exceptAttrs: Constructor<MoveAttr>[] = [
MultiHitAttr,
SacrificialAttr,
SacrificialAttrOnHit
const exceptAttrs: MoveAttrString[] = [
"MultiHitAttr",
"SacrificialAttr",
"SacrificialAttrOnHit"
];
// ...and cannot enhance these specific moves
@ -959,6 +978,9 @@ export default class Move implements Localizable {
}
export class AttackMove extends Move {
override is<K extends keyof MoveClassMap>(moveSort: K): this is MoveClassMap[K] {
return moveSort === "AttackMove" || super.is(moveSort);
}
constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation);
@ -988,7 +1010,7 @@ export class AttackMove extends Move {
const [ thisStat, offStat ]: EffectiveStat[] = this.category === MoveCategory.PHYSICAL ? [ Stat.ATK, Stat.SPATK ] : [ Stat.SPATK, Stat.ATK ];
const statHolder = new NumberHolder(user.getEffectiveStat(thisStat, target));
const offStatValue = user.getEffectiveStat(offStat, target);
applyMoveAttrs(VariableAtkAttr, user, target, move, statHolder);
applyMoveAttrs("VariableAtkAttr", user, target, move, statHolder);
const statRatio = offStatValue / statHolder.value;
if (statRatio <= 0.75) {
attackScore *= 2;
@ -997,7 +1019,7 @@ export class AttackMove extends Move {
}
const power = new NumberHolder(this.calculateEffectivePower());
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
applyMoveAttrs("VariablePowerAttr", user, target, move, power);
attackScore += Math.floor(power.value / 5);
@ -1009,12 +1031,18 @@ export class StatusMove extends Move {
constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation);
}
override is<K extends keyof MoveClassMap>(moveSort: K): this is MoveClassMap[K] {
return moveSort === "StatusMove" || super.is(moveSort);
}
}
export class SelfStatusMove extends Move {
constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation);
}
override is<K extends keyof MoveClassMap>(moveSort: K): this is MoveClassMap[K] {
return moveSort === "SelfStatusMove" || super.is(moveSort);
}
}
type SubMove = new (...args: any[]) => Move;
@ -1063,8 +1091,12 @@ function ChargeMove<TBase extends SubMove>(Base: TBase) {
* @returns Array of attributes that match `attrType`, or an empty array if
* no matches are found.
*/
getChargeAttrs<T extends MoveAttr>(attrType: Constructor<T>): T[] {
return this.chargeAttrs.filter((attr): attr is T => attr instanceof attrType);
getChargeAttrs<T extends MoveAttrString>(attrType: T): (MoveAttrMap[T])[] {
const targetAttr = MoveAttrs[attrType];
if (!targetAttr) {
return [];
}
return this.chargeAttrs.filter((attr): attr is MoveAttrMap[T] => attr instanceof targetAttr);
}
/**
@ -1072,8 +1104,12 @@ function ChargeMove<TBase extends SubMove>(Base: TBase) {
* @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns `true` if a matching attribute is found; `false` otherwise
*/
hasChargeAttr<T extends MoveAttr>(attrType: Constructor<T>): boolean {
return this.chargeAttrs.some((attr) => attr instanceof attrType);
hasChargeAttr<T extends MoveAttrString>(attrType: T): boolean {
const targetAttr = MoveAttrs[attrType];
if (!targetAttr) {
return false;
}
return this.chargeAttrs.some((attr) => attr instanceof targetAttr);
}
/**
@ -1096,6 +1132,7 @@ export class ChargingSelfStatusMove extends ChargeMove(SelfStatusMove) {}
export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove;
/**
* Base class defining all {@linkcode Move} Attributes
* @abstract
@ -1105,6 +1142,22 @@ export abstract class MoveAttr {
/** Should this {@linkcode Move} target the user? */
public selfTarget: boolean;
/**
* Returns whether this attribute is of the given type.
*
* @remarks
* Used to avoid requring the caller to have imported the specific attribute type, avoiding circular dependencies.
* @param attr - The attribute to check against
* @returns Whether the attribute is an instanceof the given type.
*/
public is<T extends MoveAttrString>(attr: T): this is MoveAttrConstructorMap[MoveAttrString] {
const targetAttr = MoveAttrs[attr];
if (!targetAttr) {
return false;
}
return this instanceof targetAttr;
}
constructor(selfTarget: boolean = false) {
this.selfTarget = selfTarget;
}
@ -1271,7 +1324,7 @@ export class MoveEffectAttr extends MoveAttr {
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move);
if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) {
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
globalScene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance);
}
@ -2334,7 +2387,7 @@ export class MultiHitAttr extends MoveAttr {
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const hitType = new NumberHolder(this.intrinsicMultiHitType);
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType);
applyMoveAttrs("ChangeMultiHitTypeAttr", user, target, move, hitType);
this.multiHitType = hitType.value;
(args[0] as NumberHolder).value = this.getHitCount(user, target);
@ -3117,7 +3170,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
const allyMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer());
if (allyMovePhase) {
const allyMove = allyMovePhase.move.getMove();
if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) {
if (allyMove !== move && allyMove.hasAttr("AwaitCombinedPledgeAttr")) {
[ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id);
// "{userPokemonName} is waiting for {allyPokemonName}'s move..."
@ -3238,22 +3291,22 @@ export class StatStageChangeAttr extends MoveEffectAttr {
switch (stat) {
case Stat.ATK:
if (this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL);
noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
}
break;
case Stat.DEF:
if (!this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL);
noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
}
break;
case Stat.SPATK:
if (this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL);
noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
}
break;
case Stat.SPDEF:
if (!this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL);
noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
}
break;
}
@ -5413,7 +5466,7 @@ export class FrenzyAttr extends MoveEffectAttr {
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id);
} else {
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
applyMoveAttrs("AddBattlerTagAttr", user, target, move, args);
user.lapseTag(BattlerTagType.FRENZY); // if FRENZY is already in effect (moveQueue.length > 0), lapse the tag
}
@ -5421,15 +5474,6 @@ export class FrenzyAttr extends MoveEffectAttr {
}
}
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) {
user.getMoveQueue().shift();
}
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
return true;
};
/**
* Attribute that grants {@link https://bulbapedia.bulbagarden.net/wiki/Semi-invulnerable_turn | semi-invulnerability} to the user during
* the associated move's charging phase. Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`.
@ -5784,7 +5828,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
while (moveHistory.length) {
turnMove = moveHistory.shift();
if (!allMoves[turnMove?.move ?? MoveId.NONE].hasAttr(ProtectAttr) || turnMove?.result !== MoveResult.SUCCESS) {
if (!allMoves[turnMove?.move ?? MoveId.NONE].hasAttr("ProtectAttr") || turnMove?.result !== MoveResult.SUCCESS) {
break;
}
timesUsed++;
@ -5915,7 +5959,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
}
if ((move.chance < 0 || move.chance === 100 || user.randBattleSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr(AddArenaTrapTagAttr) && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr("AddArenaTrapTagAttr") && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side);
return true;
}
@ -6379,7 +6423,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
return (user, target, move) => {
const switchOutTarget = (this.selfSwitch ? user : target);
const player = switchOutTarget.isPlayer();
const forceSwitchAttr = move.getAttrs(ForceSwitchOutAttr).find(attr => attr.switchType === SwitchType.FORCE_SWITCH);
const forceSwitchAttr = move.getAttrs("ForceSwitchOutAttr").find(attr => attr.switchType === SwitchType.FORCE_SWITCH);
if (!this.selfSwitch) {
if (move.hitsSubstitute(user, target)) {
@ -7948,58 +7992,6 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) =
return message;
};
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
function applyMoveAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
args: any[],
): void {
move.attrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
}
function applyMoveChargeAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
args: any[],
): void {
move.chargeAttrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
}
export function applyMoveAttrs(
attrType: Constructor<MoveAttr>,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
}
export function applyFilteredMoveAttrs(
attrFilter: MoveAttrFilter,
user: Pokemon,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function applyMoveChargeAttrs(
attrType: Constructor<MoveAttr>,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
...args: any[]
): void {
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
}
export class MoveCondition {
protected func: MoveConditionFunc;
@ -8178,73 +8170,231 @@ export type MoveTargetSet = {
multiple: boolean;
};
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet {
const variableTarget = new NumberHolder(0);
user.getOpponents(false).forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
let moveTarget: MoveTarget | undefined;
if (allMoves[move].hasAttr(VariableTargetAttr)) {
moveTarget = variableTarget.value;
} else if (replaceTarget !== undefined) {
moveTarget = replaceTarget;
} else if (move) {
moveTarget = allMoves[move].moveTarget;
} else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY;
}
const opponents = user.getOpponents(false);
let set: Pokemon[] = [];
let multiple = false;
const ally: Pokemon | undefined = user.getAlly();
switch (moveTarget) {
case MoveTarget.USER:
case MoveTarget.PARTY:
set = [ user ];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = !isNullOrUndefined(ally) ? (opponents.concat([ ally ])) : opponents;
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents;
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[user.randBattleSeedInt(opponents.length)] ];
break;
case MoveTarget.ATTACKER:
return { targets: [ -1 as BattlerIndex ], multiple: false };
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = !isNullOrUndefined(ally) ? [ ally ] : [];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = !isNullOrUndefined(ally) ? [ user, ally ] : [ user ];
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = (!isNullOrUndefined(ally) ? [ user, ally ] : [ user ]).concat(opponents);
multiple = true;
break;
case MoveTarget.CURSE:
const extraTargets = !isNullOrUndefined(ally) ? [ ally ] : [];
set = user.getTypes(true).includes(PokemonType.GHOST) ? (opponents.concat(extraTargets)) : [ user ];
break;
}
return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple };
}
/**
* Map of Move attributes to their respective classes. Used for instanceof checks.
*/
const MoveAttrs = Object.freeze({
MoveEffectAttr,
MoveHeaderAttr,
MessageHeaderAttr,
AddBattlerTagAttr,
AddBattlerTagHeaderAttr,
BeakBlastHeaderAttr,
PreMoveMessageAttr,
PreUseInterruptAttr,
RespectAttackTypeImmunityAttr,
IgnoreOpponentStatStagesAttr,
HighCritAttr,
CritOnlyAttr,
FixedDamageAttr,
UserHpDamageAttr,
TargetHalfHpDamageAttr,
MatchHpAttr,
CounterDamageAttr,
LevelDamageAttr,
RandomLevelDamageAttr,
ModifiedDamageAttr,
SurviveDamageAttr,
SplashAttr,
CelebrateAttr,
RecoilAttr,
SacrificialAttr,
SacrificialAttrOnHit,
HalfSacrificialAttr,
AddSubstituteAttr,
HealAttr,
PartyStatusCureAttr,
FlameBurstAttr,
SacrificialFullRestoreAttr,
IgnoreWeatherTypeDebuffAttr,
WeatherHealAttr,
PlantHealAttr,
SandHealAttr,
BoostHealAttr,
HealOnAllyAttr,
HitHealAttr,
IncrementMovePriorityAttr,
MultiHitAttr,
ChangeMultiHitTypeAttr,
WaterShurikenMultiHitTypeAttr,
StatusEffectAttr,
MultiStatusEffectAttr,
PsychoShiftEffectAttr,
StealHeldItemChanceAttr,
RemoveHeldItemAttr,
EatBerryAttr,
StealEatBerryAttr,
HealStatusEffectAttr,
BypassSleepAttr,
BypassBurnDamageReductionAttr,
WeatherChangeAttr,
ClearWeatherAttr,
TerrainChangeAttr,
ClearTerrainAttr,
OneHitKOAttr,
InstantChargeAttr,
WeatherInstantChargeAttr,
OverrideMoveEffectAttr,
DelayedAttackAttr,
AwaitCombinedPledgeAttr,
StatStageChangeAttr,
SecretPowerAttr,
PostVictoryStatStageChangeAttr,
AcupressureStatStageChangeAttr,
GrowthStatStageChangeAttr,
CutHpStatStageBoostAttr,
OrderUpStatBoostAttr,
CopyStatsAttr,
InvertStatsAttr,
ResetStatsAttr,
SwapStatStagesAttr,
HpSplitAttr,
VariablePowerAttr,
LessPPMorePowerAttr,
MovePowerMultiplierAttr,
BeatUpAttr,
DoublePowerChanceAttr,
ConsecutiveUsePowerMultiplierAttr,
ConsecutiveUseDoublePowerAttr,
ConsecutiveUseMultiBasePowerAttr,
WeightPowerAttr,
ElectroBallPowerAttr,
GyroBallPowerAttr,
LowHpPowerAttr,
CompareWeightPowerAttr,
HpPowerAttr,
OpponentHighHpPowerAttr,
FirstAttackDoublePowerAttr,
TurnDamagedDoublePowerAttr,
MagnitudePowerAttr,
AntiSunlightPowerDecreaseAttr,
FriendshipPowerAttr,
RageFistPowerAttr,
PositiveStatStagePowerAttr,
PunishmentPowerAttr,
PresentPowerAttr,
WaterShurikenPowerAttr,
SpitUpPowerAttr,
SwallowHealAttr,
MultiHitPowerIncrementAttr,
LastMoveDoublePowerAttr,
CombinedPledgePowerAttr,
CombinedPledgeStabBoostAttr,
RoundPowerAttr,
CueNextRoundAttr,
StatChangeBeforeDmgCalcAttr,
SpectralThiefAttr,
VariableAtkAttr,
TargetAtkUserAtkAttr,
DefAtkAttr,
VariableDefAttr,
DefDefAttr,
VariableAccuracyAttr,
ThunderAccuracyAttr,
StormAccuracyAttr,
AlwaysHitMinimizeAttr,
ToxicAccuracyAttr,
BlizzardAccuracyAttr,
VariableMoveCategoryAttr,
PhotonGeyserCategoryAttr,
TeraMoveCategoryAttr,
TeraBlastPowerAttr,
StatusCategoryOnAllyAttr,
ShellSideArmCategoryAttr,
VariableMoveTypeAttr,
FormChangeItemTypeAttr,
TechnoBlastTypeAttr,
AuraWheelTypeAttr,
RagingBullTypeAttr,
IvyCudgelTypeAttr,
WeatherBallTypeAttr,
TerrainPulseTypeAttr,
HiddenPowerTypeAttr,
TeraBlastTypeAttr,
TeraStarstormTypeAttr,
MatchUserTypeAttr,
CombinedPledgeTypeAttr,
VariableMoveTypeMultiplierAttr,
NeutralDamageAgainstFlyingTypeMultiplierAttr,
IceNoEffectTypeAttr,
FlyingTypeMultiplierAttr,
VariableMoveTypeChartAttr,
FreezeDryAttr,
OneHitKOAccuracyAttr,
SheerColdAccuracyAttr,
MissEffectAttr,
NoEffectAttr,
TypelessAttr,
BypassRedirectAttr,
FrenzyAttr,
SemiInvulnerableAttr,
LeechSeedAttr,
FallDownAttr,
GulpMissileTagAttr,
JawLockAttr,
CurseAttr,
LapseBattlerTagAttr,
RemoveBattlerTagAttr,
FlinchAttr,
ConfuseAttr,
RechargeAttr,
TrapAttr,
ProtectAttr,
IgnoreAccuracyAttr,
FaintCountdownAttr,
RemoveAllSubstitutesAttr,
HitsTagAttr,
HitsTagForDoubleDamageAttr,
AddArenaTagAttr,
RemoveArenaTagsAttr,
AddArenaTrapTagAttr,
AddArenaTrapTagHitAttr,
RemoveArenaTrapAttr,
RemoveScreensAttr,
SwapArenaTagsAttr,
AddPledgeEffectAttr,
RevivalBlessingAttr,
ForceSwitchOutAttr,
ChillyReceptionAttr,
RemoveTypeAttr,
CopyTypeAttr,
CopyBiomeTypeAttr,
ChangeTypeAttr,
AddTypeAttr,
FirstMoveTypeAttr,
CallMoveAttr,
RandomMoveAttr,
RandomMovesetMoveAttr,
NaturePowerAttr,
CopyMoveAttr,
RepeatMoveAttr,
ReducePpMoveAttr,
AttackReducePpMoveAttr,
MovesetCopyMoveAttr,
SketchAttr,
AbilityChangeAttr,
AbilityCopyAttr,
AbilityGiveAttr,
SwitchAbilitiesAttr,
SuppressAbilitiesAttr,
TransformAttr,
SwapStatAttr,
ShiftStatAttr,
AverageStatsAttr,
MoneyAttr,
DestinyBondAttr,
AddBattlerTagIfBoostedAttr,
StatusIfBoostedAttr,
LastResortAttr,
VariableTargetAttr,
AfterYouAttr,
ForceLastAttr,
HitsSameTypeAttr,
ResistLastMoveTypeAttr,
ExposedMoveAttr,
});
/** Map of names */
export type MoveAttrConstructorMap = typeof MoveAttrs;
export const selfStatLowerMoves: MoveId[] = [];
@ -11258,7 +11408,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.TOXIC)
);
allMoves.map(m => {
if (m.getAttrs(StatStageChangeAttr).some(a => a.selfTarget && a.stages < 0)) {
if (m.getAttrs("StatStageChangeAttr").some(a => a.selfTarget && a.stages < 0)) {
selfStatLowerMoves.push(m.id);
}
});

View File

@ -30,7 +30,6 @@ import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import type { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#enums/battler-index";
import { SelfStatusMove } from "#app/data/moves/move";
import { PokeballType } from "#enums/pokeball";
import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -175,7 +174,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
// Check what type of move the egg move is to determine target
const pokemonMove = new PokemonMove(eggMove);
const move = pokemonMove.getMove();
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
const target = move.is("SelfStatusMove") ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,

View File

@ -1,7 +1,6 @@
import type Pokemon from "../field/pokemon";
import type Move from "./moves/move";
import { PokemonType } from "#enums/pokemon-type";
import { ProtectAttr } from "./moves/move";
import type { BattlerIndex } from "#enums/battler-index";
import i18next from "i18next";
@ -55,7 +54,7 @@ export class Terrain {
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
switch (this.terrainType) {
case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) {
if (!move.hasAttr("ProtectAttr")) {
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return (
move.getPriority(user) > 0 &&

View File

@ -4,7 +4,6 @@ import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type";
import type Move from "./moves/move";
import { AttackMove } from "./moves/move";
import { randSeedInt } from "#app/utils/common";
import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
import { TerrainType, getTerrainName } from "./terrain";
@ -95,9 +94,9 @@ export class Weather {
switch (this.weatherType) {
case WeatherType.HARSH_SUN:
return move instanceof AttackMove && moveType === PokemonType.WATER;
return move.is("AttackMove") && moveType === PokemonType.WATER;
case WeatherType.HEAVY_RAIN:
return move instanceof AttackMove && moveType === PokemonType.FIRE;
return move.is("AttackMove") && moveType === PokemonType.FIRE;
}
return false;

View File

@ -1,4 +1,3 @@
export enum BattlerTagLapseType {
FAINT,
MOVE,

View File

@ -9,36 +9,8 @@ import BattleInfo from "#app/ui/battle-info/battle-info";
import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info";
import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info";
import type Move from "#app/data/moves/move";
import {
HighCritAttr,
HitsTagAttr,
applyMoveAttrs,
FixedDamageAttr,
VariableAtkAttr,
TypelessAttr,
CritOnlyAttr,
getMoveTargets,
OneHitKOAttr,
VariableMoveTypeAttr,
VariableDefAttr,
AttackMove,
ModifiedDamageAttr,
VariableMoveTypeMultiplierAttr,
IgnoreOpponentStatStagesAttr,
SacrificialAttr,
VariableMoveCategoryAttr,
CounterDamageAttr,
StatStageChangeAttr,
RechargeAttr,
IgnoreWeatherTypeDebuffAttr,
BypassBurnDamageReductionAttr,
SacrificialAttrOnHit,
OneHitKOAccuracyAttr,
RespectAttackTypeImmunityAttr,
CombinedPledgeStabBoostAttr,
VariableMoveTypeChartAttr,
HpSplitAttr,
} from "#app/data/moves/move";
import { getMoveTargets } from "#app/data/moves/move-utils";
import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
import { allMoves } from "#app/data/data-lists";
import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory";
@ -1428,7 +1400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
getCritStage(source: Pokemon, move: Move): number {
const critStage = new NumberHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critStage);
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
@ -1454,7 +1426,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
getMoveCategory(target: Pokemon, move: Move): MoveCategory {
const moveCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, this, target, move, moveCategory);
applyMoveAttrs("VariableMoveCategoryAttr", this, target, move, moveCategory);
return moveCategory.value;
}
@ -2349,7 +2321,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public getMoveType(move: Move, simulated = true): PokemonType {
const moveTypeHolder = new NumberHolder(move.type);
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder);
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
@ -2393,18 +2365,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.turnData?.moveEffectiveness;
}
if (move.hasAttr(TypelessAttr)) {
if (move.hasAttr("TypelessAttr")) {
return 1;
}
const moveType = source.getMoveType(move);
const typeMultiplier = new NumberHolder(
move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr)
move.category !== MoveCategory.STATUS || move.hasAttr("RespectAttackTypeImmunityAttr")
? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion)
: 1,
);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
applyMoveAttrs("VariableMoveTypeMultiplierAttr", source, this, move, typeMultiplier);
if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) {
typeMultiplier.value = 0;
}
@ -2431,7 +2403,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
for (const tag of immuneTags) {
if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) {
if (move && !move.getAttrs("HitsTagAttr").some(attr => attr.tagType === tag.tagType)) {
typeMultiplier.value = 0;
break;
}
@ -2487,7 +2459,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (move) {
applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType);
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType);
}
if (source) {
const ignoreImmunity = new BooleanHolder(false);
@ -3123,24 +3095,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Bosses never get self ko moves or Pain Split
if (this.isBoss()) {
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr) && !allMoves[m[0]].hasAttr(HpSplitAttr));
movePool = movePool.filter(
m => !allMoves[m[0]].hasAttr("SacrificialAttr") && !allMoves[m[0]].hasAttr("HpSplitAttr"),
);
}
// No one gets Memento or Final Gambit
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit));
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("SacrificialAttrOnHit"));
if (this.hasTrainer()) {
// Trainers never get OHKO moves
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr));
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("OneHitKOAttr"));
// Half the weight of self KO moves
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr("SacrificialAttr") ? 0.5 : 1)]);
// Trainers get a weight bump to stat buffing moves
movePool = movePool.map(m => [
m[0],
m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1),
m[1] * (allMoves[m[0]].getAttrs("StatStageChangeAttr").some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1),
]);
// Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [
m[0],
m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1),
m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr("RechargeAttr") ? 0.7 : 1),
]);
}
@ -3207,7 +3181,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!this.moveset.some(
mo =>
m[0] === mo.moveId ||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed
(allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed
),
)
.map(m => {
@ -3236,7 +3210,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!this.moveset.some(
mo =>
m[0] === mo.moveId ||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed
(allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed
),
);
}
@ -3444,7 +3418,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage);
}
if (move) {
applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage);
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
}
}
@ -3469,7 +3443,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns The calculated accuracy multiplier.
*/
getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number {
const isOhko = sourceMove.hasAttr(OneHitKOAccuracyAttr);
const isOhko = sourceMove.hasAttr("OneHitKOAccuracyAttr");
if (isOhko) {
return 1;
}
@ -3482,7 +3456,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage);
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage);
applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage);
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
@ -3565,7 +3539,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated,
),
);
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
applyMoveAttrs("VariableAtkAttr", source, this, move, sourceAtk);
/**
* This Pokemon's defensive stat for the given move's category.
@ -3583,7 +3557,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated,
),
);
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
applyMoveAttrs("VariableDefAttr", source, this, move, targetDef);
/**
* The attack's base damage, as determined by the source's level, move power
@ -3610,7 +3584,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number {
// If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle)
if (move.hasAttr(TypelessAttr)) {
if (move.hasAttr("TypelessAttr")) {
return 1;
}
const sourceTypes = source.getTypes();
@ -3622,7 +3596,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
stabMultiplier.value += 0.5;
}
applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier);
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
if (!ignoreSourceAbility) {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
@ -3671,7 +3645,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const variableCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory);
applyMoveAttrs("VariableMoveCategoryAttr", source, this, move, variableCategory);
const moveCategory = variableCategory.value as MoveCategory;
/** The move's type after type-changing effects are applied */
@ -3696,7 +3670,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const arenaAttackTypeMultiplier = new NumberHolder(
globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()),
);
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier);
applyMoveAttrs("IgnoreWeatherTypeDebuffAttr", source, this, move, arenaAttackTypeMultiplier);
const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0;
@ -3710,7 +3684,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If the attack deals fixed damage, return a result with that much damage
const fixedDamage = new NumberHolder(0);
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage);
if (fixedDamage.value) {
const multiLensMultiplier = new NumberHolder(1);
globalScene.applyModifiers(
@ -3732,7 +3706,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP
const isOneHitKo = new BooleanHolder(false);
applyMoveAttrs(OneHitKOAttr, source, this, move, isOneHitKo);
applyMoveAttrs("OneHitKOAttr", source, this, move, isOneHitKo);
if (isOneHitKo.value) {
return {
cancelled: false,
@ -3809,7 +3783,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isPhysical &&
source.status &&
source.status.effect === StatusEffect.BURN &&
!move.hasAttr(BypassBurnDamageReductionAttr)
!move.hasAttr("BypassBurnDamageReductionAttr")
) {
const burnDamageReductionCancelled = new BooleanHolder(false);
if (!ignoreSourceAbility) {
@ -3843,7 +3817,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
const hitsTagMultiplier = new NumberHolder(1);
move
.getAttrs(HitsTagAttr)
.getAttrs("HitsTagAttr")
.filter(hta => hta.doubleDamage)
.forEach(hta => {
if (this.getTag(hta.tagType)) {
@ -3900,7 +3874,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
// This attribute may modify damage arbitrarily, so be careful about changing its order of application.
applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage);
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
if (this.isFullHp() && !ignoreAbility) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage);
@ -3936,7 +3910,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) {
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) {
return false;
}
const isCritical = new BooleanHolder(false);
@ -3944,7 +3918,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
isCritical.value = true;
}
applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical);
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
@ -6265,7 +6239,7 @@ export class EnemyPokemon extends Pokemon {
.targets.map(ind => fieldPokemon[ind])
.filter(p => this.isPlayer() !== p.isPlayer());
// Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus
const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
const isCritical = move.hasAttr("CritOnlyAttr") || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
return (
move.category !== MoveCategory.STATUS &&
@ -6334,7 +6308,7 @@ export class EnemyPokemon extends Pokemon {
![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id)
) {
targetScore = -20;
} else if (move instanceof AttackMove) {
} else if (move.is("AttackMove")) {
/**
* Attack moves are given extra multipliers to their base benefit score based on
* the move's type effectiveness against the target and whether the move is a STAB move.
@ -6455,7 +6429,7 @@ export class EnemyPokemon extends Pokemon {
if (!sortedBenefitScores.length) {
// Set target to BattlerIndex.ATTACKER when using a counter move
// This is the same as when the player does so
if (move.hasAttr(CounterDamageAttr)) {
if (move.hasAttr("CounterDamageAttr")) {
return [BattlerIndex.ATTACKER];
}

View File

@ -2,7 +2,6 @@ import { globalScene } from "#app/global-scene";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms";
import { getBerryEffectDescription, getBerryName } from "#app/data/berry";
import { AttackMove } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { getNatureName, getNatureStatMultiplier } from "#app/data/nature";
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
@ -1337,7 +1336,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
p
.getMoveset()
.map(m => m.getMove())
.filter(m => m instanceof AttackMove)
.filter(m => m.is("AttackMove"))
.map(m => m.type),
);
if (!attackMoveTypes.length) {

View File

@ -4,7 +4,7 @@ import { BattleType } from "#enums/battle-type";
import type { EncoreTag } from "#app/data/battler-tags";
import { TrappedTag } from "#app/data/battler-tags";
import type { MoveTargetSet } from "#app/data/moves/move";
import { getMoveTargets } from "#app/data/moves/move";
import { getMoveTargets } from "#app/data/moves/move-utils";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#app/enums/battler-tag-type";

View File

@ -11,7 +11,6 @@ import {
} from "#app/data/abilities/ability";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { battleSpecDialogue } from "#app/data/dialogue";
import { PostVictoryStatStageChangeAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { BattleSpec } from "#app/enums/battle-spec";
@ -145,7 +144,7 @@ export class FaintPhase extends PokemonPhase {
if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr);
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
if (pvattrs.length) {
for (const pvattr of pvattrs) {
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#enums/battler-index";
import { MoveChargeAnim } from "#app/data/battle-anims";
import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/moves/move";
import { applyMoveChargeAttrs } from "#app/data/moves/apply-attrs";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon";
import { MoveResult } from "#enums/move-result";
@ -43,7 +43,7 @@ export class MoveChargePhase extends PokemonPhase {
new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => {
move.showChargeText(user, target);
applyMoveChargeAttrs(MoveEffectAttr, user, target, move);
applyMoveChargeAttrs("MoveEffectAttr", user, target, move);
user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
this.end();
});
@ -57,7 +57,7 @@ export class MoveChargePhase extends PokemonPhase {
if (move.isChargingMove()) {
const instantCharge = new BooleanHolder(false);
applyMoveChargeAttrs(InstantChargeAttr, user, null, move, instantCharge);
applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge);
if (instantCharge.value) {
// this MoveEndPhase will be duplicated by the queued MovePhase if not removed

View File

@ -28,23 +28,9 @@ import {
} from "#app/data/battler-tags";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import type { MoveAttr } from "#app/data/moves/move";
import {
applyFilteredMoveAttrs,
applyMoveAttrs,
AttackMove,
DelayedAttackAttr,
FlinchAttr,
getMoveTargets,
HitsTagAttr,
MissEffectAttr,
MoveEffectAttr,
MultiHitAttr,
NoEffectAttr,
OneHitKOAttr,
OverrideMoveEffectAttr,
StatChangeBeforeDmgCalcAttr,
ToxicAccuracyAttr,
} from "#app/data/moves/move";
import { MoveEffectAttr } from "#app/data/moves/move";
import { getMoveTargets } from "#app/data/moves/move-utils";
import { applyFilteredMoveAttrs, applyMoveAttrs } from "#app/data/moves/apply-attrs";
import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget";
@ -241,13 +227,13 @@ export class MoveEffectPhase extends PokemonPhase {
case HitCheckResult.NO_EFFECT_NO_MESSAGE:
case HitCheckResult.PROTECTED:
case HitCheckResult.TARGET_NOT_ON_FIELD:
applyMoveAttrs(NoEffectAttr, user, target, this.move);
applyMoveAttrs("NoEffectAttr", user, target, this.move);
break;
case HitCheckResult.MISS:
globalScene.phaseManager.queueMessage(
i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }),
);
applyMoveAttrs(MissEffectAttr, user, target, this.move);
applyMoveAttrs("MissEffectAttr", user, target, this.move);
break;
case HitCheckResult.REFLECTED:
this.queueReflectedMove(user, target);
@ -277,7 +263,7 @@ export class MoveEffectPhase extends PokemonPhase {
globalScene.currentBattle.lastPlayerInvolved = this.fieldIndex;
}
const isDelayedAttack = this.move.hasAttr(DelayedAttackAttr);
const isDelayedAttack = this.move.hasAttr("DelayedAttackAttr");
/** If the user was somehow removed from the field and it's not a delayed attack, end this phase */
if (!user.isOnField()) {
if (!isDelayedAttack) {
@ -303,7 +289,7 @@ export class MoveEffectPhase extends PokemonPhase {
const move = this.move;
// Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.virtual);
applyMoveAttrs("OverrideMoveEffectAttr", user, this.getFirstTarget() ?? null, move, overridden, this.virtual);
// If other effects were overriden, stop this phase before they can be applied
if (overridden.value) {
@ -330,7 +316,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === -1) {
const hitCount = new NumberHolder(1);
// Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount);
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
// If Parental Bond is applicable, add another hit
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null);
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
@ -362,7 +348,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Play the animation if the move was successful against any of its targets or it has a POST_TARGET effect (like self destruct)
if (
this.moveHistoryEntry.result === MoveResult.SUCCESS ||
move.getAttrs(MoveEffectAttr).some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET)
move.getAttrs("MoveEffectAttr").some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET)
) {
const firstTarget = this.getFirstTarget();
new MoveAnim(
@ -461,7 +447,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @returns a function intended to be passed into a `then()` call.
*/
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void {
if (this.move.hasAttr(FlinchAttr)) {
if (this.move.hasAttr("FlinchAttr")) {
return;
}
@ -603,7 +589,7 @@ export class MoveEffectPhase extends PokemonPhase {
const bypassAccuracy =
bypassAccAndInvuln ||
target.getTag(BattlerTagType.ALWAYS_GET_HIT) ||
(target.getTag(BattlerTagType.TELEKINESIS) && !this.move.hasAttr(OneHitKOAttr));
(target.getTag(BattlerTagType.TELEKINESIS) && !this.move.hasAttr("OneHitKOAttr"));
if (moveAccuracy === -1 || bypassAccuracy) {
return [HitCheckResult.HIT, effectiveness];
@ -644,7 +630,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
return true;
}
if (this.move.hasAttr(ToxicAccuracyAttr) && user.isOfType(PokemonType.POISON)) {
if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) {
return true;
}
// TODO: Fix lock on / mind reader check.
@ -669,7 +655,7 @@ export class MoveEffectPhase extends PokemonPhase {
return false;
}
const move = this.move;
return move.getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType);
return move.getAttrs("HitsTagAttr").some(hta => hta.tagType === semiInvulnerableTag.tagType);
}
/** @returns The {@linkcode Pokemon} using this phase's invoked move */
@ -823,7 +809,7 @@ export class MoveEffectPhase extends PokemonPhase {
* Apply stat changes from {@linkcode move} and gives it to {@linkcode source}
* before damage calculation
*/
applyMoveAttrs(StatChangeBeforeDmgCalcAttr, user, target, this.move);
applyMoveAttrs("StatChangeBeforeDmgCalcAttr", user, target, this.move);
const { result, damage: dmg } = target.getAttackDamage({
source: user,
@ -1001,12 +987,12 @@ export class MoveEffectPhase extends PokemonPhase {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult);
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
if (!user.isPlayer() && this.move instanceof AttackMove) {
if (!user.isPlayer() && this.move.is("AttackMove")) {
globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
}
// Apply Grip Claw's chance to steal an item from the target
if (this.move instanceof AttackMove) {
if (this.move.is("AttackMove")) {
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
}
}

View File

@ -1,4 +1,4 @@
import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/moves/move";
import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon";
import { BattlePhase } from "./battle-phase";
@ -23,7 +23,7 @@ export class MoveHeaderPhase extends BattlePhase {
super.start();
if (this.canMove()) {
applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove());
applyMoveAttrs("MoveHeaderAttr", this.pokemon, null, this.move.getMove());
}
this.end();
}

View File

@ -15,18 +15,8 @@ import type { DelayedAttackTag } from "#app/data/arena-tag";
import { CommonAnim } from "#enums/move-anims-common";
import { CenterOfAttentionTag } from "#app/data/battler-tags";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import {
AddArenaTrapTagAttr,
applyMoveAttrs,
BypassRedirectAttr,
BypassSleepAttr,
CopyMoveAttr,
DelayedAttackAttr,
frenzyMissFunc,
HealStatusEffectAttr,
PreMoveMessageAttr,
PreUseInterruptAttr,
} from "#app/data/moves/move";
import type { HealStatusEffectAttr } from "#app/data/moves/move";
import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
import { allMoves } from "#app/data/data-lists";
import { MoveFlags } from "#enums/MoveFlags";
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
@ -47,6 +37,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id";
import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next";
import { frenzyMissFunc } from "#app/data/moves/move-utils";
export class MovePhase extends BattlePhase {
public readonly phaseName = "MovePhase";
@ -205,7 +196,7 @@ export class MovePhase extends BattlePhase {
const moveQueue = this.pokemon.getMoveQueue();
if (
(targets.length === 0 && !this.move.getMove().hasAttr(AddArenaTrapTagAttr)) ||
(targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) ||
(moveQueue.length && moveQueue[0].move === MoveId.NONE)
) {
this.showMoveText();
@ -234,7 +225,7 @@ export class MovePhase extends BattlePhase {
Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
break;
case StatusEffect.SLEEP: {
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs(
ReduceStatusEffectDurationAbAttr,
@ -254,7 +245,10 @@ export class MovePhase extends BattlePhase {
!!this.move
.getMove()
.findAttr(
attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE),
attr =>
attr.is("HealStatusEffectAttr") &&
attr.selfTarget &&
(attr as unknown as HealStatusEffectAttr).isOfEffect(StatusEffect.FREEZE),
) ||
(!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) ||
Overrides.STATUS_ACTIVATION_OVERRIDE === false;
@ -304,7 +298,7 @@ export class MovePhase extends BattlePhase {
// form changes happen even before we know that the move wll execute.
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr);
const isDelayedAttack = this.move.getMove().hasAttr("DelayedAttackAttr");
if (isDelayedAttack) {
// Check the player side arena if future sight is active
const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT);
@ -332,7 +326,7 @@ export class MovePhase extends BattlePhase {
let success = true;
// Check if there are any attributes that can interrupt the move, overriding the fail message.
for (const move of this.move.getMove().getAttrs(PreUseInterruptAttr)) {
for (const move of this.move.getMove().getAttrs("PreUseInterruptAttr")) {
if (move.apply(this.pokemon, targets[0], this.move.getMove())) {
success = false;
break;
@ -387,7 +381,7 @@ export class MovePhase extends BattlePhase {
}
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {
if (!allMoves[this.move.moveId].hasAttr("CopyMoveAttr")) {
// The last move used is unaffected by moves that fail
if (success) {
globalScene.currentBattle.lastMove = this.move.moveId;
@ -544,7 +538,7 @@ export class MovePhase extends BattlePhase {
});
if (currentTarget !== redirectTarget.value) {
const bypassRedirectAttrs = this.move.getMove().getAttrs(BypassRedirectAttr);
const bypassRedirectAttrs = this.move.getMove().getAttrs("BypassRedirectAttr");
bypassRedirectAttrs.forEach(attr => {
if (!attr.abilitiesOnly || redirectedByAbility) {
redirectTarget.value = currentTarget;
@ -665,7 +659,7 @@ export class MovePhase extends BattlePhase {
}),
500,
);
applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
}
public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void {

View File

@ -1,5 +1,4 @@
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/abilities/ability";
import { MoveHeaderAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { Stat } from "#app/enums/stat";
@ -168,7 +167,7 @@ export class TurnStartPhase extends FieldPhase {
const move =
pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) ||
new PokemonMove(queuedMove.move);
if (move.getMove().hasAttr(MoveHeaderAttr)) {
if (move.getMove().hasAttr("MoveHeaderAttr")) {
phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move);
}
if (pokemon.isPlayer()) {

View File

@ -12,7 +12,6 @@ import {
PokemonHeldItemModifier,
SwitchEffectTransferModifier,
} from "#app/modifier/modifier";
import { ForceSwitchOutAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
import { StatusEffect } from "#enums/status-effect";
@ -1178,7 +1177,7 @@ export default class PartyUiHandler extends MessageUiHandler {
return !!(
this.partyUiMode === PartyUiMode.FAINT_SWITCH &&
moveHistory.length &&
allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() &&
allMoves[moveHistory[moveHistory.length - 1].move].getAttrs("ForceSwitchOutAttr")[0]?.isBatonPass() &&
moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS
);
}

View File

@ -2,7 +2,7 @@ import { BattlerIndex } from "#enums/battler-index";
import { UiMode } from "#enums/ui-mode";
import UiHandler from "./ui-handler";
import { isNullOrUndefined, fixedInt } from "#app/utils/common";
import { getMoveTargets } from "../data/moves/move";
import { getMoveTargets } from "#app/data/moves/move-utils";
import { Button } from "#enums/buttons";
import type { MoveId } from "#enums/move-id";
import type Pokemon from "#app/field/pokemon";

View File

@ -1,4 +1,3 @@
import { MultiHitAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { MultiHitType } from "#enums/MultiHitType";
import { Status } from "#app/data/status-effect";
@ -66,7 +65,7 @@ describe("Abilities - BATTLE BOND", () => {
vi.spyOn(waterShuriken, "calculateBattlePower");
let actualMultiHitType: MultiHitType | null = null;
const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0];
const multiHitAttr = waterShuriken.getAttrs("MultiHitAttr")[0];
vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => {
actualMultiHitType = multiHitAttr.getMultiHitType();
return 3;

View File

@ -6,7 +6,6 @@ import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { allMoves } from "#app/data/data-lists";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FlinchAttr } from "#app/data/moves/move";
describe("Abilities - Serene Grace", () => {
let phaserGame: Phaser.Game;
@ -39,7 +38,7 @@ describe("Abilities - Serene Grace", () => {
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
const airSlashMove = allMoves[MoveId.AIR_SLASH];
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
const airSlashFlinchAttr = airSlashMove.getAttrs("FlinchAttr")[0];
vi.spyOn(airSlashFlinchAttr, "getMoveChance");
game.move.select(MoveId.AIR_SLASH);

View File

@ -7,7 +7,6 @@ import { Stat } from "#enums/stat";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FlinchAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
describe("Abilities - Sheer Force", () => {
@ -43,7 +42,7 @@ describe("Abilities - Sheer Force", () => {
const airSlashMove = allMoves[MoveId.AIR_SLASH];
vi.spyOn(airSlashMove, "calculateBattlePower");
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
const airSlashFlinchAttr = airSlashMove.getAttrs("FlinchAttr")[0];
vi.spyOn(airSlashFlinchAttr, "getMoveChance");
game.move.select(MoveId.AIR_SLASH);
@ -98,7 +97,7 @@ describe("Abilities - Sheer Force", () => {
const enemyPokemon = game.scene.getEnemyPokemon();
const headbuttMove = allMoves[MoveId.HEADBUTT];
vi.spyOn(headbuttMove, "calculateBattlePower");
const headbuttFlinchAttr = headbuttMove.getAttrs(FlinchAttr)[0];
const headbuttFlinchAttr = headbuttMove.getAttrs("FlinchAttr")[0];
vi.spyOn(headbuttFlinchAttr, "getMoveChance");
game.move.select(MoveId.HEADBUTT);

View File

@ -1,7 +1,6 @@
import type BattleScene from "#app/battle-scene";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type Move from "#app/data/moves/move";
import { CritOnlyAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { ArenaTagType } from "#app/enums/arena-tag-type";
import type Pokemon from "#app/field/pokemon";
@ -166,7 +165,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
if (move.getAttrs(CritOnlyAttr).length === 0) {
if (move.getAttrs("CritOnlyAttr").length === 0) {
globalScene.arena.applyTagsForSide(
ArenaTagType.AURORA_VEIL,
side,

View File

@ -1,5 +1,5 @@
import { BattlerIndex } from "#enums/battler-index";
import { RandomMoveAttr } from "#app/data/moves/move";
import type { RandomMoveAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { Stat } from "#app/enums/stat";
import { MoveResult } from "#enums/move-result";
@ -27,7 +27,7 @@ describe("Moves - Copycat", () => {
});
beforeEach(() => {
randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs(RandomMoveAttr)[0];
randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0];
game = new GameManager(phaserGame);
game.override
.moveset([MoveId.COPYCAT, MoveId.SPIKY_SHIELD, MoveId.SWORDS_DANCE, MoveId.SPLASH])

View File

@ -1,7 +1,6 @@
import type BattleScene from "#app/battle-scene";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type Move from "#app/data/moves/move";
import { CritOnlyAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#app/enums/arena-tag-type";
@ -129,7 +128,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
if (move.getAttrs(CritOnlyAttr).length === 0) {
if (move.getAttrs("CritOnlyAttr").length === 0) {
globalScene.arena.applyTagsForSide(
ArenaTagType.LIGHT_SCREEN,
side,

View File

@ -1,5 +1,5 @@
import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags";
import { RandomMoveAttr } from "#app/data/moves/move";
import type { RandomMoveAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { Stat } from "#app/enums/stat";
@ -27,7 +27,7 @@ describe("Moves - Metronome", () => {
});
beforeEach(() => {
randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs(RandomMoveAttr)[0];
randomMoveAttr = allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0];
game = new GameManager(phaserGame);
game.override
.moveset([MoveId.METRONOME, MoveId.SPLASH])

View File

@ -1,4 +1,3 @@
import { RandomMoveAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
@ -49,7 +48,7 @@ describe("Moves - Moongeist Beam", () => {
// Also covers Photon Geyser and Sunsteel Strike
it("should not ignore enemy abilities when called by another move, such as metronome", async () => {
await game.classicMode.startBattle([SpeciesId.MILOTIC]);
vi.spyOn(allMoves[MoveId.METRONOME].getAttrs(RandomMoveAttr)[0], "getMoveOverride").mockReturnValue(
vi.spyOn(allMoves[MoveId.METRONOME].getAttrs("RandomMoveAttr")[0], "getMoveOverride").mockReturnValue(
MoveId.MOONGEIST_BEAM,
);

View File

@ -1,7 +1,6 @@
import { BattlerIndex } from "#enums/battler-index";
import { allAbilities } from "#app/data/data-lists";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { FlinchAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { PokemonType } from "#enums/pokemon-type";
import { ArenaTagType } from "#enums/arena-tag-type";
@ -228,7 +227,7 @@ describe("Moves - Pledge Moves", () => {
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
const ironHeadFlinchAttr = allMoves[MoveId.IRON_HEAD].getAttrs(FlinchAttr)[0];
const ironHeadFlinchAttr = allMoves[MoveId.IRON_HEAD].getAttrs("FlinchAttr")[0];
vi.spyOn(ironHeadFlinchAttr, "getMoveChance");
game.move.select(MoveId.WATER_PLEDGE, 0, BattlerIndex.ENEMY);

View File

@ -1,7 +1,6 @@
import type BattleScene from "#app/battle-scene";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type Move from "#app/data/moves/move";
import { CritOnlyAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#app/enums/arena-tag-type";
@ -145,7 +144,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
if (move.getAttrs(CritOnlyAttr).length === 0) {
if (move.getAttrs("CritOnlyAttr").length === 0) {
globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder);
}
}

View File

@ -1,5 +1,5 @@
import { BattlerIndex } from "#enums/battler-index";
import { ShellSideArmCategoryAttr } from "#app/data/moves/move";
import type { ShellSideArmCategoryAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import type Move from "#app/data/moves/move";
import { AbilityId } from "#enums/ability-id";
@ -27,7 +27,7 @@ describe("Moves - Shell Side Arm", () => {
beforeEach(() => {
shellSideArm = allMoves[MoveId.SHELL_SIDE_ARM];
shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
shellSideArmAttr = shellSideArm.getAttrs("ShellSideArmCategoryAttr")[0];
game = new GameManager(phaserGame);
game.override
.moveset([MoveId.SHELL_SIDE_ARM, MoveId.SPLASH])

View File

@ -1,6 +1,6 @@
import { BattlerIndex } from "#enums/battler-index";
import { Stat } from "#enums/stat";
import { TeraMoveCategoryAttr } from "#app/data/moves/move";
import type { TeraMoveCategoryAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import type Move from "#app/data/moves/move";
import { PokemonType } from "#enums/pokemon-type";
@ -23,7 +23,7 @@ describe("Moves - Tera Blast", () => {
type: Phaser.HEADLESS,
});
moveToCheck = allMoves[MoveId.TERA_BLAST];
teraBlastAttr = moveToCheck.getAttrs(TeraMoveCategoryAttr)[0];
teraBlastAttr = moveToCheck.getAttrs("TeraMoveCategoryAttr")[0];
});
afterEach(() => {

View File

@ -1,4 +1,4 @@
import { FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move";
import type { FlinchAttr, StatStageChangeAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
@ -20,8 +20,8 @@ describe("Moves - Triple Arrows", () => {
type: Phaser.HEADLESS,
});
tripleArrows = allMoves[MoveId.TRIPLE_ARROWS];
flinchAttr = tripleArrows.getAttrs(FlinchAttr)[0];
defDropAttr = tripleArrows.getAttrs(StatStageChangeAttr)[0];
flinchAttr = tripleArrows.getAttrs("FlinchAttr")[0];
defDropAttr = tripleArrows.getAttrs("StatStageChangeAttr")[0];
});
afterEach(() => {

View File

@ -1,5 +1,5 @@
import type { BattlerIndex } from "#enums/battler-index";
import { getMoveTargets } from "#app/data/moves/move";
import { getMoveTargets } from "#app/data/moves/move-utils";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import Overrides from "#app/overrides";