From 11e720f3bf88b07f91a737d08ea0a0b11cfe790c Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sun, 16 Feb 2025 09:03:51 -0600 Subject: [PATCH] Rename attribute --- src/data/move.ts | 29 ++++++++++++++++++++++------- src/phases/move-phase.ts | 28 +++++++++++++++++++++------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 91022066735..fead6206e93 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1317,8 +1317,14 @@ export class PreMoveMessageAttr extends MoveAttr { } } -export class MoveInterruptedMessageAttr extends MoveAttr { - protected message: string | ((user: Pokemon, target: Pokemon, move: Move) => string); +/** + * Attribute for moves that can be conditionally interrupted to be considered to + * have failed before their "useMove" message is displayed. Currently used by + * Focus Punch. + * @extends MoveAttr + */ +export class PreUseInterruptAttr extends MoveAttr { + protected message: string | ((user: Pokemon, target: Pokemon, move: Move) => string) | undefined; protected overridesFailedMessage: boolean; protected conditionFunc: MoveConditionFunc; @@ -1326,12 +1332,22 @@ export class MoveInterruptedMessageAttr extends MoveAttr { * Create a new MoveInterruptedMessageAttr. * @param message The message to display when the move is interrupted, or a function that formats the message based on the user, target, and move. */ - constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string), conditionFunc?: MoveConditionFunc) { + constructor(message?: string | ((user: Pokemon, target: Pokemon, move: Move) => string), conditionFunc?: MoveConditionFunc) { super(); this.message = message; this.conditionFunc = conditionFunc ?? (() => true); } + /** + * Message to display when a move is interrupted. + * @param user {@linkcode Pokemon} using the move + * @param target {@linkcode Pokemon} target of the move + * @param move {@linkcode Move} with this attribute + */ + override apply(user: Pokemon, target: Pokemon, move: Move): boolean { + return this.conditionFunc(user, target, move); + } + /** * Message to display when a move is interrupted. * @param user {@linkcode Pokemon} using the move @@ -1339,7 +1355,7 @@ export class MoveInterruptedMessageAttr extends MoveAttr { * @param move {@linkcode Move} with this attribute */ override getFailedText(user: Pokemon, target: Pokemon, move: Move): string | undefined { - if (this.conditionFunc(user, target, move)) { + if (this.message && this.conditionFunc(user, target, move)) { const message = typeof this.message === "string" ? (this.message as string) @@ -9208,9 +9224,8 @@ export function initMoves() { .attr(BypassBurnDamageReductionAttr), new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3) .attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) })) - .attr(MoveInterruptedMessageAttr, i18next.t("moveTriggers:lostFocus"), user => !!user.turnData.attacksReceived.find(r => r.damage)) - .punchingMove() - .condition((user, target, move) => !user.turnData.attacksReceived.find(r => r.damage)), + .attr(PreUseInterruptAttr, i18next.t("moveTriggers:lostFocus"), user => !!user.turnData.attacksReceived.find(r => r.damage)) + .punchingMove(), new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS), diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 4a19b6d112e..dc2b381b923 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -9,7 +9,7 @@ import { PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, - ReduceStatusEffectDurationAbAttr + ReduceStatusEffectDurationAbAttr, } from "#app/data/ability"; import type { DelayedAttackTag } from "#app/data/arena-tag"; import { CommonAnim } from "#app/data/battle-anims"; @@ -24,7 +24,8 @@ import { frenzyMissFunc, HealStatusEffectAttr, MoveFlags, - PreMoveMessageAttr + PreMoveMessageAttr, + PreUseInterruptAttr, } from "#app/data/move"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; @@ -281,7 +282,18 @@ export class MovePhase extends BattlePhase { } } - this.showMoveText(); + let success: boolean = 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)) { + if (move.apply(this.pokemon, targets[0], this.move.getMove())) { + success = false; + break; + } + } + + if (success) { + this.showMoveText(); + } if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used @@ -317,11 +329,13 @@ export class MovePhase extends BattlePhase { * Move conditions assume the move has a single target * TODO: is this sustainable? */ - const passesConditions = move.applyConditions(this.pokemon, targets[0], move); - const failedDueToWeather: boolean = globalScene.arena.isMoveWeatherCancelled(this.pokemon, move); - const failedDueToTerrain: boolean = globalScene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move); + if (success) { + const passesConditions = move.applyConditions(this.pokemon, targets[0], move); + const failedDueToWeather: boolean = globalScene.arena.isMoveWeatherCancelled(this.pokemon, move); + const failedDueToTerrain: boolean = globalScene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move); + success = passesConditions && !failedDueToWeather && !failedDueToTerrain; + } - const success = passesConditions && !failedDueToWeather && !failedDueToTerrain; // Update the battle's "last move" pointer, unless we're currently mimicking a move. if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) {