[Move] Implement Shell Trap (#3500)

* Implement Shell Trap

* Fix error in EN message

* Add ZH/KO translations

Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com>
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>
Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>

* Fix error in placeholder messages

* FR translation / KO translation fix

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>

* More translations (DE, ES, IT, PT-BR)

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Asdar <asdargmng@gmail.com>
Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>

* Update src/locales/ja/move-trigger.ts

Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>

* Fix Shell Trap activating from ally attacks (+ test fixes)

* Remove todo + add test with shell trap as first move

---------

Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com>
Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com>
Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com>
Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Asdar <asdargmng@gmail.com>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
This commit is contained in:
innerthunder 2024-08-13 13:47:05 -07:00 committed by GitHub
parent afc391868b
commit 161043ecd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 258 additions and 15 deletions

View File

@ -119,7 +119,7 @@ export class RechargingTag extends BattlerTag {
}
/**
* BattlerTag representing the "charge phase" of Beak Blast
* BattlerTag representing the "charge phase" of Beak Blast.
* Pokemon with this tag will inflict BURN status on any attacker that makes contact.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Beak_Blast_(move) | Beak Blast}
*/
@ -156,6 +156,50 @@ export class BeakBlastChargingTag extends BattlerTag {
}
}
/**
* BattlerTag implementing Shell Trap's pre-move behavior.
* Pokemon with this tag will act immediately after being hit by a physical move.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Trap_(move) | Shell Trap}
*/
export class ShellTrapTag extends BattlerTag {
public activated: boolean;
constructor() {
super(BattlerTagType.SHELL_TRAP, BattlerTagLapseType.TURN_END, 1);
this.activated = false;
}
onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage(i18next.t("moveTriggers:setUpShellTrap", { pokemonName: getPokemonNameWithAffix(pokemon) }));
}
/**
* "Activates" the shell trap, causing the tag owner to move next.
* @param pokemon {@linkcode Pokemon} the owner of this tag
* @param lapseType {@linkcode BattlerTagLapseType} the type of functionality invoked in battle
* @returns `true` if invoked with the `CUSTOM` lapse type; `false` otherwise
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.CUSTOM) {
const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase && phase.pokemon === pokemon
);
const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex(
phase => phase instanceof MovePhase
);
if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) {
const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0];
pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase);
}
this.activated = true;
return true;
}
return super.lapse(pokemon, lapseType);
}
}
export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
@ -1785,6 +1829,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new RechargingTag(sourceMove);
case BattlerTagType.BEAK_BLAST_CHARGING:
return new BeakBlastChargingTag();
case BattlerTagType.SHELL_TRAP:
return new ShellTrapTag();
case BattlerTagType.FLINCHED:
return new FlinchedTag(sourceMove);
case BattlerTagType.INTERRUPTED:

View File

@ -1,7 +1,7 @@
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { BattleEndPhase, MoveEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchPhase, SwitchSummonPhase } from "../phases";
import { BattleStat, getBattleStatName } from "./battle-stat";
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, StockpilingTag, TrappedTag, TypeBoostTag } from "./battler-tags";
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, TypeBoostTag } from "./battler-tags";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect";
@ -1021,19 +1021,36 @@ export class MessageHeaderAttr extends MoveHeaderAttr {
}
}
/**
* Header attribute to add a battler tag to the user at the beginning of a turn.
* @see {@linkcode MoveHeaderAttr}
*/
export class AddBattlerTagHeaderAttr extends MoveHeaderAttr {
private tagType: BattlerTagType;
constructor(tagType: BattlerTagType) {
super();
this.tagType = tagType;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.addTag(this.tagType);
return true;
}
}
/**
* Header attribute to implement the "charge phase" of Beak Blast at the
* beginning of a turn.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Beak_Blast_(move) | Beak Blast}
* @see {@linkcode BeakBlastChargingTag}
*/
export class BeakBlastHeaderAttr extends MoveHeaderAttr {
export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr {
/** Required to initialize Beak Blast's charge animation correctly */
public chargeAnim = ChargeAnim.BEAK_BLAST_CHARGING;
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.addTag(BattlerTagType.BEAK_BLAST_CHARGING);
return true;
constructor() {
super(BattlerTagType.BEAK_BLAST_CHARGING);
}
}
@ -8145,8 +8162,10 @@ export function initMoves() {
.ignoresVirtual(),
/* End Unused */
new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7)
.attr(AddBattlerTagHeaderAttr, BattlerTagType.SHELL_TRAP)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.partial(),
// Fails if the user was not hit by a physical attack during the turn
.condition((user, target, move) => user.getTag(ShellTrapTag)?.activated === true),
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7)
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)

View File

@ -68,5 +68,6 @@ export enum BattlerTagType {
IGNORE_DARK = "IGNORE_DARK",
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING"
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING",
SHELL_TRAP = "SHELL_TRAP"
}

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}} is absorbing power!",
"burnedItselfOut": "{{pokemonName}} burned itself out!",
"startedHeatingUpBeak": "{{pokemonName}} started\nheating up its beak!",
"setUpShellTrap": "{{pokemonName}} set a shell trap!",
"isOverflowingWithSpacePower": "{{pokemonName}} is overflowing\nwith space power!",
"usedUpAllElectricity": "{{pokemonName}} used up all its electricity!",
"stoleItem": "{{pokemonName}} stole\n{{targetName}}'s {{itemName}}!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}} saugt Kraft in sich auf!",
"burnedItselfOut": "{{pokemonName}} braucht sein Feuer komplett auf!",
"startedHeatingUpBeak": "{{pokemonName}} erhitzt seinen Schnabel!",
"setUpShellTrap": "{{pokemonName}} hat eine Panzerfalle gelegt!",
"isOverflowingWithSpacePower": "Kosmische Kräfte strömen aus {{pokemonName}}!",
"usedUpAllElectricity": "{{pokemonName}} braucht seinen Strom komplett auf!",
"stoleItem": "{{pokemonName}} hat {{targetName}} das Item {{itemName}} geklaut!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}} is absorbing power!",
"burnedItselfOut": "{{pokemonName}} burned itself out!",
"startedHeatingUpBeak": "{{pokemonName}} started\nheating up its beak!",
"setUpShellTrap": "{{pokemonName}} set a shell trap!",
"isOverflowingWithSpacePower": "{{pokemonName}} is overflowing\nwith space power!",
"usedUpAllElectricity": "{{pokemonName}} used up all its electricity!",
"stoleItem": "{{pokemonName}} stole\n{{targetName}}'s {{itemName}}!",

View File

@ -30,12 +30,13 @@ export const moveTriggers: SimpleTranslationEntries = {
"tookTargetIntoSky": "{{pokemonName}} took {{targetName}}\ninto the sky!",
"becameCloakedInFreezingLight": "{{pokemonName}} became cloaked\nin a freezing light!",
"becameCloakedInFreezingAir": "{{pokemonName}} became cloaked\nin freezing air!",
"isChargingPower": "{{pokemonName}} is absorbing power!",
"burnedItselfOut": "{{pokemonName}} burned itself out!",
"startedHeatingUpBeak": "{{pokemonName}} started\nheating up its beak!",
"isOverflowingWithSpacePower": "{{pokemonName}} is overflowing\nwith space power!",
"usedUpAllElectricity": "{{pokemonName}} used up all its electricity!",
"stoleItem": "{{pokemonName}} stole\n{{targetName}}'s {{itemName}}!",
"isChargingPower": "¡{{pokemonName}} está acumulando energía!",
"burnedItselfOut": "¡El fuego interior de {{pokemonName}} se ha extinguido!",
"startedHeatingUpBeak": "¡{{pokemonName}} empieza\na calentar su pico!",
"setUpShellTrap": "¡{{pokemonName}} ha activado la Coraza Trampa!",
"isOverflowingWithSpacePower": "¡{{pokemonName}} rebosa\nenergía cósmica!",
"usedUpAllElectricity": "¡{{pokemonName}} ha descargado toda su electricidad!",
"stoleItem": "¡{{pokemonName}} robó el objeto\n{{itemName}} de {{targetName}}!",
"incineratedItem": "{{pokemonName}} incinerated\n{{targetName}}'s {{itemName}}!",
"knockedOffItem": "{{pokemonName}} knocked off\n{{targetName}}'s {{itemName}}!",
"tookMoveAttack": "{{pokemonName}} took\nthe {{moveName}} attack!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}}\nconcentre son énergie !",
"burnedItselfOut": "Le feu intérieur de {{pokemonName}}\nsest entièrement consumé !",
"startedHeatingUpBeak": "{{pokemonName}}\nfait chauffer son bec !",
"setUpShellTrap": "{{pokemonName}} déclenche\nle Carapiège!",
"isOverflowingWithSpacePower": "La puissance du cosmos afflue dans le corps\nde {{pokemonName}} !",
"usedUpAllElectricity": "{{pokemonName}}a utilisé\ntoute son électricité !",
"stoleItem": "{{pokemonName}} vole\nlobjet {{itemName}} de {{targetName}} !",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}} accumula energia!",
"burnedItselfOut": "Le fiamme di {{pokemonName}} si sono spente!",
"startedHeatingUpBeak": "{{pokemonName}} inizia a\nscaldare il becco!",
"setUpShellTrap": "{{pokemonName}} ha preparato\nla Gusciotrappola!",
"isOverflowingWithSpacePower": "La forza delluniverso pervade {{pokemonName}}!",
"usedUpAllElectricity": "{{pokemonName}} ha usato tutta la sua elettricità!",
"stoleItem": "{{pokemonName}} ruba\nil/lo/la {{itemName}} di {{targetName}}!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}}は\nパワーを ためこんでいる",
"burnedItselfOut": "{{pokemonName}}の ほのうは\nもえつきた",
"startedHeatingUpBeak": "{{pokemonName}}は\nクチバシを かねつしはじめた",
"setUpShellTrap": "{{pokemonName}}は\nトラップシェルを 仕掛けた",
"isOverflowingWithSpacePower": "{{pokemonName}}に\nうちゅうの ちからが あふれだす",
"usedUpAllElectricity": "{{pokemonName}}は\nでんきを つかいきった",
"stoleItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を ぬすんだ!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}}는(은)\n파워를 모으고 있다!",
"burnedItselfOut": "{{pokemonName}}의 불꽃은 다 타 버렸다!",
"startedHeatingUpBeak": "{{pokemonName}}는(은)\n부리를 가열하기 시작했다!",
"setUpShellTrap": "{{pokemonName}}[[는]]\n트랩셸을 설치했다!",
"isOverflowingWithSpacePower": "{{pokemonName}}에게서\n우주의 힘이 넘쳐난다!",
"usedUpAllElectricity": "{{pokemonName}}[[는]]\n전기를 다 써 버렸다!",
"stoleItem": "{{pokemonName}}[[는]] {{targetName}}[[로]]부터\n{{itemName}}[[을]] 빼앗았다!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}} está absorvendo energia!",
"burnedItselfOut": "{{pokemonName}} apagou seu próprio fogo!",
"startedHeatingUpBeak": "{{pokemonName}} começou\na esquentar seu bico!",
"setUpShellTrap": "{{pokemonName}} armou uma armadilha de carapaça!",
"isOverflowingWithSpacePower": "{{pokemonName}} está sobrecarregado\ncom energia espacial!",
"usedUpAllElectricity": "{{pokemonName}} usou toda a sua eletricidade!",
"stoleItem": "{{pokemonName}} roubou/no(a) {{itemName}} de {{targetName}}!",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}}\n正在积蓄力量",
"burnedItselfOut": "{{pokemonName}}的火焰燃尽了!",
"startedHeatingUpBeak": "{{pokemonName}}\n开始给鸟嘴加热了",
"setUpShellTrap": "{{pokemonName}}\n设置了陷阱甲壳",
"isOverflowingWithSpacePower": "{{pokemonName}}身上\n溢出了宇宙之力",
"usedUpAllElectricity": "{{pokemonName}}\n用尽电力了",
"stoleItem": "{{pokemonName}}从{{targetName}}那里\n夺取了{{itemName}}",

View File

@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
"isChargingPower": "{{pokemonName}}\n正在積蓄力量",
"burnedItselfOut": "{{pokemonName}}的火焰燃盡了!",
"startedHeatingUpBeak": "{{pokemonName}}\n開始給鳥嘴加熱了",
"setUpShellTrap": "{{pokemonName}}\n設下了陷阱甲殼",
"isOverflowingWithSpacePower": "{{pokemonName}}湧起了宇宙的力量!",
"usedUpAllElectricity": "{{pokemonName}}\n用盡了電力",
"stoleItem": "{{pokemonName}}从{{targetName}}那裏\n奪取了{{itemName}}",

View File

@ -1,7 +1,7 @@
import BattleScene, { bypassLogin } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from "./utils";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr, MoveHeaderAttr } from "./data/move";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr, MoveHeaderAttr, MoveCategory } from "./data/move";
import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
@ -3191,6 +3191,9 @@ export class MoveEffectPhase extends PokemonPhase {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
// If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tags and tokens
target.lapseTag(BattlerTagType.BEAK_BLAST_CHARGING);
if (move.category === MoveCategory.PHYSICAL && user.isPlayer() !== target.isPlayer()) {
target.lapseTag(BattlerTagType.SHELL_TRAP);
}
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
}

View File

@ -0,0 +1,163 @@
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { Moves } from "#app/enums/moves.js";
import { Species } from "#app/enums/species.js";
import { allMoves } from "#app/data/move.js";
import { BattlerIndex } from "#app/battle.js";
import { getMovePosition } from "../utils/gameManagerUtils";
import { BerryPhase, MoveEndPhase, MovePhase } from "#app/phases.js";
import { SPLASH_ONLY } from "../utils/testUtils";
import { MoveResult } from "#app/field/pokemon.js";
const TIMEOUT = 20 * 1000;
describe("Moves - Shell Trap", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("double")
.moveset([Moves.SHELL_TRAP, Moves.SPLASH, Moves.BULLDOZE])
.enemySpecies(Species.SNORLAX)
.enemyMoveset(Array(4).fill(Moves.RAZOR_LEAF))
.startingLevel(100)
.enemyLevel(100);
vi.spyOn(allMoves[Moves.RAZOR_LEAF], "accuracy", "get").mockReturnValue(100);
});
it(
"should activate after the user is hit by a physical attack",
async () => {
await game.startBattle([Species.CHARIZARD, Species.TURTONATOR]);
const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField();
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
game.doAttack(getMovePosition(game.scene, 1, Moves.SHELL_TRAP));
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2]);
await game.phaseInterceptor.to(MoveEndPhase);
const movePhase = game.scene.getCurrentPhase();
expect(movePhase instanceof MovePhase).toBeTruthy();
expect((movePhase as MovePhase).pokemon).toBe(playerPokemon[1]);
await game.phaseInterceptor.to(MoveEndPhase);
enemyPokemon.forEach(p => expect(p.hp).toBeLessThan(p.getMaxHp()));
}, TIMEOUT
);
it(
"should fail if the user is only hit by special attacks",
async () => {
game.override.enemyMoveset(Array(4).fill(Moves.SWIFT));
await game.startBattle([Species.CHARIZARD, Species.TURTONATOR]);
const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField();
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
game.doAttack(getMovePosition(game.scene, 1, Moves.SHELL_TRAP));
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2]);
await game.phaseInterceptor.to(MoveEndPhase);
const movePhase = game.scene.getCurrentPhase();
expect(movePhase instanceof MovePhase).toBeTruthy();
expect((movePhase as MovePhase).pokemon).not.toBe(playerPokemon[1]);
await game.phaseInterceptor.to(BerryPhase, false);
enemyPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
}, TIMEOUT
);
it(
"should fail if the user isn't hit with any attack",
async () => {
game.override.enemyMoveset(SPLASH_ONLY);
await game.startBattle([Species.CHARIZARD, Species.TURTONATOR]);
const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField();
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
game.doAttack(getMovePosition(game.scene, 1, Moves.SHELL_TRAP));
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2]);
await game.phaseInterceptor.to(MoveEndPhase);
const movePhase = game.scene.getCurrentPhase();
expect(movePhase instanceof MovePhase).toBeTruthy();
expect((movePhase as MovePhase).pokemon).not.toBe(playerPokemon[1]);
await game.phaseInterceptor.to(BerryPhase, false);
enemyPokemon.forEach(p => expect(p.hp).toBe(p.getMaxHp()));
}, TIMEOUT
);
it(
"should not activate from an ally's attack",
async () => {
game.override.enemyMoveset(SPLASH_ONLY);
await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerField();
const enemyPokemon = game.scene.getEnemyField();
game.doAttack(getMovePosition(game.scene, 0, Moves.SHELL_TRAP));
game.doAttack(getMovePosition(game.scene, 1, Moves.BULLDOZE));
await game.phaseInterceptor.to(MoveEndPhase);
const movePhase = game.scene.getCurrentPhase();
expect(movePhase instanceof MovePhase).toBeTruthy();
expect((movePhase as MovePhase).pokemon).not.toBe(playerPokemon[1]);
const enemyStartingHp = enemyPokemon.map(p => p.hp);
await game.phaseInterceptor.to(BerryPhase, false);
enemyPokemon.forEach((p, i) => expect(p.hp).toBe(enemyStartingHp[i]));
}, TIMEOUT
);
it(
"should not activate from a subsequent physical attack",
async () => {
game.override.battleType("single");
vi.spyOn(allMoves[Moves.RAZOR_LEAF], "priority", "get").mockReturnValue(-4);
await game.startBattle([Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.SHELL_TRAP));
await game.phaseInterceptor.to(BerryPhase, false);
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
}, TIMEOUT
);
});