[Bug] Burning Bulwark, King's Shield, Obstruct and Silk Trap no longer protect against status moves. (#4106)
* Obstruct et al no longer protect against status moves * Remove `.js` from import Co-authored-by: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> * Add tsdocs for new/modified classes * List relevant moves in tsdocs --------- Co-authored-by: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com>
This commit is contained in:
parent
951b568590
commit
91d266ea74
|
@ -1298,6 +1298,13 @@ export class ProtectedTag extends BattlerTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Base class for `BattlerTag`s that block damaging moves but not status moves */
|
||||||
|
export class DamageProtectedTag extends ProtectedTag {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact
|
||||||
|
* Used by {@linkcode Moves.SPIKY_SHIELD}
|
||||||
|
*/
|
||||||
export class ContactDamageProtectedTag extends ProtectedTag {
|
export class ContactDamageProtectedTag extends ProtectedTag {
|
||||||
private damageRatio: number;
|
private damageRatio: number;
|
||||||
|
|
||||||
|
@ -1333,7 +1340,11 @@ export class ContactDamageProtectedTag extends ProtectedTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContactStatStageChangeProtectedTag extends ProtectedTag {
|
/**
|
||||||
|
* `BattlerTag` class for moves that block damaging moves and lower enemy stats if the enemy's move makes contact
|
||||||
|
* Used by {@linkcode Moves.KINGS_SHIELD}, {@linkcode Moves.OBSTRUCT}, {@linkcode Moves.SILK_TRAP}
|
||||||
|
*/
|
||||||
|
export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
|
||||||
private stat: BattleStat;
|
private stat: BattleStat;
|
||||||
private levels: number;
|
private levels: number;
|
||||||
|
|
||||||
|
@ -1389,7 +1400,11 @@ export class ContactPoisonProtectedTag extends ProtectedTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContactBurnProtectedTag extends ProtectedTag {
|
/**
|
||||||
|
* `BattlerTag` class for moves that block damaging moves and burn the enemy if the enemy's move makes contact
|
||||||
|
* Used by {@linkcode Moves.BURNING_BULWARK}
|
||||||
|
*/
|
||||||
|
export class ContactBurnProtectedTag extends DamageProtectedTag {
|
||||||
constructor(sourceMove: Moves) {
|
constructor(sourceMove: Moves) {
|
||||||
super(sourceMove, BattlerTagType.BURNING_BULWARK);
|
super(sourceMove, BattlerTagType.BURNING_BULWARK);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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 } from "#app/data/ability";
|
||||||
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
||||||
import { MoveAnim } from "#app/data/battle-anims";
|
import { MoveAnim } from "#app/data/battle-anims";
|
||||||
import { BattlerTagLapseType, ProtectedTag, SemiInvulnerableTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag } from "#app/data/battler-tags";
|
||||||
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move";
|
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr } from "#app/data/move";
|
||||||
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
|
@ -152,7 +152,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||||
|
|
||||||
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
|
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
|
||||||
const isProtected = (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target))
|
const isProtected = (bypassIgnoreProtect.value || !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target))
|
||||||
&& (hasConditionalProtectApplied.value || target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)));
|
&& (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))));
|
||||||
|
|
||||||
/** Does this phase represent the invoked move's first strike? */
|
/** Does this phase represent the invoked move's first strike? */
|
||||||
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
|
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Moves } from "#app/enums/moves";
|
||||||
|
import { Stat } from "#app/enums/stat";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Obstruct", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.moveset([Moves.OBSTRUCT]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.OBSTRUCT);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
expect(player.isFullHp()).toBe(true);
|
||||||
|
expect(enemy.getStatStage(Stat.DEF)).toBe(-2);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN));
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.OBSTRUCT);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
expect(player.isFullHp()).toBe(true);
|
||||||
|
expect(enemy.getStatStage(Stat.DEF)).toBe(0);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("doesn't protect from status moves", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.OBSTRUCT);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
expect(player.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
Loading…
Reference in New Issue