diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 98166915560..06f06a176f8 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -1747,8 +1747,9 @@ export class RecoilAttr extends MoveEffectAttr { return false; } - const damageValue = (!this.useHp ? user.turnData.singleHitDamageDealt : user.getMaxHp()) * this.damageRatio; - const minValue = user.turnData.singleHitDamageDealt ? 1 : 0; + const prevDmgDealt = user.turnData.lastMoveDamageDealt.reduce((acc, dmg) => acc+dmg, 0) + const damageValue = (!this.useHp ? prevDmgDealt : user.getMaxHp()) * this.damageRatio; + const minValue = prevDmgDealt ? 1 : 0; const recoilDamage = toDmgValue(damageValue, minValue); if (!recoilDamage) { return false; @@ -2083,13 +2084,6 @@ export class FlameBurstAttr extends MoveEffectAttr { } } -/** - * Attribute to KO the user while fully restoring HP/status of the next switched in Pokemon. - * - * Used for {@linkcode Moves.HEALING_WISH} and {@linkcode Moves.LUNAR_DANCE}. - * TODO: Implement "heal storing" if switched in pokemon is at full HP (likely with an end-of-turn ArenaTag). - * Will likely be blocked by the need for a "slot dependent ArenaTag" similar to Future Sight - */ export class SacrificialFullRestoreAttr extends SacrificialAttr { protected restorePP: boolean; protected moveMessage: string; @@ -5931,8 +5925,8 @@ export class ProtectAttr extends AddBattlerTagAttr { for (const turnMove of user.getLastXMoves(-1).slice()) { if ( // Quick & Wide guard increment the Protect counter without using it for fail chance - !(allMoves[turnMove.move].hasAttr("ProtectAttr") || - [MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) || + !(allMoves[turnMove.move].hasAttr("ProtectAttr") || + [MoveId.QUICK_GUARD, MoveId.WIDE_GUARD].includes(turnMove.move)) || turnMove.result !== MoveResult.SUCCESS ) { break; @@ -6402,11 +6396,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - // Damaging switch moves and ones w/o a secondary effect do not "fail" + // Damaging switch moves do not "fail" // upon an unsuccessful switch - they still succeed and perform secondary effects // (just without actually switching out). - // TODO: Remove attr check once move attribute application is cleaned up - return (user, target, move) => (move.category !== MoveCategory.STATUS || move.attrs.length > 1 || this.canApply(user, target, move, [])); + // TODO: Remove condition check once move attribute application is cleaned up + return (user, target, move) => (move.category !== MoveCategory.STATUS || this.canApply(user, target, move, [])); } getFailedText(_user: Pokemon, target: Pokemon): string | undefined { @@ -8633,7 +8627,7 @@ export function initMoves() { new AttackMove(MoveId.RAGE, PokemonType.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1) .partial(), // No effect implemented new SelfStatusMove(MoveId.TELEPORT, PokemonType.PSYCHIC, -1, 20, -1, -6, 1) - .attr(ForceSwitchOutAttr, {selfSwitch: true, switchType: SwitchType.SWITCH, allowFlee: true}) + .attr(ForceSwitchOutAttr, {selfSwitch: true, allowFlee: true}) .hidesUser(), new AttackMove(MoveId.NIGHT_SHADE, PokemonType.GHOST, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1) .attr(LevelDamageAttr), @@ -9216,7 +9210,7 @@ export function initMoves() { .condition(failIfLastCondition) // Interactions with stomping tantrum, instruct, and other moves that // rely on move history - // Also will not reflect roar / whirlwind if the target has ForceSwitchOutImmunityAbAttr + // Will not reflect moves failing conditions .edgeCase(), new SelfStatusMove(MoveId.RECYCLE, PokemonType.NORMAL, -1, 10, -1, 0, 3) .unimplemented(), @@ -10172,7 +10166,9 @@ export function initMoves() { .attr(ForceSwitchOutAttr, {selfSwitch: true}) .soundBased() .reflectable() - .edgeCase(), // should not fail if no target is switched out + // should not fail if no target is switched out + // Should not switch out if target cannot be stat dropped + .edgeCase(), new StatusMove(MoveId.TOPSY_TURVY, PokemonType.DARK, -1, 20, -1, 0, 6) .attr(InvertStatsAttr) .reflectable(), diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index d63a32c29b8..33cbc32f4bf 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -772,8 +772,8 @@ export class MoveEffectPhase extends PokemonPhase { } if (this.lastHit) { + // Trigger form changes on the final hit, alongside Wimp Out/Emergency Exit globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); - // Trigger Form changes on the final hit, alongside Wimp Out. applyAbAttrs("PostDamageAbAttr", { pokemon: target, damage: user.turnData.lastMoveDamageDealt[target.getBattlerIndex()], @@ -830,6 +830,7 @@ export class MoveEffectPhase extends PokemonPhase { const substitute = target.getTag(SubstituteTag); const isBlockedBySubstitute = !!substitute && this.move.hitsSubstitute(user, target); if (isBlockedBySubstitute) { + // NB: This cannot possibly cause issues with wimp out as the latter breaks if nothing works user.turnData.lastMoveDamageDealt[target.getBattlerIndex()] += Math.min(dmg, substitute.hp); substitute.hp -= dmg; } else if (!target.isPlayer() && dmg >= target.hp) { diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index 9739c58d667..7c219ea7b8c 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -12,6 +12,7 @@ export abstract class PokemonPhase extends FieldPhase { public player: boolean; public fieldIndex: number; + // TODO: When will this ever be undefined? constructor(battlerIndex?: BattlerIndex | number) { super();