mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 23:42:18 +02:00
Fixed info resetting and added backup leaveField
check
is this bad? yes does it work? maybe am i going insane? 101%
This commit is contained in:
parent
e743f79536
commit
2b0484a7cb
@ -1250,7 +1250,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
|
||||
/**
|
||||
* Determine if the move type change attribute can be applied
|
||||
*
|
||||
*
|
||||
* Can be applied if:
|
||||
* - The ability's condition is met, e.g. pixilate only boosts normal moves,
|
||||
* - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode Moves.MULTI_ATTACK}
|
||||
@ -1266,7 +1266,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
*/
|
||||
override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean {
|
||||
return (!this.condition || this.condition(pokemon, _defender, move)) &&
|
||||
!noAbilityTypeOverrideMoves.has(move.id) &&
|
||||
!noAbilityTypeOverrideMoves.has(move.id) &&
|
||||
(!pokemon.isTerastallized ||
|
||||
(move.id !== Moves.TERA_BLAST &&
|
||||
(move.id !== Moves.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(Species.TERAPAGOS))));
|
||||
@ -6946,10 +6946,12 @@ export function initAbilities() {
|
||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||
new Ability(Abilities.WIMP_OUT, 7)
|
||||
.attr(PostDamageForceSwitchAbAttr)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
.condition(getSheerForceHitDisableAbCondition())
|
||||
.bypassFaint(), // allows Wimp Out to activate with Reviver Seed
|
||||
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
||||
.attr(PostDamageForceSwitchAbAttr)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
.condition(getSheerForceHitDisableAbCondition())
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.WATER_COMPACTION, 7)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||
new Ability(Abilities.MERCILESS, 7)
|
||||
|
@ -35,6 +35,12 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
||||
protected canSwitchOut(switchOutTarget: Pokemon): boolean {
|
||||
const isPlayer = switchOutTarget instanceof PlayerPokemon;
|
||||
|
||||
if (switchOutTarget.isFainted()) {
|
||||
// Fainted Pokemon cannot be switched out by any means.
|
||||
// This is already checked in `MoveEffectAttr.canApply`, but better safe than sorry
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we aren't switching ourself out, ensure the target in question can actually be switched out by us
|
||||
if (!this.selfSwitch && !this.performForceSwitchChecks(switchOutTarget)) {
|
||||
return false;
|
||||
@ -86,15 +92,10 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
||||
|
||||
/**
|
||||
* Wrapper function to handle the actual "switching out" of Pokemon.
|
||||
* @param switchOutTarget - The {@linkcode Pokemon} (player or enemy) to be switched switch out.
|
||||
* @param switchOutTarget - The {@linkcode Pokemon} (player or enemy) to be switched out.
|
||||
*/
|
||||
protected doSwitch(switchOutTarget: Pokemon): void {
|
||||
if (switchOutTarget instanceof PlayerPokemon) {
|
||||
this.trySwitchPlayerPokemon(switchOutTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(switchOutTarget instanceof EnemyPokemon)) {
|
||||
if (!(switchOutTarget instanceof PlayerPokemon) && !(switchOutTarget instanceof EnemyPokemon)) {
|
||||
console.warn(
|
||||
"Switched out target (index %i) neither player nor enemy Pokemon!",
|
||||
switchOutTarget.getFieldIndex(),
|
||||
@ -102,17 +103,31 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (globalScene.currentBattle.battleType !== BattleType.WILD) {
|
||||
this.trySwitchTrainerPokemon(switchOutTarget);
|
||||
return;
|
||||
switch (true) {
|
||||
case switchOutTarget instanceof PlayerPokemon:
|
||||
this.trySwitchPlayerPokemon(switchOutTarget);
|
||||
break;
|
||||
case globalScene.currentBattle.battleType !== BattleType.WILD:
|
||||
this.trySwitchTrainerPokemon(switchOutTarget);
|
||||
break;
|
||||
default:
|
||||
this.tryFleeWildPokemon(switchOutTarget);
|
||||
}
|
||||
|
||||
this.tryFleeWildPokemon(switchOutTarget);
|
||||
// Hide the info container as soon as the switch out occurs.
|
||||
// Effects are kept to ensure correct Shell Bell interactions.
|
||||
// TODO: Should we hide the info container for wild fleeing?
|
||||
// Currently keeping it same as prior logic for consistency
|
||||
if (switchOutTarget instanceof EnemyPokemon && globalScene.currentBattle.battleType === BattleType.WILD) {
|
||||
switchOutTarget.hideInfo();
|
||||
}
|
||||
}
|
||||
|
||||
// NB: `prependToPhase` is used here to ensure that the switch happens before the move ends
|
||||
// and `arena.ignoreAbilities` is reset.
|
||||
// This ensures ability ignore effects will persist for the duration of the switch (for hazards).
|
||||
/*
|
||||
NB: `prependToPhase` is used here to ensure that the switch happens before the move ends
|
||||
and `arena.ignoreAbilities` is reset.
|
||||
This ensures ability ignore effects will persist for the duration of the switch (for hazards, etc).
|
||||
*/
|
||||
|
||||
private trySwitchPlayerPokemon(switchOutTarget: PlayerPokemon): void {
|
||||
// If not forced to switch, add a SwitchPhase to allow picking the next switched in Pokemon.
|
||||
@ -156,7 +171,6 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
||||
|
||||
private tryFleeWildPokemon(switchOutTarget: EnemyPokemon): void {
|
||||
// flee wild pokemon, redirecting moves to an ally in doubles as applicable.
|
||||
switchOutTarget.leaveField(false);
|
||||
globalScene.queueMessage(
|
||||
i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }),
|
||||
null,
|
||||
@ -169,7 +183,7 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
||||
globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
||||
}
|
||||
|
||||
// End battle if no enemies are active and enemy wasn't already KO'd (kos do )
|
||||
// End battle if no enemies are active and enemy wasn't already KO'd
|
||||
if (!allyPokemon?.isActive(true) && !switchOutTarget.isFainted()) {
|
||||
globalScene.clearEnemyHeldItemModifiers();
|
||||
|
||||
|
@ -1239,10 +1239,12 @@ export class MoveEffectAttr extends MoveAttr {
|
||||
* @param user {@linkcode Pokemon} using the move
|
||||
* @param target {@linkcode Pokemon} target of the move
|
||||
* @param move {@linkcode Move} with this attribute
|
||||
* @param args Set of unique arguments needed by this attribute
|
||||
* @returns true if basic application of the ability attribute should be possible
|
||||
* @param args - Any unique arguments needed by this attribute
|
||||
* @returns `true` if basic application of the ability attribute should be possible.
|
||||
* By default, checks that the target is not fainted and (for non self-targeting moves) not protected by an effect.
|
||||
*/
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]) {
|
||||
// TODO: why do we check frenzy tag here?
|
||||
return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp)
|
||||
&& (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) ||
|
||||
move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target }));
|
||||
@ -6239,7 +6241,7 @@ export class ForceSwitchOutAttr extends ForceSwitch(MoveEffectAttr) {
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
// Damaging switch moves should not "fail" _per se_ upon a failed switch -
|
||||
// Damaging switch moves should not "fail" _per se_ upon an unsuccessful switch -
|
||||
// they still succeed and deal damage (but just without actually switching out).
|
||||
return (user, target, move) => (move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move));
|
||||
}
|
||||
|
@ -6326,9 +6326,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param hideInfo - Whether to play the animation to hide the Pokemon's info container; default `true`.
|
||||
* @param destroy - Whether to destroy this Pokemon once it leaves the field; default `false`
|
||||
* @remarks
|
||||
* This **SHOULD NOT** be called when a `SummonPhase` or `SwitchSummonPhase` is already being added,
|
||||
* which can lead to premature resetting of {@linkcode turnData} and {@linkcode summonData}.
|
||||
* This **SHOULD NOT** be called with `clearEffects=true` when a `SummonPhase` or `SwitchSummonPhase` is already being added,
|
||||
* both of which do so already and can lead to premature resetting of {@linkcode turnData} and {@linkcode summonData}.
|
||||
*/
|
||||
// TODO: Review where this is being called and where it is necessary to call it
|
||||
leaveField(clearEffects = true, hideInfo = true, destroy = false) {
|
||||
console.log(`leaveField called on Pokemon ${this.name}`)
|
||||
this.resetSprite();
|
||||
|
@ -1788,6 +1788,10 @@ export class HitHealModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
const totalDmgDealt = pokemon.turnData.lastMoveDamageDealt.reduce((r, d) => r + d, 0);
|
||||
if (totalDmgDealt === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
globalScene.unshiftPhase(
|
||||
new PokemonHealPhase(
|
||||
pokemon.getBattlerIndex(),
|
||||
|
@ -61,8 +61,6 @@ export class FaintPhase extends PokemonPhase {
|
||||
faintPokemon.getTag(BattlerTagType.GRUDGE)?.lapse(faintPokemon, BattlerTagLapseType.CUSTOM, this.source);
|
||||
}
|
||||
|
||||
faintPokemon.resetSummonData();
|
||||
|
||||
if (!this.preventInstantRevive) {
|
||||
const instantReviveModifier = globalScene.applyModifier(
|
||||
PokemonInstantReviveModifier,
|
||||
@ -71,6 +69,7 @@ export class FaintPhase extends PokemonPhase {
|
||||
) as PokemonInstantReviveModifier;
|
||||
|
||||
if (instantReviveModifier) {
|
||||
faintPokemon.resetSummonData();
|
||||
faintPokemon.loseHeldItem(instantReviveModifier);
|
||||
globalScene.updateModifiers(this.player);
|
||||
return this.end();
|
||||
|
@ -3,7 +3,6 @@ import { globalScene } from "#app/global-scene";
|
||||
import {
|
||||
AddSecondStrikeAbAttr,
|
||||
AlwaysHitAbAttr,
|
||||
applyAbAttrs,
|
||||
applyPostAttackAbAttrs,
|
||||
applyPostDamageAbAttrs,
|
||||
applyPostDefendAbAttrs,
|
||||
@ -79,7 +78,6 @@ import type Move from "#app/data/moves/move";
|
||||
import { isFieldTargeted } from "#app/data/moves/move-utils";
|
||||
import { FaintPhase } from "./faint-phase";
|
||||
import { DamageAchv } from "#app/system/achv";
|
||||
import { userInfo } from "node:os";
|
||||
|
||||
type HitCheckEntry = [HitCheckResult, TypeDamageMultiplier];
|
||||
|
||||
@ -767,7 +765,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* - Triggering form changes and emergency exit / wimp out if this is the last hit
|
||||
*
|
||||
* @param target - the {@linkcode Pokemon} hit by this phase's move.
|
||||
* @param targetIndex - The index of the target (used to update damage dealt amounts)
|
||||
* @param effectiveness - The effectiveness of the move (as previously evaluated in {@linkcode hitCheck})
|
||||
* @param firstTarget - Whether this is the first target successfully struck by the move
|
||||
*/
|
||||
@ -813,7 +810,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
*/
|
||||
applyMoveAttrs(StatChangeBeforeDmgCalcAttr, user, target, this.move);
|
||||
|
||||
const { result: result, damage: dmg } = target.getAttackDamage({
|
||||
const { result, damage: dmg } = target.getAttackDamage({
|
||||
source: user,
|
||||
move: this.move,
|
||||
ignoreAbility: false,
|
||||
@ -852,7 +849,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
? 0
|
||||
: target.damageAndUpdate(dmg, {
|
||||
result: result as DamageResult,
|
||||
ignoreFaintPhase: true,
|
||||
ignoreFaintPhase: true, // ignore faint phase so we can handle it ourselves
|
||||
ignoreSegments: isOneHitKo,
|
||||
isCritical,
|
||||
});
|
||||
|
@ -43,7 +43,7 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||
import { MoveChargePhase } from "#app/phases/move-charge-phase";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||
import { getEnumValues, NumberHolder } from "#app/utils/common";
|
||||
import { NumberHolder } from "#app/utils/common";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
@ -160,7 +160,8 @@ export class MovePhase extends BattlePhase {
|
||||
}
|
||||
|
||||
this.pokemon.turnData.acted = true;
|
||||
this.pokemon.turnData.lastMoveDamageDealt = Array(Math.max(...getEnumValues(BattlerIndex))).fill(0);
|
||||
// TODO: Increase this if triple battles are added
|
||||
this.pokemon.turnData.lastMoveDamageDealt = Array(4).fill(0);
|
||||
|
||||
// Reset hit-related turn data when starting follow-up moves (e.g. Metronomed moves, Dancer repeats)
|
||||
if (this.followUp) {
|
||||
|
@ -36,8 +36,8 @@ export class SwitchPhase extends BattlePhase {
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
// Failsafe: skip modal switches if impossible (no eligible party members in reserve).
|
||||
if (this.isModal && globalScene.getBackupPartyMemberIndices(true).length === 0) {
|
||||
// Skip modal switch if impossible (no remaining party members that aren't in battle)
|
||||
if (this.isModal && !globalScene.getPlayerParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) {
|
||||
return super.end();
|
||||
}
|
||||
|
||||
@ -53,10 +53,9 @@ export class SwitchPhase extends BattlePhase {
|
||||
}
|
||||
|
||||
// Check if there is any space still on field.
|
||||
// TODO: Do we need this?
|
||||
if (
|
||||
this.isModal &&
|
||||
globalScene.getPlayerField().filter(p => p.isActive(true)).length > globalScene.currentBattle.getBattlerCount()
|
||||
globalScene.getPlayerField().filter(p => p.isActive(true)).length >= globalScene.currentBattle.getBattlerCount()
|
||||
) {
|
||||
return super.end();
|
||||
}
|
||||
|
@ -45,6 +45,15 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
}
|
||||
|
||||
preSummon(): void {
|
||||
const switchOutPokemon = this.getPokemon();
|
||||
|
||||
// if the target is still on-field, remove it and/or hide its info container.
|
||||
// Effects are kept to be transferred to the new Pokemon if applicable
|
||||
// TODO: Make moves that switch out pokemon defer to this phase
|
||||
if (switchOutPokemon.isOnField()) {
|
||||
switchOutPokemon.leaveField(false, switchOutPokemon.getBattleInfo()?.visible);
|
||||
}
|
||||
|
||||
if (!this.player) {
|
||||
if (this.slotIndex === -1) {
|
||||
//@ts-ignore
|
||||
@ -71,13 +80,12 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastPokemon = this.getPokemon();
|
||||
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon =>
|
||||
enemyPokemon.removeTagsBySourceId(lastPokemon.id),
|
||||
enemyPokemon.removeTagsBySourceId(switchOutPokemon.id),
|
||||
);
|
||||
|
||||
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
|
||||
const substitute = lastPokemon.getTag(SubstituteTag);
|
||||
const substitute = switchOutPokemon.getTag(SubstituteTag);
|
||||
if (substitute) {
|
||||
globalScene.tweens.add({
|
||||
targets: substitute.sprite,
|
||||
@ -92,25 +100,26 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
globalScene.ui.showText(
|
||||
this.player
|
||||
? i18next.t("battle:playerComeBack", {
|
||||
pokemonName: getPokemonNameWithAffix(lastPokemon),
|
||||
pokemonName: getPokemonNameWithAffix(switchOutPokemon),
|
||||
})
|
||||
: i18next.t("battle:trainerComeBack", {
|
||||
trainerName: globalScene.currentBattle.trainer?.getName(
|
||||
!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER,
|
||||
),
|
||||
pokemonName: lastPokemon.getNameToRender(),
|
||||
pokemonName: switchOutPokemon.getNameToRender(),
|
||||
}),
|
||||
);
|
||||
globalScene.playSound("se/pb_rel");
|
||||
lastPokemon.hideInfo();
|
||||
lastPokemon.tint(getPokeballTintColor(lastPokemon.getPokeball(true)), 1, 250, "Sine.easeIn");
|
||||
switchOutPokemon.hideInfo();
|
||||
switchOutPokemon.tint(getPokeballTintColor(switchOutPokemon.getPokeball(true)), 1, 250, "Sine.easeIn");
|
||||
globalScene.tweens.add({
|
||||
targets: lastPokemon,
|
||||
targets: switchOutPokemon,
|
||||
duration: 250,
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.5,
|
||||
onComplete: () => {
|
||||
globalScene.time.delayedCall(750, () => this.switchAndSummon());
|
||||
switchOutPokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -161,17 +170,9 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
}
|
||||
}
|
||||
|
||||
// Swap around the 2 pokemon's party positions and play an animation to send in the new pokemon.
|
||||
party[this.slotIndex] = this.lastPokemon;
|
||||
party[this.fieldIndex] = switchedInPokemon;
|
||||
const showTextAndSummon = () => {
|
||||
// We don't reset temp effects here as we need to transfer them to tne new pokemon
|
||||
// TODO: When should this remove the info container?
|
||||
// Force switch moves did it prior
|
||||
this.lastPokemon.leaveField(
|
||||
![SwitchType.BATON_PASS, SwitchType.SHED_TAIL].includes(this.switchType),
|
||||
this.doReturn,
|
||||
);
|
||||
globalScene.ui.showText(
|
||||
this.player
|
||||
? i18next.t("battle:playerGo", {
|
||||
|
@ -14,6 +14,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { HitResult } from "#app/field/pokemon";
|
||||
import type { ModifierOverride } from "#app/modifier/modifier-type";
|
||||
import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
||||
|
||||
describe("Abilities - Wimp Out", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -64,7 +65,7 @@ describe("Abilities - Wimp Out", () => {
|
||||
|
||||
expect(pokemon1.species.speciesId).toBe(Species.WIMPOD);
|
||||
expect(pokemon1.isFainted()).toBe(false);
|
||||
expect(pokemon1.getHpRatio()).toBeLessThan(0.5);
|
||||
expect(pokemon1.getHpRatio(true)).toBeLessThan(0.5);
|
||||
}
|
||||
|
||||
it("should switch the user out when falling below half HP, canceling its subsequent moves", async () => {
|
||||
@ -86,8 +87,11 @@ describe("Abilities - Wimp Out", () => {
|
||||
expect(wimpod.turnData.acted).toBe(false);
|
||||
});
|
||||
|
||||
it("should not trigger if user faints from damage", async () => {
|
||||
game.override.enemyMoveset(Moves.BRAVE_BIRD).enemyLevel(1000);
|
||||
it("should not trigger if user faints from damage and is revived", async () => {
|
||||
game.override
|
||||
.startingHeldItems([{ name: "REVIVER_SEED", count: 1 }])
|
||||
.enemyMoveset(Moves.BRAVE_BIRD)
|
||||
.enemyLevel(1000);
|
||||
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
|
||||
|
||||
const wimpod = game.scene.getPlayerPokemon()!;
|
||||
@ -95,9 +99,12 @@ describe("Abilities - Wimp Out", () => {
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(wimpod.isFainted()).toBe(true);
|
||||
expect(wimpod.isFainted()).toBe(false);
|
||||
expect(wimpod.isOnField()).toBe(true);
|
||||
expect(wimpod.getHpRatio()).toBeCloseTo(0.5);
|
||||
expect(wimpod.getHeldItems()).toHaveLength(0);
|
||||
expect(wimpod.waveData.abilitiesApplied).not.toContain(Abilities.WIMP_OUT);
|
||||
});
|
||||
|
||||
@ -185,34 +192,41 @@ describe("Abilities - Wimp Out", () => {
|
||||
});
|
||||
|
||||
it("should block U-turn or Volt Switch on activation", async () => {
|
||||
game.override.enemyMoveset(Moves.U_TURN);
|
||||
game.override.battleType(BattleType.TRAINER).enemyMoveset(Moves.U_TURN);
|
||||
await game.classicMode.startBattle([Species.WIMPOD, Species.TYRUNT]);
|
||||
const ninjask = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const hasFled = enemyPokemon.switchOutStatus;
|
||||
expect(hasFled).toBe(false);
|
||||
confirmSwitch();
|
||||
expect(ninjask.isOnField()).toBe(true);
|
||||
});
|
||||
|
||||
it("should not block U-turn or Volt Switch if not activated", async () => {
|
||||
game.override.enemyMoveset(Moves.U_TURN).battleType(BattleType.TRAINER);
|
||||
game.override.battleType(BattleType.TRAINER).enemyMoveset(Moves.U_TURN).battleType(BattleType.TRAINER);
|
||||
await game.classicMode.startBattle([Species.GOLISOPOD, Species.TYRUNT]);
|
||||
const ninjask1 = game.scene.getEnemyPokemon()!;
|
||||
|
||||
vi.spyOn(game.scene.getPlayerPokemon()!, "getAttackDamage").mockReturnValue({
|
||||
const wimpod = game.scene.getPlayerPokemon()!;
|
||||
const ninjask = game.scene.getEnemyPokemon()!;
|
||||
|
||||
// force enemy u turn to do 1 dmg
|
||||
vi.spyOn(wimpod, "getAttackDamage").mockReturnValue({
|
||||
cancelled: false,
|
||||
damage: 1,
|
||||
result: HitResult.EFFECTIVE,
|
||||
});
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("SwitchSummonPhase", false);
|
||||
const switchSummonPhase = game.scene.getCurrentPhase() as SwitchSummonPhase;
|
||||
expect(switchSummonPhase.getPokemon()).toBe(ninjask);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(ninjask1.isOnField()).toBe(true);
|
||||
expect(wimpod.isOnField()).toBe(true);
|
||||
expect(ninjask.isOnField()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not activate from Dragon Tail and Circle Throw", async () => {
|
||||
@ -242,7 +256,7 @@ describe("Abilities - Wimp Out", () => {
|
||||
{ type: "status", enemyMove: Moves.TOXIC },
|
||||
{ type: "Ghost-type Curse", enemyMove: Moves.CURSE },
|
||||
{ type: "Salt Cure", enemyMove: Moves.SALT_CURE },
|
||||
{ type: "partial trapping moves", enemyMove: Moves.WHIRLPOOL }, // no guard passive makes this guaranteed
|
||||
{ type: "partial trapping moves", enemyMove: Moves.WHIRLPOOL }, // no guard passive makes this 100% accurate
|
||||
{ type: "Leech Seed", enemyMove: Moves.LEECH_SEED },
|
||||
{ type: "Powder", playerMove: Moves.EMBER, enemyMove: Moves.POWDER },
|
||||
{ type: "Nightmare", playerPassive: Abilities.COMATOSE, enemyMove: Moves.NIGHTMARE },
|
||||
@ -254,10 +268,11 @@ describe("Abilities - Wimp Out", () => {
|
||||
playerMove = Moves.SPLASH,
|
||||
playerPassive = Abilities.NONE,
|
||||
enemyMove = Moves.SPLASH,
|
||||
enemyAbility = Abilities.BALL_FETCH,
|
||||
enemyAbility = Abilities.STURDY,
|
||||
}) => {
|
||||
game.override
|
||||
.moveset(playerMove)
|
||||
.enemyLevel(1)
|
||||
.passiveAbility(playerPassive)
|
||||
.enemySpecies(Species.GASTLY)
|
||||
.enemyMoveset(enemyMove)
|
||||
@ -266,7 +281,7 @@ describe("Abilities - Wimp Out", () => {
|
||||
|
||||
const wimpod = game.scene.getPlayerPokemon()!;
|
||||
expect(wimpod).toBeDefined();
|
||||
wimpod.hp = toDmgValue(wimpod.getMaxHp() / 2 + 5);
|
||||
wimpod.hp = toDmgValue(wimpod.getMaxHp() / 2 + 2);
|
||||
// mock enemy attack damage func to only do 1 dmg (for whirlpool)
|
||||
vi.spyOn(wimpod, "getAttackDamage").mockReturnValueOnce({
|
||||
cancelled: false,
|
||||
@ -342,18 +357,16 @@ describe("Abilities - Wimp Out", () => {
|
||||
|
||||
game.move.select(Moves.SUBSTITUTE);
|
||||
await game.forceEnemyMove(Moves.TIDY_UP);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.toNextTurn();
|
||||
|
||||
confirmNoSwitch();
|
||||
|
||||
// Turn 2: get back enough HP that substitute doesn't put us under
|
||||
wimpod.hp = wimpod.getMaxHp() * 0.78;
|
||||
wimpod.hp = wimpod.getMaxHp() * 0.8;
|
||||
|
||||
game.move.select(Moves.SUBSTITUTE);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.forceEnemyMove(Moves.ROUND);
|
||||
game.doSelectPartyPokemon(1);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
@ -413,7 +426,7 @@ describe("Abilities - Wimp Out", () => {
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
|
||||
confirmNoSwitch();
|
||||
expect(wimpod.isOnField()).toBe(true);
|
||||
expect(wimpod.getHpRatio()).toBeCloseTo(0.51);
|
||||
});
|
||||
|
||||
@ -548,7 +561,7 @@ describe("Abilities - Wimp Out", () => {
|
||||
|
||||
await game.classicMode.startBattle([Species.REGIDRAGO, Species.MAGIKARP]);
|
||||
|
||||
// turn 1
|
||||
// turn 1 - 1st wimpod faints while the 2nd one flees
|
||||
game.move.select(Moves.DRAGON_ENERGY, 0);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves } from "#app/data/moves/move";
|
||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||
import type { PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||
import { HitResult } from "#app/field/pokemon";
|
||||
import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@ -26,77 +26,106 @@ describe("Items - Reviver Seed", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.SPLASH, Moves.TACKLE, Moves.ENDURE])
|
||||
.moveset([Moves.SPLASH, Moves.TACKLE, Moves.LUMINA_CRASH])
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyAbility(Abilities.NO_GUARD)
|
||||
.startingHeldItems([{ name: "REVIVER_SEED" }])
|
||||
.enemyHeldItems([{ name: "REVIVER_SEED" }])
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
vi.spyOn(allMoves[Moves.SHEER_COLD], "accuracy", "get").mockReturnValue(100);
|
||||
vi.spyOn(allMoves[Moves.LEECH_SEED], "accuracy", "get").mockReturnValue(100);
|
||||
vi.spyOn(allMoves[Moves.WHIRLPOOL], "accuracy", "get").mockReturnValue(100);
|
||||
vi.spyOn(allMoves[Moves.WILL_O_WISP], "accuracy", "get").mockReturnValue(100);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ moveType: "Special Move", move: Moves.WATER_GUN },
|
||||
{ moveType: "Physical Move", move: Moves.TACKLE },
|
||||
{ moveType: "Fixed Damage Move", move: Moves.SEISMIC_TOSS },
|
||||
{ moveType: "Final Gambit", move: Moves.FINAL_GAMBIT },
|
||||
{ moveType: "Counter", move: Moves.COUNTER },
|
||||
{ moveType: "OHKO", move: Moves.SHEER_COLD },
|
||||
])("should activate the holder's reviver seed from a $moveType", async ({ move }) => {
|
||||
game.override.enemyLevel(100).startingLevel(1).enemyMoveset(move);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
player.damageAndUpdate(player.hp - 1);
|
||||
|
||||
const reviverSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier;
|
||||
vi.spyOn(reviverSeed, "apply");
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(player.isFainted()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should activate the holder's reviver seed from confusion self-hit", async () => {
|
||||
game.override.enemyLevel(1).startingLevel(100).enemyMoveset(Moves.SPLASH);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
player.damageAndUpdate(player.hp - 1);
|
||||
player.addTag(BattlerTagType.CONFUSED, 3);
|
||||
|
||||
const reviverSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier;
|
||||
vi.spyOn(reviverSeed, "apply");
|
||||
|
||||
vi.spyOn(player, "randBattleSeedInt").mockReturnValue(0); // Force confusion self-hit
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(player.isFainted()).toBeFalsy();
|
||||
});
|
||||
|
||||
// Damaging opponents tests
|
||||
it.each([
|
||||
{ moveType: "Damaging Move Chip Damage", move: Moves.SALT_CURE },
|
||||
{ moveType: "Chip Damage", move: Moves.LEECH_SEED },
|
||||
{ moveType: "Trapping Chip Damage", move: Moves.WHIRLPOOL },
|
||||
{ moveType: "Status Effect Damage", move: Moves.WILL_O_WISP },
|
||||
{ moveType: "Weather", move: Moves.SANDSTORM },
|
||||
])("should not activate the holder's reviver seed from $moveType", async ({ move }) => {
|
||||
game.override
|
||||
.enemyLevel(1)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.startingLevel(100)
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.moveset(move)
|
||||
.enemyMoveset(Moves.ENDURE);
|
||||
.enemyLevel(100); // makes hp tests more accurate due to rounding
|
||||
});
|
||||
|
||||
it("should be consumed upon fainting to revive the holder, removing temporary effects and healing to 50% max HP", async () => {
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
enemy.hp = 1;
|
||||
enemy.setStatStage(Stat.ATK, 6);
|
||||
|
||||
expect(enemy.getHeldItems()[0]).toBeInstanceOf(PokemonInstantReviveModifier);
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
// Enemy ate seed, was revived and healed to half HP, clearing its attack boost at the same time.
|
||||
expect(enemy.isFainted()).toBeFalsy();
|
||||
expect(enemy.getHpRatio()).toBeCloseTo(0.5);
|
||||
expect(enemy.getHeldItems()[0]).toBeUndefined();
|
||||
expect(enemy.getStatStage(Stat.ATK)).toBe(0);
|
||||
expect(enemy.turnData.acted).toBe(true);
|
||||
});
|
||||
|
||||
it("should nullify move effects on the killing blow and interrupt multi hits", async () => {
|
||||
// Give player a 4 hit lumina crash that lowers spdef by 2 stages per hit
|
||||
game.override.ability(Abilities.PARENTAL_BOND).startingHeldItems([
|
||||
{ name: "REVIVER_SEED", count: 1 },
|
||||
{ name: "MULTI_LENS", count: 2 },
|
||||
]);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
|
||||
// give enemy 3 hp, dying 3 hits into the move
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
enemy.hp = 3;
|
||||
vi.spyOn(enemy, "getAttackDamage").mockReturnValue({ cancelled: false, damage: 1, result: HitResult.EFFECTIVE });
|
||||
|
||||
game.move.select(Moves.LUMINA_CRASH);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("FaintPhase", false);
|
||||
expect(enemy.getStatStage(Stat.SPDEF)).toBe(-4); // killing hit effect got nullified due to fainting the target
|
||||
expect(enemy.getAttackDamage).toHaveBeenCalledTimes(3);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
// Attack was cut short due to lack of targets, after which the enemy was revived and their stat stages reset
|
||||
expect(enemy.isFainted()).toBeFalsy();
|
||||
expect(enemy.getStatStage(Stat.SPDEF)).toBe(0);
|
||||
expect(enemy.getHpRatio()).toBeCloseTo(0.5);
|
||||
expect(enemy.getHeldItems()[0]).toBeUndefined();
|
||||
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
expect(player.turnData.hitsLeft).toBe(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ moveType: "Special Moves", move: Moves.WATER_GUN },
|
||||
{ moveType: "Physical Moves", move: Moves.TACKLE },
|
||||
{ moveType: "Fixed Damage Moves", move: Moves.SEISMIC_TOSS },
|
||||
{ moveType: "Final Gambit", move: Moves.FINAL_GAMBIT },
|
||||
{ moveType: "Counter Moves", move: Moves.COUNTER },
|
||||
{ moveType: "OHKOs", move: Moves.SHEER_COLD },
|
||||
{ moveType: "Confusion Self-hits", move: Moves.CONFUSE_RAY },
|
||||
])("should activate from $moveType", async ({ move }) => {
|
||||
game.override.enemyMoveset(move).confusionActivation(true);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
player.hp = 1;
|
||||
|
||||
const reviverSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier;
|
||||
const seedSpy = vi.spyOn(reviverSeed, "apply");
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
expect(player.isFainted()).toBe(false);
|
||||
expect(seedSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Damaging tests
|
||||
it.each([
|
||||
{ moveType: "Salt Cure", move: Moves.SALT_CURE },
|
||||
{ moveType: "Leech Seed", move: Moves.LEECH_SEED },
|
||||
{ moveType: "Partial Trapping Move", move: Moves.WHIRLPOOL },
|
||||
{ moveType: "Status Effect", move: Moves.WILL_O_WISP },
|
||||
{ moveType: "Weather", move: Moves.SANDSTORM },
|
||||
])("should not activate from $moveType damage", async ({ move }) => {
|
||||
game.override.moveset(move).enemyMoveset(Moves.ENDURE);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
enemy.damageAndUpdate(enemy.hp - 1);
|
||||
enemy.hp = 1;
|
||||
|
||||
game.move.select(move);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
@ -107,31 +136,26 @@ describe("Items - Reviver Seed", () => {
|
||||
// Self-damage tests
|
||||
it.each([
|
||||
{ moveType: "Recoil", move: Moves.DOUBLE_EDGE },
|
||||
{ moveType: "Self-KO", move: Moves.EXPLOSION },
|
||||
{ moveType: "Self-Deduction", move: Moves.CURSE },
|
||||
{ moveType: "Sacrificial", move: Moves.EXPLOSION },
|
||||
{ moveType: "Ghost-type Curse", move: Moves.CURSE },
|
||||
{ moveType: "Liquid Ooze", move: Moves.GIGA_DRAIN },
|
||||
])("should not activate the holder's reviver seed from $moveType", async ({ move }) => {
|
||||
game.override
|
||||
.enemyLevel(100)
|
||||
.startingLevel(1)
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.moveset(move)
|
||||
.enemyAbility(Abilities.LIQUID_OOZE)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
])("should not activate from $moveType self-damage", async ({ move }) => {
|
||||
game.override.moveset(move).enemyAbility(Abilities.LIQUID_OOZE);
|
||||
await game.classicMode.startBattle([Species.GASTLY, Species.FEEBAS]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
player.damageAndUpdate(player.hp - 1);
|
||||
player.hp = 1;
|
||||
|
||||
const playerSeed = player.getHeldItems()[0] as PokemonInstantReviveModifier;
|
||||
vi.spyOn(playerSeed, "apply");
|
||||
const seedSpy = vi.spyOn(playerSeed, "apply");
|
||||
|
||||
game.move.select(move);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(player.isFainted()).toBeTruthy();
|
||||
expect(seedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not activate the holder's reviver seed from Destiny Bond fainting", async () => {
|
||||
it("should not activate from Destiny Bond fainting", async () => {
|
||||
game.override
|
||||
.enemyLevel(100)
|
||||
.startingLevel(1)
|
||||
@ -141,7 +165,7 @@ describe("Items - Reviver Seed", () => {
|
||||
.enemyMoveset(Moves.TACKLE);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
player.damageAndUpdate(player.hp - 1);
|
||||
player.hp = 1;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.DESTINY_BOND);
|
||||
|
@ -156,7 +156,7 @@ describe("Moves - Dragon Tail", () => {
|
||||
expect(enemySecPokemon.hp).toBeLessThan(enemySecPokemon.getMaxHp());
|
||||
});
|
||||
|
||||
it("should not switch out a target with suction cups", async () => {
|
||||
it("should not switch out a target with suction cups, unless the user has Mold Breaker", async () => {
|
||||
game.override.enemyAbility(Abilities.SUCTION_CUPS);
|
||||
await game.classicMode.startBattle([Species.REGIELEKI]);
|
||||
|
||||
@ -167,6 +167,16 @@ describe("Moves - Dragon Tail", () => {
|
||||
|
||||
expect(enemy.isOnField()).toBe(true);
|
||||
expect(enemy.isFullHp()).toBe(false);
|
||||
|
||||
// Turn 2: Mold Breaker should ignore switch blocking ability and switch out the target
|
||||
game.override.ability(Abilities.MOLD_BREAKER);
|
||||
enemy.hp = enemy.getMaxHp();
|
||||
|
||||
game.move.select(Moves.DRAGON_TAIL);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(enemy.isOnField()).toBe(false);
|
||||
expect(enemy.isFullHp()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not switch out a Commanded Dondozo", async () => {
|
||||
@ -203,43 +213,47 @@ describe("Moves - Dragon Tail", () => {
|
||||
expect(game.scene.getEnemyField().length).toBe(1);
|
||||
});
|
||||
|
||||
it("should not cause a softlock when activating an opponent trainer's reviver seed", async () => {
|
||||
it("should neither switch nor softlock when activating an opponent's reviver seed", async () => {
|
||||
game.override
|
||||
.startingWave(5)
|
||||
.battleType(BattleType.TRAINER)
|
||||
.enemyHeldItems([{ name: "REVIVER_SEED" }])
|
||||
.startingLevel(1000); // To make sure Dragon Tail KO's the opponent
|
||||
.startingLevel(1000); // make sure Dragon Tail KO's the opponent
|
||||
await game.classicMode.startBattle([Species.DRATINI]);
|
||||
|
||||
game.move.select(Moves.DRAGON_TAIL);
|
||||
const [wailord1, wailord2] = game.scene.getEnemyParty()!;
|
||||
expect(wailord1).toBeDefined();
|
||||
expect(wailord2).toBeDefined();
|
||||
|
||||
game.move.select(Moves.DRAGON_TAIL);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Make sure the enemy field is not empty and has a revived Pokemon
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
expect(enemy).toBeDefined();
|
||||
expect(enemy.hp).toBe(Math.floor(enemy.getMaxHp() / 2));
|
||||
expect(game.scene.getEnemyField().length).toBe(1);
|
||||
// Wailord should have consumed the reviver seed and stayed on field
|
||||
expect(wailord1.isOnField()).toBe(true);
|
||||
expect(wailord1.getHpRatio()).toBeCloseTo(0.5);
|
||||
expect(wailord1.getHeldItems()).toHaveLength(0);
|
||||
expect(wailord2.isOnField()).toBe(false);
|
||||
});
|
||||
|
||||
it("should not cause a softlock when activating a player's reviver seed", async () => {
|
||||
it("should neither switch nor softlock when activating a player's reviver seed", async () => {
|
||||
game.override
|
||||
.startingHeldItems([{ name: "REVIVER_SEED" }])
|
||||
.enemyMoveset(Moves.DRAGON_TAIL)
|
||||
.enemyLevel(1000); // To make sure Dragon Tail KO's the player
|
||||
await game.classicMode.startBattle([Species.DRATINI, Species.BULBASAUR]);
|
||||
.enemyLevel(1000); // make sure Dragon Tail KO's the player
|
||||
await game.classicMode.startBattle([Species.BLISSEY, Species.BULBASAUR]);
|
||||
|
||||
const [blissey, bulbasaur] = game.scene.getPlayerParty();
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
|
||||
await game.toNextTurn();
|
||||
|
||||
// Make sure the player's field is not empty and has a revived Pokemon
|
||||
const dratini = game.scene.getPlayerPokemon()!;
|
||||
expect(dratini).toBeDefined();
|
||||
expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2));
|
||||
expect(game.scene.getPlayerField().length).toBe(1);
|
||||
// dratini should have consumed the reviver seed and stayed on field
|
||||
expect(blissey.isOnField()).toBe(true);
|
||||
expect(blissey.getHpRatio()).toBeCloseTo(0.5);
|
||||
expect(blissey.getHeldItems()).toHaveLength(0);
|
||||
expect(bulbasaur.isOnField()).toBe(false);
|
||||
});
|
||||
|
||||
it("should force switches randomly", async () => {
|
||||
it("should force switches to a random off-field pokemon", async () => {
|
||||
game.override.enemyMoveset(Moves.DRAGON_TAIL).startingLevel(100).enemyLevel(1);
|
||||
await game.classicMode.startBattle([Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE]);
|
||||
|
||||
@ -279,6 +293,7 @@ describe("Moves - Dragon Tail", () => {
|
||||
|
||||
const [lapras, eevee, toxapex, primarina] = game.scene.getPlayerParty();
|
||||
expect(toxapex).toBeDefined();
|
||||
toxapex.hp = 0;
|
||||
|
||||
// Mock an RNG call to switch to the first eligible pokemon.
|
||||
// Eevee is ineligible and Toxapex is fainted, so it should proc on Primarina instead
|
||||
@ -286,7 +301,6 @@ describe("Moves - Dragon Tail", () => {
|
||||
return min;
|
||||
});
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.killPokemon(toxapex);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(lapras.isOnField()).toBe(false);
|
||||
@ -309,14 +323,15 @@ describe("Moves - Dragon Tail", () => {
|
||||
game.move.select(Moves.SPLASH, BattlerIndex.PLAYER_2);
|
||||
await game.forceEnemyMove(Moves.DRAGON_TAIL, BattlerIndex.PLAYER);
|
||||
await game.forceEnemyMove(Moves.DRAGON_TAIL, BattlerIndex.PLAYER_2);
|
||||
await game.killPokemon(cloyster);
|
||||
await game.killPokemon(kyogre);
|
||||
game.doSelectPartyPokemon(3);
|
||||
await game.toNextTurn();
|
||||
|
||||
// Eevee is ineligble due to challenge and cloyster is fainted, leaving no backup pokemon able to switch in
|
||||
// Eevee is ineligble due to challenge and kyogre is fainted, leaving no backup pokemon able to switch in
|
||||
expect(lapras.isOnField()).toBe(true);
|
||||
expect(kyogre.isOnField()).toBe(true);
|
||||
expect(kyogre.isOnField()).toBe(false);
|
||||
expect(eevee.isOnField()).toBe(false);
|
||||
expect(cloyster.isOnField()).toBe(false);
|
||||
expect(cloyster.isOnField()).toBe(true);
|
||||
expect(lapras.getInverseHp()).toBeGreaterThan(0);
|
||||
expect(kyogre.getInverseHp()).toBeGreaterThan(0);
|
||||
expect(game.scene.getBackupPartyMemberIndices(true)).toHaveLength(0);
|
||||
|
Loading…
Reference in New Issue
Block a user