[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:
parent
afc391868b
commit
161043ecd6
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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}}!",
|
||||
|
|
|
@ -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!",
|
||||
|
|
|
@ -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}}!",
|
||||
|
|
|
@ -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!",
|
||||
|
|
|
@ -33,6 +33,7 @@ export const moveTriggers: SimpleTranslationEntries = {
|
|||
"isChargingPower": "{{pokemonName}}\nconcentre son énergie !",
|
||||
"burnedItselfOut": "Le feu intérieur de {{pokemonName}}\ns’est 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\nl’objet {{itemName}} de {{targetName}} !",
|
||||
|
|
|
@ -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 dell’universo pervade {{pokemonName}}!",
|
||||
"usedUpAllElectricity": "{{pokemonName}} ha usato tutta la sua elettricità!",
|
||||
"stoleItem": "{{pokemonName}} ruba\nil/lo/la {{itemName}} di {{targetName}}!",
|
||||
|
|
|
@ -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}}を ぬすんだ!",
|
||||
|
|
|
@ -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}}[[을]] 빼앗았다!",
|
||||
|
|
|
@ -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}}!",
|
||||
|
|
|
@ -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}}!",
|
||||
|
|
|
@ -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}}!",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
Loading…
Reference in New Issue