mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-27 10:42:25 +02:00
Finish magic bounce impl
This commit is contained in:
parent
79fefa9f67
commit
acd27eb1da
@ -5818,10 +5818,9 @@ export function initAbilities() {
|
||||
new Ability(Abilities.MAGIC_BOUNCE, 5)
|
||||
.attr(ReflectStatusMoveAbAttr)
|
||||
.ignorable()
|
||||
// Interactions with stomping tantrum, instruct, and other moves that
|
||||
// Interactions with stomping tantrum, instruct, encore, and probably other moves that
|
||||
// rely on move history
|
||||
.edgeCase()
|
||||
.unimplemented(),
|
||||
.edgeCase(),
|
||||
new Ability(Abilities.SAP_SIPPER, 5)
|
||||
.attr(TypeImmunityStatStageChangeAbAttr, Type.GRASS, Stat.ATK, 1)
|
||||
.ignorable(),
|
||||
|
@ -62,15 +62,16 @@ import {
|
||||
} from "#app/modifier/modifier";
|
||||
import { PokemonPhase } from "#app/phases/pokemon-phase";
|
||||
import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils";
|
||||
import { type nil } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import type { Moves } from "#enums/moves";
|
||||
import i18next from "i18next";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||
import { MessagePhase } from "./message-phase";
|
||||
import type { Phase } from "#app/phase";
|
||||
import { ShowAbilityPhase } from "./show-ability-phase";
|
||||
import { MovePhase } from "./move-phase";
|
||||
import { MoveEndPhase } from "./move-end-phase";
|
||||
|
||||
export class MoveEffectPhase extends PokemonPhase {
|
||||
public move: PokemonMove;
|
||||
@ -190,7 +191,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
&& (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
|
||||
&& !targets[0]?.getTag(SemiInvulnerableTag);
|
||||
|
||||
const mayBounce = move.hasFlag(MoveFlags.REFLECTABLE) && !this.reflected;
|
||||
const mayBounce = move.hasFlag(MoveFlags.REFLECTABLE) && !this.reflected && targets.some(t => t.hasAbilityWithAttr(ReflectStatusMoveAbAttr) || !!t.getTag(BattlerTagType.MAGIC_COAT));
|
||||
|
||||
/**
|
||||
* If no targets are left for the move to hit (FAIL), or the invoked move is non-reflectable, single-target
|
||||
@ -223,22 +224,21 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
// Prevent ENEMY_SIDE targeted moves from occurring twice in double battles
|
||||
// and determine which enemy will magic bounce based on speed order, respecting trick room
|
||||
const trueTargets: Pokemon[] = move.moveTarget !== MoveTarget.ENEMY_SIDE ? targets : (() => {
|
||||
const magicCoatTargets = targets.filter(t => t.getTag(BattlerTagType.MAGIC_COAT) !== null);
|
||||
const magicCoatTargets = targets.filter(t => t.getTag(BattlerTagType.MAGIC_COAT) || t.hasAbilityWithAttr(ReflectStatusMoveAbAttr));
|
||||
|
||||
// only magic coat effect cares about order
|
||||
if (!mayBounce || magicCoatTargets.length === 0) {
|
||||
return [ targets[0] ];
|
||||
} else if (magicCoatTargets.length === 1) {
|
||||
return magicCoatTargets;
|
||||
}
|
||||
|
||||
const reversed = globalScene.arena.hasTag(ArenaTagType.TRICK_ROOM);
|
||||
const sortedTargets = targets
|
||||
.sort((a: Pokemon, b: Pokemon) => {
|
||||
const aSpeed = a?.getEffectiveStat(Stat.SPD) ?? 0;
|
||||
const bSpeed = b?.getEffectiveStat(Stat.SPD) ?? 0;
|
||||
return reversed ? aSpeed - bSpeed : bSpeed - aSpeed;
|
||||
})
|
||||
.filter(t => t.getTag(BattlerTagType.MAGIC_COAT) !== null);
|
||||
return sortedTargets.length === 1 ? sortedTargets : [ sortedTargets[globalScene.randBattleSeedInt(sortedTargets.length)] ];
|
||||
// Filter the list of magic coat targets to those with the highest speed, or lowest if trick room is active.
|
||||
const speeds = magicCoatTargets.map(p => p.getEffectiveStat(Stat.SPD) ?? 0);
|
||||
const targetSpeed = globalScene.arena.hasTag(ArenaTagType.TRICK_ROOM) ? Math.min(...speeds) : Math.max(...speeds);
|
||||
const filteredTargets = magicCoatTargets.filter((_, idx) => speeds[idx] === targetSpeed);
|
||||
// In the event of a speed tie, choose a pokemon at random that will bounce the move.
|
||||
return filteredTargets.length === 1 ? filteredTargets : [ filteredTargets[globalScene.randBattleSeedInt(filteredTargets.length)] ];
|
||||
})();
|
||||
|
||||
const queuedPhases: Phase[] = [];
|
||||
@ -255,7 +255,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
|
||||
const isProtected = (
|
||||
const isProtected = !([ MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.move.getMove().moveTarget)) && (
|
||||
bypassIgnoreProtect.value
|
||||
|| !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target))
|
||||
&& (hasConditionalProtectApplied.value
|
||||
@ -268,21 +268,30 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
const isCommanding = globalScene.currentBattle.double && target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target;
|
||||
|
||||
/** Is the target reflecting status moves from the magic coat move? */
|
||||
const isReflecting = target.getTag(BattlerTagType.MAGIC_COAT) !== null;
|
||||
const isReflecting = !!target.getTag(BattlerTagType.MAGIC_COAT);
|
||||
|
||||
/** Is the target's magic bounce ability not ignored and able to reflect this move? */
|
||||
const canMagicBounce = !(isReflecting || move.checkFlag(MoveFlags.IGNORE_ABILITIES, user, target) && target.hasAbilityWithAttr(ReflectStatusMoveAbAttr));
|
||||
const canMagicBounce = !isReflecting && !move.checkFlag(MoveFlags.IGNORE_ABILITIES, user, target) && target.hasAbilityWithAttr(ReflectStatusMoveAbAttr);
|
||||
|
||||
/** Is the target protected and reflecting the effect */
|
||||
const willBounce = !isProtected && !this.reflected && !isCommanding && (isReflecting || canMagicBounce);
|
||||
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||
/** Is the target in a semi-invulnerable state that isn't being bypassed by this move? */
|
||||
const activeSemiInvulnerability = !!semiInvulnerableTag &&
|
||||
!(this.checkBypassSemiInvuln(semiInvulnerableTag)
|
||||
|| this.checkBypassAccAndInvuln(target));
|
||||
|
||||
/** If the move will bounce, then queue the bounce and move on to the next target*/
|
||||
/** Is the target reflecting the effect, not protected, and not in an semi-invulnerable state?*/
|
||||
const willBounce = (!isProtected && !this.reflected && !isCommanding
|
||||
&& move.hasFlag(MoveFlags.REFLECTABLE)
|
||||
&& (isReflecting || canMagicBounce)
|
||||
&& !activeSemiInvulnerability);
|
||||
|
||||
// If the move will bounce, then queue the bounce and move on to the next target
|
||||
if (!target.switchOutStatus && willBounce) {
|
||||
const newTargets = move.isMultiTarget() ? getMoveTargets(target, move.id).targets : [ user.getBattlerIndex() ];
|
||||
if (!isReflecting && canMagicBounce) {
|
||||
if (!isReflecting) {
|
||||
queuedPhases.push(new ShowAbilityPhase(target.getBattlerIndex(), target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr)));
|
||||
}
|
||||
queuedPhases.push(new MessagePhase(i18next.t("battle:magicCoatBounce", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })));
|
||||
|
||||
queuedPhases.push(new MovePhase(target, newTargets, new PokemonMove(move.id, 0, 0, true), true, true, true));
|
||||
continue;
|
||||
}
|
||||
@ -290,7 +299,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
/** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */
|
||||
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr)
|
||||
&& (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
|
||||
&& !target.getTag(SemiInvulnerableTag);
|
||||
&& !semiInvulnerableTag;
|
||||
|
||||
|
||||
/**
|
||||
@ -419,7 +428,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
// Apply queued phases
|
||||
queuedPhases.forEach(p => globalScene.unshiftPhase(p));
|
||||
if (queuedPhases.length) {
|
||||
globalScene.appendToPhase(queuedPhases, MoveEndPhase);
|
||||
}
|
||||
// Apply the move's POST_TARGET effects on the move's last hit, after all targeted effects have resolved
|
||||
const postTarget = (user.turnData.hitsLeft === 1 || !this.getFirstTarget()?.isActive()) ?
|
||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) :
|
||||
@ -635,12 +646,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
}
|
||||
|
||||
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match
|
||||
if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) {
|
||||
if (this.checkBypassAccAndInvuln(target)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -648,15 +654,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (target.getTag(BattlerTagType.TELEKINESIS) && !target.getTag(SemiInvulnerableTag) && !this.move.getMove().hasAttr(OneHitKOAttr)) {
|
||||
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||
if (target.getTag(BattlerTagType.TELEKINESIS) && !semiInvulnerableTag && !this.move.getMove().hasAttr(OneHitKOAttr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
|
||||
if (semiInvulnerableTag
|
||||
&& !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)
|
||||
&& !(this.move.getMove().hasAttr(ToxicAccuracyAttr) && user.isOfType(Type.POISON))
|
||||
) {
|
||||
if (this.checkBypassSemiInvuln(semiInvulnerableTag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -672,6 +675,58 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return rand < (moveAccuracy * accuracyMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the move should bypass *both* the accuracy *and* semi-invulnerable states.
|
||||
* @param target - The {@linkcode Pokemon} targeted by the invoked move
|
||||
* @returns `true` if the move should bypass accuracy and semi-invulnerability
|
||||
*
|
||||
* Accuracy and semi-invulnerability can be bypassed by:
|
||||
* - An ability like {@linkcode Abilities.NO_GUARD | No Guard}
|
||||
* - A poison type using {@linkcode Moves.TOXIC | Toxic}
|
||||
* - A move like {@linkcode Moves.LOCK_ON | Lock-On} or {@linkcode Moves.MIND_READER | Mind Reader}.
|
||||
* - A move like {@linkcode Moves.SPIEKS | Spikes} or {@linkcode Moves.SANDSTORM | Sandstorm} that targets the field
|
||||
*
|
||||
* Does *not* check against effects {@linkcode Moves.GLAIVE_RUSH | Glaive Rush} status (which
|
||||
* should not bypass semi-invulnerability), or interactions like Earthquake hitting against Dig,
|
||||
* (which should not bypass the accuracy check).
|
||||
*
|
||||
* @see {@linkcode hitCheck}
|
||||
*/
|
||||
public checkBypassAccAndInvuln(target: Pokemon) {
|
||||
const user = this.getUserPokemon();
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
if ([ MoveTarget.USER, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE, MoveTarget.BOTH_SIDES ].includes(this.move.getMove().moveTarget)) {
|
||||
return true;
|
||||
}
|
||||
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
|
||||
return true;
|
||||
}
|
||||
if ((this.move.getMove().hasAttr(ToxicAccuracyAttr) && user.isOfType(Type.POISON))) {
|
||||
return true;
|
||||
}
|
||||
// TODO: Fix lock on / mind reader check.
|
||||
if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the move is able to ignore the given `semiInvulnerableTag`
|
||||
* @param semiInvulnerableTag - The semiInvulnerbale tag to check against
|
||||
* @returns `true` if the move can ignore the semi-invulnerable state
|
||||
*/
|
||||
public checkBypassSemiInvuln(semiInvulnerableTag: SemiInvulnerableTag | nil): boolean {
|
||||
if (!semiInvulnerableTag) {
|
||||
return false;
|
||||
}
|
||||
const move = this.move.getMove();
|
||||
/** Does the move target the field instead of the target itself? */
|
||||
const isIndirectTarget = move.moveTarget in [ MoveTarget.USER, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE, MoveTarget.BOTH_SIDES ];
|
||||
return isIndirectTarget || move.getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType);
|
||||
}
|
||||
|
||||
/** @returns The {@linkcode Pokemon} using this phase's invoked move */
|
||||
public getUserPokemon(): Pokemon | null {
|
||||
if (this.battlerIndex > BattlerIndex.ENEMY_2) {
|
||||
|
@ -547,7 +547,7 @@ export class MovePhase extends BattlePhase {
|
||||
return;
|
||||
}
|
||||
|
||||
globalScene.queueMessage(i18next.t("battle:useMove", {
|
||||
globalScene.queueMessage(i18next.t(this.reflected ? "battle:magicCoatExe" : "battle:useMove", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon),
|
||||
moveName: this.move.getName()
|
||||
}), 500);
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allAbilities } from "#app/data/ability";
|
||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import { Stat } from "#app/enums/stat";
|
||||
import { StatusEffect } from "#app/enums/status-effect";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Magic Bounce", () => {
|
||||
@ -134,11 +135,12 @@ describe("Abilities - Magic Bounce", () => {
|
||||
game.move.select(Moves.SPIKES);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER)).toBe(1);
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY)).toBe(0);
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER)!["layers"]).toBe(1);
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not bounce back curse", async() => {
|
||||
game.override.starterSpecies(Species.GASTLY);
|
||||
await game.classicMode.startBattle([ Species.GASTLY ]);
|
||||
game.override.moveset([ Moves.CURSE ]);
|
||||
|
||||
@ -148,9 +150,67 @@ describe("Abilities - Magic Bounce", () => {
|
||||
expect(game.scene.getEnemyPokemon()!.getTag(BattlerTagType.CURSED)).toBeDefined();
|
||||
});
|
||||
|
||||
it("should cause stomping tantrum to double in power if the bounced move fails", async () => {
|
||||
it("should not cause encore to be interrupted after bouncing", async () => {
|
||||
game.override.moveset([ Moves.SPLASH, Moves.GROWL, Moves.ENCORE ]);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.GROWL ]);
|
||||
// game.override.ability(Abilities.MOLD_BREAKER);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
// Give the player MOLD_BREAKER for this turn to bypass Magic Bounce.
|
||||
vi.spyOn(playerPokemon, "getAbility").mockReturnValue(allAbilities[Abilities.MOLD_BREAKER]);
|
||||
|
||||
// turn 1
|
||||
game.move.select(Moves.ENCORE);
|
||||
await game.forceEnemyMove(Moves.TACKLE);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(Moves.TACKLE);
|
||||
|
||||
// turn 2
|
||||
vi.spyOn(playerPokemon, "getAbility").mockRestore();
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(Moves.TACKLE);
|
||||
expect(enemyPokemon.getLastXMoves()[0].move).toBe(Moves.TACKLE);
|
||||
|
||||
});
|
||||
|
||||
// TODO: encore is failing if the last move was virtual.
|
||||
it.todo("should not cause the bounced move to count for encore", async () => {
|
||||
game.override.moveset([ Moves.SPLASH, Moves.GROWL, Moves.ENCORE ]);
|
||||
game.override.enemyMoveset([ Moves.GROWL, Moves.TACKLE ]);
|
||||
game.override.enemyAbility(Abilities.MAGIC_BOUNCE);
|
||||
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
// turn 1
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.forceEnemyMove(Moves.TACKLE);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Give the player MOLD_BREAKER for this turn to bypass Magic Bounce.
|
||||
vi.spyOn(playerPokemon, "getAbility").mockReturnValue(allAbilities[Abilities.MOLD_BREAKER]);
|
||||
|
||||
// turn 2
|
||||
game.move.select(Moves.ENCORE);
|
||||
await game.forceEnemyMove(Moves.TACKLE);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemyPokemon.getTag(BattlerTagType.ENCORE)!["moveId"]).toBe(Moves.TACKLE);
|
||||
expect(enemyPokemon.getLastXMoves()[0].move).toBe(Moves.TACKLE);
|
||||
});
|
||||
|
||||
// TODO: stomping tantrum should consider moves that were bounced.
|
||||
it.todo("should cause stomping tantrum to double in power when the last move was bounced", async () => {
|
||||
game.override.battleType("single");
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
game.override.moveset([ Moves.STOMPING_TANTRUM, Moves.CHARM ]);
|
||||
|
||||
const stomping_tantrum = allMoves[Moves.STOMPING_TANTRUM];
|
||||
vi.spyOn(stomping_tantrum, "calculateBattlePower");
|
||||
@ -163,26 +223,25 @@ describe("Abilities - Magic Bounce", () => {
|
||||
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150);
|
||||
});
|
||||
|
||||
it("should properly cause the enemy's stomping tantrum to be doubled in power after bouncing and failing", async () => {
|
||||
game.override.battleType("double");
|
||||
game.override.enemyMoveset([ Moves.GROWL, Moves.STOMPING_TANTRUM, Moves.CHARM, Moves.SPLASH ]);
|
||||
game.override.enemyLevel(50);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
// TODO: stomping tantrum should consider moves that were bounced.
|
||||
it.todo("should properly cause the enemy's stomping tantrum to be doubled in power after bouncing and failing", async () => {
|
||||
game.override.enemyMoveset([ Moves.STOMPING_TANTRUM, Moves.SPLASH, Moves.CHARM ]);
|
||||
await game.classicMode.startBattle([ Species.BULBASAUR ]);
|
||||
|
||||
const stomping_tantrum = allMoves[Moves.STOMPING_TANTRUM];
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(stomping_tantrum, "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.CHARM, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
game.move.select(Moves.SPORE);
|
||||
await game.forceEnemyMove(Moves.CHARM);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(enemy.getLastXMoves(1)[0].result).toBe("success");
|
||||
|
||||
game.move.select(Moves.STOMPING_TANTRUM, 0, BattlerIndex.ENEMY_2);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150);
|
||||
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75);
|
||||
|
||||
await game.toNextTurn();
|
||||
game.move.select(Moves.GROWL, 0);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75);
|
||||
});
|
||||
@ -213,7 +272,7 @@ describe("Abilities - Magic Bounce", () => {
|
||||
vi.spyOn(attacker, "getAccuracyMultiplier").mockReturnValue(0.0);
|
||||
game.move.select(Moves.SPORE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(game.scene.getPlayerPokemon()!.status).toBeUndefined();
|
||||
expect(game.scene.getPlayerPokemon()!.status?.effect).toBe(StatusEffect.SLEEP);
|
||||
});
|
||||
|
||||
it("should take the accuracy of the magic bounce user into account", async () => {
|
||||
@ -240,14 +299,27 @@ describe("Abilities - Magic Bounce", () => {
|
||||
game.move.select(Moves.TRICK_ROOM, 1);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER)?.getSourcePokemon()?.id).toBe(enemy_1.id);
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER)?.getSourcePokemon()?.getBattlerIndex()).toBe(BattlerIndex.ENEMY);
|
||||
game.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER, true);
|
||||
|
||||
// turn 2
|
||||
game.move.select(Moves.STICKY_WEB, 0);
|
||||
game.move.select(Moves.TRICK_ROOM, 1);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER)?.getSourcePokemon()?.id).toBe(enemy_2.id);
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER)?.getSourcePokemon()?.getBattlerIndex()).toBe(BattlerIndex.ENEMY_2);
|
||||
});
|
||||
|
||||
it("should bounce back moves like spikes when the magic bounce user is semi-invulnerable", async () => {
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
game.override.moveset([ Moves.SPIKES ]);
|
||||
game.override.enemyMoveset([ Moves.FLY ]);
|
||||
|
||||
game.move.select(Moves.SPIKES);
|
||||
await game.forceEnemyMove(Moves.FLY);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER)!["layers"]).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user