From 2d52b05b315e2d32db4451ba255635672f45f1ac Mon Sep 17 00:00:00 2001 From: muscode13 Date: Sat, 2 Nov 2024 01:33:58 -0600 Subject: [PATCH] added source to damageAndUpdate and applyPostDamageAbAttrs, added Parental Bond logic + test, put applyPostDamageAbAttrs back in damageAndUpdate --- src/data/ability.ts | 22 ++++++++++----------- src/field/pokemon.ts | 17 +++++++++------- src/phases/post-turn-status-effect-phase.ts | 3 ++- src/test/abilities/wimp_out.test.ts | 20 +++++++++++++++++++ 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 696a64d289e..006e2faa01e 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -4979,7 +4979,7 @@ function calculateShellBellRecovery(pokemon: Pokemon): number { * @extends AbAttr */ export class PostDamageAbAttr extends AbAttr { - public applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + public applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[], source?: Pokemon): boolean | Promise { return false; } } @@ -5011,9 +5011,10 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { * @param passive N/A * @param simulated Whether the ability is being simulated. * @param args N/A + * @param source The Pokemon that dealt damage * @returns `true` if the switch-out logic was successfully applied */ - public override applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { + public override applyPostDamage(pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean, args: any[], source?: Pokemon): boolean | Promise { const moveHistory = pokemon.getMoveHistory(); // Will not activate when the Pokémon's HP is lowered by cutting its own HP const fordbiddenAttackingMoves = [ Moves.BELLY_DRUM, Moves.SUBSTITUTE, Moves.CURSE, Moves.PAIN_SPLIT ]; @@ -5027,22 +5028,21 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { // Dragon Tail and Circle Throw switch out Pokémon before the Ability activates. const fordbiddenDefendingMoves = [ Moves.DRAGON_TAIL, Moves.CIRCLE_THROW ]; - const getField = [ ...pokemon.getOpponents(), ...pokemon.getAlliedField() ]; - for (const opponent of getField) { - const enemyMoveHistory = opponent.getMoveHistory(); + if (source) { + const enemyMoveHistory = source.getMoveHistory(); if (enemyMoveHistory.length > 0) { const enemyLastMoveUsed = enemyMoveHistory[enemyMoveHistory.length - 1]; if (fordbiddenDefendingMoves.includes(enemyLastMoveUsed.move) || enemyLastMoveUsed.move === Moves.SKY_DROP && enemyLastMoveUsed.result === MoveResult.OTHER) { return false; // Will not activate if the Pokémon's HP falls below half by a move affected by Sheer Force. - } else if (allMoves[enemyLastMoveUsed.move].chance >= 0 && opponent.hasAbility(Abilities.SHEER_FORCE)) { + } else if (allMoves[enemyLastMoveUsed.move].chance >= 0 && source.hasAbility(Abilities.SHEER_FORCE)) { return false; // Activate only after the last hit of multistrike moves - } else if (opponent.turnData.hitsLeft > 1) { + } else if (source.turnData.hitsLeft > 1) { return false; } - const multiHitModifier = opponent.getHeldItems().find(m => m instanceof PokemonMultiHitModifier); - if (allMoves[enemyLastMoveUsed.move].hasAttr(MultiHitAttr) || multiHitModifier) { + const multiHitModifier = source.getHeldItems().find(m => m instanceof PokemonMultiHitModifier); + if (allMoves[enemyLastMoveUsed.move].hasAttr(MultiHitAttr) || multiHitModifier || source.hasAbilityWithAttr(AddSecondStrikeAbAttr)) { damage = pokemon.turnData.damageTaken; } } @@ -5105,8 +5105,8 @@ export function applyPostSetStatusAbAttrs(attrType: Constructor, - pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean = false, args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDamage(pokemon, damage, passive, simulated, args), args); + pokemon: Pokemon, damage: number, passive: boolean, simulated: boolean = false, args: any[], source?: Pokemon): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDamage(pokemon, damage, passive, simulated, args, source), args); } /** diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 1edb1374119..4c17eafb417 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2791,7 +2791,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * We explicitly require to ignore the faint phase here, as we want to show the messages * about the critical hit and the super effective/not very effective messages before the faint phase. */ - const damage = this.damageAndUpdate(isBlockedBySubstitute ? 0 : dmg, result as DamageResult, isCritical, isOneHitKo, isOneHitKo, true); + const damage = this.damageAndUpdate(isBlockedBySubstitute ? 0 : dmg, result as DamageResult, isCritical, isOneHitKo, isOneHitKo, true, source); if (damage > 0) { if (source.isPlayer()) { @@ -2805,11 +2805,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.turnData.damageTaken += damage; this.battleData.hitCount++; - // Multi-Lens check for Wimp Out/Emergency Exit + // Multi-Lens and Parental Bond check for Wimp Out/Emergency Exit if (this.hasAbilityWithAttr(PostDamageForceSwitchAbAttr)) { const multiHitModifier = source.getHeldItems().find(m => m instanceof PokemonMultiHitModifier); - if (multiHitModifier) { - applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, []); + if (multiHitModifier || source.hasAbilityWithAttr(AddSecondStrikeAbAttr)) { + applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); } } @@ -2900,8 +2900,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.destroySubstitute(); this.resetSummonData(); } - applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, []); - return damage; } @@ -2915,12 +2913,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage() * @returns integer of damage done */ - damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false): integer { + damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): integer { const damagePhase = new DamagePhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical); this.scene.unshiftPhase(damagePhase); damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); // Damage amount may have changed, but needed to be queued before calling damage function damagePhase.updateAmount(damage); + if (source) { + applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); + } else { + applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, []); + } return damage; } diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 2efd992a2b5..08e4d7cb952 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -1,6 +1,6 @@ import BattleScene from "#app/battle-scene"; import { BattlerIndex } from "#app/battle"; -import { applyAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability"; +import { applyAbAttrs, applyPostDamageAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, PostDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability"; import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; import { getStatusEffectActivationText } from "#app/data/status-effect"; import { BattleSpec } from "#app/enums/battle-spec"; @@ -41,6 +41,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); pokemon.updateInfo(); + applyPostDamageAbAttrs(PostDamageAbAttr, pokemon, damage.value, pokemon.hasPassive(), false, []); } new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, false, () => this.end()); } else { diff --git a/src/test/abilities/wimp_out.test.ts b/src/test/abilities/wimp_out.test.ts index 9b44de6c070..46922a930c4 100644 --- a/src/test/abilities/wimp_out.test.ts +++ b/src/test/abilities/wimp_out.test.ts @@ -568,6 +568,26 @@ describe("Abilities - Wimp Out", () => { expect(enemyPokemon.turnData.hitCount).toBe(2); confirmSwitch(); }); + it("triggers after last hit of Parental Bond", async () => { + game.override + .enemyMoveset(Moves.TACKLE) + .enemyAbility(Abilities.PARENTAL_BOND); + await game.classicMode.startBattle([ + Species.WIMPOD, + Species.TYRUNT + ]); + + game.scene.getPlayerPokemon()!.hp *= 0.51; + + game.move.select(Moves.ENDURE); + game.doSelectPartyPokemon(1); + await game.phaseInterceptor.to("TurnEndPhase"); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + expect(enemyPokemon.turnData.hitsLeft).toBe(0); + expect(enemyPokemon.turnData.hitCount).toBe(2); + confirmSwitch(); + }); // TODO: This interaction is not implemented yet it.todo("Wimp Out will not activate if the Pokémon's HP falls below half due to hurting itself in confusion", async () => {