From 70516abb1662ec8228f0f3f27cb211f45288841d Mon Sep 17 00:00:00 2001 From: innerthunder Date: Thu, 15 Aug 2024 16:57:45 -0700 Subject: [PATCH] Added checks for substitute in contact logic --- src/data/move.ts | 8 +++-- src/test/abilities/unseen_fist.test.ts | 45 +++++++++++++++++++------- src/test/moves/substitute.test.ts | 18 +++++++++++ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index bafb310ebca..d048252125e 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -599,7 +599,8 @@ export default class Move implements Localizable { // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { case MoveFlags.MAKES_CONTACT: - if (user.hasAbilityWithAttr(IgnoreContactAbAttr)) { + if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || + (target?.getTag(BattlerTagType.SUBSTITUTE) && !this.canIgnoreSubstitute(user))) { return false; } break; @@ -612,8 +613,9 @@ export default class Move implements Localizable { } } case MoveFlags.IGNORE_PROTECT: - if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) && - this.checkFlag(MoveFlags.MAKES_CONTACT, user, target)) { + if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) + && this.hasFlag(MoveFlags.MAKES_CONTACT) + && !user.hasAbilityWithAttr(IgnoreContactAbAttr)) { return true; } } diff --git a/src/test/abilities/unseen_fist.test.ts b/src/test/abilities/unseen_fist.test.ts index a6cad8b03ce..86f841e8709 100644 --- a/src/test/abilities/unseen_fist.test.ts +++ b/src/test/abilities/unseen_fist.test.ts @@ -1,11 +1,12 @@ -import { TurnEndPhase } from "#app/phases.js"; +import { BerryPhase, TurnEndPhase } from "#app/phases.js"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; const TIMEOUT = 20 * 1000; @@ -33,37 +34,57 @@ describe("Abilities - Unseen Fist", () => { game.override.enemyLevel(100); }); - test( - "ability causes a contact move to ignore Protect", + it( + "should cause a contact move to ignore Protect", () => testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, true), TIMEOUT ); - test( - "ability does not cause a non-contact move to ignore Protect", + it( + "should not cause a non-contact move to ignore Protect", () => testUnseenFistHitResult(game, Moves.ABSORB, Moves.PROTECT, false), TIMEOUT ); - test( - "ability does not apply if the source has Long Reach", + it( + "should not apply if the source has Long Reach", () => { game.override.passiveAbility(Abilities.LONG_REACH); testUnseenFistHitResult(game, Moves.QUICK_ATTACK, Moves.PROTECT, false); }, TIMEOUT ); - test( - "ability causes a contact move to ignore Wide Guard", + it( + "should cause a contact move to ignore Wide Guard", () => testUnseenFistHitResult(game, Moves.BREAKING_SWIPE, Moves.WIDE_GUARD, true), TIMEOUT ); - test( - "ability does not cause a non-contact move to ignore Wide Guard", + it( + "should not cause a non-contact move to ignore Wide Guard", () => testUnseenFistHitResult(game, Moves.BULLDOZE, Moves.WIDE_GUARD, false), TIMEOUT ); + + it( + "should cause a contact move to ignore Protect, but not Substitute", + async () => { + game.override.enemyLevel(1); + game.override.moveset([Moves.TACKLE]); + + await game.startBattle(); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + enemyPokemon.addTag(BattlerTagType.SUBSTITUTE, 0, Moves.NONE, enemyPokemon.id); + + game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); + + await game.phaseInterceptor.to(BerryPhase, false); + + expect(enemyPokemon.getTag(BattlerTagType.SUBSTITUTE)).toBeUndefined(); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }, TIMEOUT + ); }); async function testUnseenFistHitResult(game: GameManager, attackMove: Moves, protectMove: Moves, shouldSucceed: boolean = true): Promise { diff --git a/src/test/moves/substitute.test.ts b/src/test/moves/substitute.test.ts index 8056834b6c6..705bb950ef7 100644 --- a/src/test/moves/substitute.test.ts +++ b/src/test/moves/substitute.test.ts @@ -406,4 +406,22 @@ describe("Moves - Substitute", () => { expect(subTag.hp).toBe(Math.floor(leadPokemon.getMaxHp() * 1/4)); }, TIMEOUT ); + + it ( + "should prevent the source's Rough Skin from activating when hit", + async () => { + game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)); + game.override.ability(Abilities.ROUGH_SKIN); + + await game.startBattle([Species.BLASTOISE]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.SUBSTITUTE)); + + await game.phaseInterceptor.to(BerryPhase, false); + + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }, TIMEOUT + ); });