[Bug] Flash Fire/etc now trigger even if the attack would miss (#4337)
* adding immunity check * making tests * modifying and adding tests * making tests more rigorous * changing hitcheck return to be what it was originally, no significant effect --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
4557a73ecc
commit
3d4eadbc3e
|
@ -366,6 +366,10 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
|
|||
return false;
|
||||
}
|
||||
|
||||
getImmuneType(): Type | null {
|
||||
return this.immuneType;
|
||||
}
|
||||
|
||||
override getCondition(): AbAttrCondition | null {
|
||||
return this.condition;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import BattleScene from "#app/battle-scene";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, applyPostDefendAbAttrs, PostDefendAbAttr, applyPostAttackAbAttrs, PostAttackAbAttr, MaxMultiHitAbAttr, AlwaysHitAbAttr } from "#app/data/ability";
|
||||
import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, applyPostDefendAbAttrs, PostDefendAbAttr, applyPostAttackAbAttrs, PostAttackAbAttr, MaxMultiHitAbAttr, AlwaysHitAbAttr, TypeImmunityAbAttr } from "#app/data/ability";
|
||||
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
||||
import { MoveAnim } from "#app/data/battle-anims";
|
||||
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags";
|
||||
|
@ -97,12 +97,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
*/
|
||||
const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)]));
|
||||
const hasActiveTargets = targets.some(t => t.isActive(true));
|
||||
|
||||
/** Check if the target is immune via ability to the attacking move */
|
||||
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
|
||||
|
||||
/**
|
||||
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target
|
||||
* (and not random target) and failed the hit check against its target (MISS), log the move
|
||||
* as FAILed or MISSed (depending on the conditions above) and end this phase.
|
||||
*/
|
||||
if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag))) {
|
||||
|
||||
if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) {
|
||||
this.stopMultiHit();
|
||||
if (hasActiveTargets) {
|
||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getTarget()? getPokemonNameWithAffix(this.getTarget()!) : "" }));
|
||||
|
@ -132,7 +137,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
const hasConditionalProtectApplied = new Utils.BooleanHolder(false);
|
||||
/** Does the applied conditional protection bypass Protect-ignoring effects? */
|
||||
const bypassIgnoreProtect = new Utils.BooleanHolder(false);
|
||||
// If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects
|
||||
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
|
||||
if (!this.move.getMove().isAllyTarget()) {
|
||||
this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect);
|
||||
}
|
||||
|
@ -142,11 +147,14 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
&& (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)))
|
||||
|| (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))));
|
||||
|
||||
/** Is the pokemon immune due to an ablility? */
|
||||
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
|
||||
|
||||
/**
|
||||
* If the move missed a target, stop all future hits against that target
|
||||
* and move on to the next target (if there is one).
|
||||
*/
|
||||
if (!isProtected && !targetHitChecks[target.getBattlerIndex()]) {
|
||||
if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) {
|
||||
this.stopMultiHit(target);
|
||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
||||
if (moveHistoryEntry.result === MoveResult.PENDING) {
|
||||
|
|
|
@ -141,4 +141,18 @@ describe("Abilities - Dry Skin", () => {
|
|||
|
||||
expect(healthGainedFromWaterShuriken).toBe(healthGainedFromWaterGun);
|
||||
});
|
||||
|
||||
it("opposing water moves still heal regardless of accuracy check", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.WATER_GUN);
|
||||
enemy.hp = enemy.hp - 1;
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
await game.move.forceMiss();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,7 +38,7 @@ describe("Abilities - Flash Fire", () => {
|
|||
|
||||
it("immune to Fire-type moves", async () => {
|
||||
game.override.enemyMoveset([Moves.EMBER]).moveset(Moves.SPLASH);
|
||||
await game.startBattle([Species.BLISSEY]);
|
||||
await game.classicMode.startBattle([Species.BLISSEY]);
|
||||
|
||||
const blissey = game.scene.getPlayerPokemon()!;
|
||||
|
||||
|
@ -49,7 +49,7 @@ describe("Abilities - Flash Fire", () => {
|
|||
|
||||
it("not activate if the Pokémon is protected from the Fire-type move", async () => {
|
||||
game.override.enemyMoveset([Moves.EMBER]).moveset([Moves.PROTECT]);
|
||||
await game.startBattle([Species.BLISSEY]);
|
||||
await game.classicMode.startBattle([Species.BLISSEY]);
|
||||
|
||||
const blissey = game.scene.getPlayerPokemon()!;
|
||||
|
||||
|
@ -60,7 +60,7 @@ describe("Abilities - Flash Fire", () => {
|
|||
|
||||
it("activated by Will-O-Wisp", async () => {
|
||||
game.override.enemyMoveset([Moves.WILL_O_WISP]).moveset(Moves.SPLASH);
|
||||
await game.startBattle([Species.BLISSEY]);
|
||||
await game.classicMode.startBattle([Species.BLISSEY]);
|
||||
|
||||
const blissey = game.scene.getPlayerPokemon()!;
|
||||
|
||||
|
@ -76,7 +76,7 @@ describe("Abilities - Flash Fire", () => {
|
|||
it("activated after being frozen", async () => {
|
||||
game.override.enemyMoveset([Moves.EMBER]).moveset(Moves.SPLASH);
|
||||
game.override.statusEffect(StatusEffect.FREEZE);
|
||||
await game.startBattle([Species.BLISSEY]);
|
||||
await game.classicMode.startBattle([Species.BLISSEY]);
|
||||
|
||||
const blissey = game.scene.getPlayerPokemon()!;
|
||||
|
||||
|
@ -88,7 +88,7 @@ describe("Abilities - Flash Fire", () => {
|
|||
|
||||
it("not passing with baton pass", async () => {
|
||||
game.override.enemyMoveset([Moves.EMBER]).moveset([Moves.BATON_PASS]);
|
||||
await game.startBattle([Species.BLISSEY, Species.CHANSEY]);
|
||||
await game.classicMode.startBattle([Species.BLISSEY, Species.CHANSEY]);
|
||||
|
||||
// ensure use baton pass after enemy moved
|
||||
game.move.select(Moves.BATON_PASS);
|
||||
|
@ -105,7 +105,7 @@ describe("Abilities - Flash Fire", () => {
|
|||
it("boosts Fire-type move when the ability is activated", async () => {
|
||||
game.override.enemyMoveset([Moves.FIRE_PLEDGE]).moveset([Moves.EMBER, Moves.SPLASH]);
|
||||
game.override.enemyAbility(Abilities.FLASH_FIRE).ability(Abilities.NONE);
|
||||
await game.startBattle([Species.BLISSEY]);
|
||||
await game.classicMode.startBattle([Species.BLISSEY]);
|
||||
const blissey = game.scene.getPlayerPokemon()!;
|
||||
const initialHP = 1000;
|
||||
blissey.hp = initialHP;
|
||||
|
@ -126,4 +126,33 @@ describe("Abilities - Flash Fire", () => {
|
|||
|
||||
expect(flashFireDmg).toBeGreaterThan(originalDmg);
|
||||
}, 20000);
|
||||
|
||||
it("still activates regardless of accuracy check", async () => {
|
||||
game.override.moveset(Moves.FIRE_PLEDGE).enemyMoveset(Moves.EMBER);
|
||||
game.override.enemyAbility(Abilities.NONE).ability(Abilities.FLASH_FIRE);
|
||||
game.override.enemySpecies(Species.BLISSEY);
|
||||
await game.classicMode.startBattle([Species.RATTATA]);
|
||||
|
||||
const blissey = game.scene.getEnemyPokemon()!;
|
||||
const initialHP = 1000;
|
||||
blissey.hp = initialHP;
|
||||
|
||||
// first turn
|
||||
game.move.select(Moves.FIRE_PLEDGE);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
await game.move.forceMiss();
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
const originalDmg = initialHP - blissey.hp;
|
||||
|
||||
expect(blissey.hp > 0);
|
||||
blissey.hp = initialHP;
|
||||
|
||||
// second turn
|
||||
game.move.select(Moves.FIRE_PLEDGE);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
const flashFireDmg = initialHP - blissey.hp;
|
||||
|
||||
expect(flashFireDmg).toBeGreaterThan(originalDmg);
|
||||
}, 20000);
|
||||
});
|
||||
|
|
|
@ -165,4 +165,22 @@ describe("Abilities - Sap Sipper", () => {
|
|||
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||
});
|
||||
|
||||
it("still activates regardless of accuracy check", async () => {
|
||||
game.override.moveset(Moves.LEAF_BLADE);
|
||||
game.override.enemyMoveset(Moves.SPLASH);
|
||||
game.override.enemySpecies(Species.MAGIKARP);
|
||||
game.override.enemyAbility(Abilities.SAP_SIPPER);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.LEAF_BLADE);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
await game.move.forceMiss();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Species } from "#enums/species";
|
|||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
|
||||
// See also: TypeImmunityAbAttr
|
||||
describe("Abilities - Volt Absorb", () => {
|
||||
|
@ -39,7 +40,7 @@ describe("Abilities - Volt Absorb", () => {
|
|||
game.override.enemySpecies(Species.DUSKULL);
|
||||
game.override.enemyAbility(Abilities.BALL_FETCH);
|
||||
|
||||
await game.startBattle();
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
|
@ -51,4 +52,23 @@ describe("Abilities - Volt Absorb", () => {
|
|||
expect(playerPokemon.getTag(BattlerTagType.CHARGED)).toBeDefined();
|
||||
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||
});
|
||||
it("should activate regardless of accuracy checks", async () => {
|
||||
game.override.moveset(Moves.THUNDERBOLT);
|
||||
game.override.enemyMoveset(Moves.SPLASH);
|
||||
game.override.enemySpecies(Species.MAGIKARP);
|
||||
game.override.enemyAbility(Abilities.VOLT_ABSORB);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.THUNDERBOLT);
|
||||
enemyPokemon.hp = enemyPokemon.hp - 1;
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
await game.move.forceMiss();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue