mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-05 16:02:20 +02:00
Fixed Arena Trap tests and refactored SwitchSummonPhase
to be slightly less janky
This commit is contained in:
parent
2b0484a7cb
commit
5f1c98cac6
@ -129,6 +129,10 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
|||||||
This ensures ability ignore effects will persist for the duration of the switch (for hazards, etc).
|
This ensures ability ignore effects will persist for the duration of the switch (for hazards, etc).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to handle switching out a player Pokemon.
|
||||||
|
* @param switchOutTarget - The {@linkcode PlayerPokemon} to be switched out.
|
||||||
|
*/
|
||||||
private trySwitchPlayerPokemon(switchOutTarget: PlayerPokemon): void {
|
private trySwitchPlayerPokemon(switchOutTarget: PlayerPokemon): void {
|
||||||
// If not forced to switch, add a SwitchPhase to allow picking the next switched in Pokemon.
|
// If not forced to switch, add a SwitchPhase to allow picking the next switched in Pokemon.
|
||||||
if (this.switchType !== SwitchType.FORCE_SWITCH) {
|
if (this.switchType !== SwitchType.FORCE_SWITCH) {
|
||||||
@ -149,6 +153,10 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to handle switching out an opposing trainer's Pokemon.
|
||||||
|
* @param switchOutTarget - The {@linkcode EnemyPokemon} to be switched out.
|
||||||
|
*/
|
||||||
private trySwitchTrainerPokemon(switchOutTarget: EnemyPokemon): void {
|
private trySwitchTrainerPokemon(switchOutTarget: EnemyPokemon): void {
|
||||||
// fallback for no trainer
|
// fallback for no trainer
|
||||||
if (!globalScene.currentBattle.trainer) {
|
if (!globalScene.currentBattle.trainer) {
|
||||||
@ -169,8 +177,12 @@ export function ForceSwitch<TBase extends SubMoveOrAbAttr>(Base: TBase) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to handle fleeing a wild enemy Pokemon, redirecting incoming moves to its ally as applicable.
|
||||||
|
* @param switchOutTarget - The {@linkcode EnemyPokemon} fleeing the battle.
|
||||||
|
*/
|
||||||
private tryFleeWildPokemon(switchOutTarget: EnemyPokemon): void {
|
private tryFleeWildPokemon(switchOutTarget: EnemyPokemon): void {
|
||||||
// flee wild pokemon, redirecting moves to an ally in doubles as applicable.
|
switchOutTarget.leaveField(true);
|
||||||
globalScene.queueMessage(
|
globalScene.queueMessage(
|
||||||
i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }),
|
i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }),
|
||||||
null,
|
null,
|
||||||
|
@ -48,6 +48,7 @@ import {
|
|||||||
ConfusionOnStatusEffectAbAttr,
|
ConfusionOnStatusEffectAbAttr,
|
||||||
FieldMoveTypePowerBoostAbAttr,
|
FieldMoveTypePowerBoostAbAttr,
|
||||||
FieldPreventExplosiveMovesAbAttr,
|
FieldPreventExplosiveMovesAbAttr,
|
||||||
|
ForceSwitchOutImmunityAbAttr,
|
||||||
HealFromBerryUseAbAttr,
|
HealFromBerryUseAbAttr,
|
||||||
IgnoreContactAbAttr,
|
IgnoreContactAbAttr,
|
||||||
IgnoreMoveEffectsAbAttr,
|
IgnoreMoveEffectsAbAttr,
|
||||||
@ -6222,6 +6223,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
|||||||
/**
|
/**
|
||||||
* Attribute to forcibly switch out the user or target of a Move.
|
* Attribute to forcibly switch out the user or target of a Move.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Add custom failure text & locales
|
||||||
export class ForceSwitchOutAttr extends ForceSwitch(MoveEffectAttr) {
|
export class ForceSwitchOutAttr extends ForceSwitch(MoveEffectAttr) {
|
||||||
constructor(
|
constructor(
|
||||||
selfSwitch: boolean = false,
|
selfSwitch: boolean = false,
|
||||||
@ -6279,6 +6281,14 @@ export class ForceSwitchOutAttr extends ForceSwitch(MoveEffectAttr) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
|
||||||
|
const blockedByAbility = new BooleanHolder(false);
|
||||||
|
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
|
||||||
|
if (blockedByAbility.value) {
|
||||||
|
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
const reservePartyMembers = globalScene.getBackupPartyMemberIndices(user.isPlayer() === this.selfSwitch, !user.isPlayer() ? (user as EnemyPokemon).trainerSlot : undefined)
|
const reservePartyMembers = globalScene.getBackupPartyMemberIndices(user.isPlayer() === this.selfSwitch, !user.isPlayer() ? (user as EnemyPokemon).trainerSlot : undefined)
|
||||||
if (reservePartyMembers.length === 0) {
|
if (reservePartyMembers.length === 0) {
|
||||||
|
@ -342,7 +342,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
public luck: number;
|
public luck: number;
|
||||||
public pauseEvolutions: boolean;
|
public pauseEvolutions: boolean;
|
||||||
public pokerus: boolean;
|
public pokerus: boolean;
|
||||||
// TODO: Document these
|
/** Whether this Pokemon is currently attempting to switch in. */
|
||||||
public switchOutStatus = false;
|
public switchOutStatus = false;
|
||||||
public evoCounter: number;
|
public evoCounter: number;
|
||||||
public teraType: PokemonType;
|
public teraType: PokemonType;
|
||||||
|
@ -39,16 +39,12 @@ export class CheckSwitchPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ...if there are no other allowed Pokemon in the player's party to switch with
|
// ...if there are no other allowed Pokemon in the player's party to switch with
|
||||||
if (
|
if (globalScene.getBackupPartyMemberIndices(true).length === 0) {
|
||||||
!globalScene
|
|
||||||
.getPlayerParty()
|
|
||||||
.slice(1)
|
|
||||||
.filter(p => p.isActive()).length
|
|
||||||
) {
|
|
||||||
return super.end();
|
return super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...or if any player Pokemon has an effect that prevents the checked Pokemon from switching
|
// ...or if any player Pokemon has an effect that prevents the checked Pokemon from switching
|
||||||
|
// TODO: Ignore trapping check if baton item is held (since those bypass trapping)
|
||||||
if (
|
if (
|
||||||
pokemon.getTag(BattlerTagType.FRENZY) ||
|
pokemon.getTag(BattlerTagType.FRENZY) ||
|
||||||
pokemon.isTrapped() ||
|
pokemon.isTrapped() ||
|
||||||
|
@ -276,7 +276,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||||||
globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex()));
|
globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pokemon.resetTurnData();
|
pokemon.resetTurnData(); // TODO: this can probably be removed...???
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.loaded ||
|
!this.loaded ||
|
||||||
|
@ -19,16 +19,17 @@ import { SwitchType } from "#enums/switch-type";
|
|||||||
|
|
||||||
export class SwitchSummonPhase extends SummonPhase {
|
export class SwitchSummonPhase extends SummonPhase {
|
||||||
private readonly switchType: SwitchType;
|
private readonly switchType: SwitchType;
|
||||||
private readonly slotIndex: number;
|
|
||||||
private readonly doReturn: boolean;
|
private readonly doReturn: boolean;
|
||||||
|
private slotIndex: number;
|
||||||
|
|
||||||
private lastPokemon: Pokemon;
|
private lastPokemon: Pokemon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for creating a new {@linkcode SwitchSummonPhase}, the phase where player and enemy Pokemon are switched out.
|
* Constructor for creating a new {@linkcode SwitchSummonPhase}, the phase where player and enemy Pokemon are switched out
|
||||||
|
* and replaced by another Pokemon from the same party.
|
||||||
* @param switchType - The type of switch behavior
|
* @param switchType - The type of switch behavior
|
||||||
* @param fieldIndex - Position on the battle field
|
* @param fieldIndex - The position on field of the Pokemon being switched out
|
||||||
* @param slotIndex - The index of pokemon (in party of 6) to switch into
|
* @param slotIndex - The 0-indexed party position of the Pokemon switching in, or `-1` to use the default trainer switch logic.
|
||||||
* @param doReturn - Whether to render "comeback" dialogue
|
* @param doReturn - Whether to render "comeback" dialogue
|
||||||
* @param player - Whether the switch came from the player or enemy; default `true`
|
* @param player - Whether the switch came from the player or enemy; default `true`
|
||||||
*/
|
*/
|
||||||
@ -40,6 +41,9 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
this.doReturn = doReturn;
|
this.doReturn = doReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This is calling `applyPreSummonAbAttrs` both far too early and on the wrong pokemon;
|
||||||
|
// `super.start` calls applyPreSummonAbAttrs(PreSummonAbAttr, this.getPokemon()),
|
||||||
|
// and `this.getPokemon` is the pokemon SWITCHING OUT, NOT IN
|
||||||
start(): void {
|
start(): void {
|
||||||
super.start();
|
super.start();
|
||||||
}
|
}
|
||||||
@ -47,36 +51,35 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
preSummon(): void {
|
preSummon(): void {
|
||||||
const switchOutPokemon = this.getPokemon();
|
const switchOutPokemon = this.getPokemon();
|
||||||
|
|
||||||
// if the target is still on-field, remove it and/or hide its info container.
|
// For enemy trainers, pick a pokemon to switch to and/or display the opposing pokeball tray
|
||||||
// Effects are kept to be transferred to the new Pokemon if applicable
|
if (!this.player && globalScene.currentBattle.trainer) {
|
||||||
// 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) {
|
if (this.slotIndex === -1) {
|
||||||
//@ts-ignore
|
this.slotIndex = globalScene.currentBattle.trainer.getNextSummonIndex(this.getTrainerSlotFromFieldIndex());
|
||||||
this.slotIndex = globalScene.currentBattle.trainer?.getNextSummonIndex(
|
|
||||||
!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER,
|
|
||||||
); // TODO: what would be the default trainer-slot fallback?
|
|
||||||
}
|
}
|
||||||
|
// TODO: Remove this check since `getNextSummonIndex` _should_ always return a number between 0 and party length inclusive
|
||||||
if (this.slotIndex > -1) {
|
if (this.slotIndex > -1) {
|
||||||
this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
|
this.showEnemyTrainer(this.getTrainerSlotFromFieldIndex());
|
||||||
globalScene.pbTrayEnemy.showPbTray(globalScene.getEnemyParty());
|
globalScene.pbTrayEnemy.showPbTray(globalScene.getEnemyParty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.doReturn ||
|
!this.doReturn ||
|
||||||
|
// TODO: this part of the check need not exist `- `switchAndSummon` returns near immediately if we have no pokemon to switch into
|
||||||
(this.slotIndex !== -1 &&
|
(this.slotIndex !== -1 &&
|
||||||
!(this.player ? globalScene.getPlayerParty() : globalScene.getEnemyParty())[this.slotIndex])
|
!(this.player ? globalScene.getPlayerParty() : globalScene.getEnemyParty())[this.slotIndex])
|
||||||
) {
|
) {
|
||||||
|
// 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 later on.
|
||||||
|
if (switchOutPokemon.isOnField()) {
|
||||||
|
switchOutPokemon.leaveField(false, switchOutPokemon.getBattleInfo()?.visible);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.player) {
|
if (this.player) {
|
||||||
this.switchAndSummon();
|
this.switchAndSummon();
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
globalScene.time.delayedCall(750, () => this.switchAndSummon());
|
globalScene.time.delayedCall(750, () => this.switchAndSummon());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +87,8 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
enemyPokemon.removeTagsBySourceId(switchOutPokemon.id),
|
enemyPokemon.removeTagsBySourceId(switchOutPokemon.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
|
// If not transferring a substitute, play animation to remove it from the field
|
||||||
|
if (!this.shouldKeepEffects()) {
|
||||||
const substitute = switchOutPokemon.getTag(SubstituteTag);
|
const substitute = switchOutPokemon.getTag(SubstituteTag);
|
||||||
if (substitute) {
|
if (substitute) {
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
@ -103,9 +107,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
pokemonName: getPokemonNameWithAffix(switchOutPokemon),
|
pokemonName: getPokemonNameWithAffix(switchOutPokemon),
|
||||||
})
|
})
|
||||||
: i18next.t("battle:trainerComeBack", {
|
: i18next.t("battle:trainerComeBack", {
|
||||||
trainerName: globalScene.currentBattle.trainer?.getName(
|
trainerName: globalScene.currentBattle.trainer?.getName(this.getTrainerSlotFromFieldIndex()),
|
||||||
!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER,
|
|
||||||
),
|
|
||||||
pokemonName: switchOutPokemon.getNameToRender(),
|
pokemonName: switchOutPokemon.getNameToRender(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -119,19 +121,21 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
globalScene.time.delayedCall(750, () => this.switchAndSummon());
|
globalScene.time.delayedCall(750, () => this.switchAndSummon());
|
||||||
switchOutPokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
|
switchOutPokemon.leaveField(this.switchType === SwitchType.SWITCH, false); // TODO: do we have to do this right here right now
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
switchAndSummon() {
|
switchAndSummon() {
|
||||||
const party = this.player ? this.getParty() : globalScene.getEnemyParty();
|
const party = this.player ? this.getParty() : globalScene.getEnemyParty();
|
||||||
const switchedInPokemon: Pokemon | undefined = party[this.slotIndex];
|
const switchInPokemon: Pokemon | undefined = party[this.slotIndex];
|
||||||
this.lastPokemon = this.getPokemon();
|
this.lastPokemon = this.getPokemon();
|
||||||
|
|
||||||
applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon);
|
applyPreSummonAbAttrs(PreSummonAbAttr, switchInPokemon);
|
||||||
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
||||||
if (!switchedInPokemon) {
|
// TODO: Why do we trigger post switch out attributes even if the switch in target doesn't exist?
|
||||||
|
// (This should almost certainly go somewhere inside `preSummon`)
|
||||||
|
if (!switchInPokemon) {
|
||||||
this.end();
|
this.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -139,7 +143,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
if (this.switchType === SwitchType.BATON_PASS) {
|
if (this.switchType === SwitchType.BATON_PASS) {
|
||||||
// If switching via baton pass, update opposing tags coming from the prior pokemon
|
// If switching via baton pass, update opposing tags coming from the prior pokemon
|
||||||
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) =>
|
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach((enemyPokemon: Pokemon) =>
|
||||||
enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id),
|
enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchInPokemon.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the recipient pokemon lacks a baton, give our baton to it during the swap
|
// If the recipient pokemon lacks a baton, give our baton to it during the swap
|
||||||
@ -147,7 +151,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
!globalScene.findModifier(
|
!globalScene.findModifier(
|
||||||
m =>
|
m =>
|
||||||
m instanceof SwitchEffectTransferModifier &&
|
m instanceof SwitchEffectTransferModifier &&
|
||||||
(m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id,
|
(m as SwitchEffectTransferModifier).pokemonId === switchInPokemon.id,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const batonPassModifier = globalScene.findModifier(
|
const batonPassModifier = globalScene.findModifier(
|
||||||
@ -159,7 +163,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
if (batonPassModifier) {
|
if (batonPassModifier) {
|
||||||
globalScene.tryTransferHeldItemModifier(
|
globalScene.tryTransferHeldItemModifier(
|
||||||
batonPassModifier,
|
batonPassModifier,
|
||||||
switchedInPokemon,
|
switchInPokemon,
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
@ -171,12 +175,14 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
party[this.slotIndex] = this.lastPokemon;
|
party[this.slotIndex] = this.lastPokemon;
|
||||||
party[this.fieldIndex] = switchedInPokemon;
|
party[this.fieldIndex] = switchInPokemon;
|
||||||
|
// TODO: Make this text configurable for Dragon Tail & co.
|
||||||
|
// TODO: Make this a method
|
||||||
const showTextAndSummon = () => {
|
const showTextAndSummon = () => {
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
this.player
|
this.player
|
||||||
? i18next.t("battle:playerGo", {
|
? i18next.t("battle:playerGo", {
|
||||||
pokemonName: getPokemonNameWithAffix(switchedInPokemon),
|
pokemonName: getPokemonNameWithAffix(switchInPokemon),
|
||||||
})
|
})
|
||||||
: i18next.t("battle:trainerGo", {
|
: i18next.t("battle:trainerGo", {
|
||||||
trainerName: globalScene.currentBattle.trainer?.getName(
|
trainerName: globalScene.currentBattle.trainer?.getName(
|
||||||
@ -190,15 +196,15 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
* If this switch is passing a Substitute, make the switched Pokemon matches the returned Pokemon's state as it left.
|
* If this switch is passing a Substitute, make the switched Pokemon matches the returned Pokemon's state as it left.
|
||||||
* Otherwise, clear any persisting tags on the returned Pokemon.
|
* Otherwise, clear any persisting tags on the returned Pokemon.
|
||||||
*/
|
*/
|
||||||
if (this.switchType === SwitchType.BATON_PASS || this.switchType === SwitchType.SHED_TAIL) {
|
if (this.shouldKeepEffects()) {
|
||||||
const substitute = this.lastPokemon.getTag(SubstituteTag);
|
const substitute = this.lastPokemon.getTag(SubstituteTag);
|
||||||
if (substitute) {
|
if (substitute) {
|
||||||
switchedInPokemon.x += this.lastPokemon.getSubstituteOffset()[0];
|
switchInPokemon.x += this.lastPokemon.getSubstituteOffset()[0];
|
||||||
switchedInPokemon.y += this.lastPokemon.getSubstituteOffset()[1];
|
switchInPokemon.y += this.lastPokemon.getSubstituteOffset()[1];
|
||||||
switchedInPokemon.setAlpha(0.5);
|
switchInPokemon.setAlpha(0.5);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switchedInPokemon.fieldSetup();
|
switchInPokemon.fieldSetup();
|
||||||
}
|
}
|
||||||
this.summon();
|
this.summon();
|
||||||
};
|
};
|
||||||
@ -253,4 +259,16 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
queuePostSummon(): void {
|
queuePostSummon(): void {
|
||||||
globalScene.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex()));
|
globalScene.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldKeepEffects(): boolean {
|
||||||
|
return [SwitchType.BATON_PASS, SwitchType.SHED_TAIL].includes(this.switchType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTrainerSlotFromFieldIndex(): TrainerSlot {
|
||||||
|
return this.player || !globalScene.currentBattle.trainer
|
||||||
|
? TrainerSlot.NONE
|
||||||
|
: this.fieldIndex % 2 === 0
|
||||||
|
? TrainerSlot.TRAINER
|
||||||
|
: TrainerSlot.TRAINER_PARTNER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import { allAbilities } from "#app/data/data-lists";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
|
import type CommandUiHandler from "#app/ui/command-ui-handler";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Button } from "#enums/buttons";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import i18next from "i18next";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||||
|
|
||||||
describe("Abilities - Arena Trap", () => {
|
describe("Abilities - Arena Trap", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -23,68 +28,94 @@ describe("Abilities - Arena Trap", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset(Moves.SPLASH)
|
.moveset([Moves.SPLASH, Moves.TELEPORT])
|
||||||
.ability(Abilities.ARENA_TRAP)
|
.ability(Abilities.ARENA_TRAP)
|
||||||
.enemySpecies(Species.RALTS)
|
.enemySpecies(Species.RALTS)
|
||||||
.enemyAbility(Abilities.BALL_FETCH)
|
.enemyAbility(Abilities.ARENA_TRAP)
|
||||||
.enemyMoveset(Moves.TELEPORT);
|
.enemyMoveset(Moves.SPLASH);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Enable test when Issue #935 is addressed
|
// NB: Since switching moves bypass trapping, the only way fleeing can occur is from the player
|
||||||
it.todo("should not allow grounded Pokémon to flee", async () => {
|
// TODO: Implement once forced flee helper exists
|
||||||
|
it.todo("should interrupt player flee attempt and display message, unless user has Run Away", async () => {
|
||||||
game.override.battleStyle("single");
|
game.override.battleStyle("single");
|
||||||
|
await game.classicMode.startBattle([Species.DUGTRIO, Species.GOTHITELLE]);
|
||||||
|
|
||||||
await game.classicMode.startBattle();
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
const enemy = game.scene.getEnemyPokemon();
|
game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => {
|
||||||
|
// no switch out command should be queued due to arena trap
|
||||||
|
expect(game.scene.currentBattle.turnCommands[0]).toBeNull();
|
||||||
|
|
||||||
|
// back out and cancel the flee to avoid timeout
|
||||||
|
(game.scene.ui.getHandler() as CommandUiHandler).processInput(Button.CANCEL);
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(game.textInterceptor.logs).toContain(
|
||||||
|
i18next.t("abilityTriggers:arenaTrap", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(enemy),
|
||||||
|
abilityName: allAbilities[Abilities.ARENA_TRAP].name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
game.override.ability(Abilities.RUN_AWAY);
|
||||||
|
|
||||||
|
// do switch stuff
|
||||||
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(enemy).toBe(game.scene.getEnemyPokemon());
|
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should interrupt player switch attempt and display message", async () => {
|
||||||
|
game.override.battleStyle("single").enemyAbility(Abilities.ARENA_TRAP);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.DUGTRIO, Species.GOTHITELLE]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
game.onNextPrompt("CommandPhase", UiMode.PARTY, () => {
|
||||||
|
// no switch out command should be queued due to arena trap
|
||||||
|
expect(game.scene.currentBattle.turnCommands[0]).toBeNull();
|
||||||
|
|
||||||
|
// back out and cancel the switch to avoid timeout
|
||||||
|
(game.scene.ui.getHandler() as CommandUiHandler).processInput(Button.CANCEL);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(game.textInterceptor.logs).toContain(
|
||||||
|
i18next.t("abilityTriggers:arenaTrap", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(enemy),
|
||||||
|
abilityName: allAbilities[Abilities.ARENA_TRAP].name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should guarantee double battle with any one LURE", async () => {
|
it("should guarantee double battle with any one LURE", async () => {
|
||||||
game.override.startingModifier([{ name: "LURE" }]).startingWave(2);
|
game.override.startingModifier([{ name: "LURE" }]).startingWave(2);
|
||||||
|
await game.classicMode.startBattle([Species.DUGTRIO]);
|
||||||
|
|
||||||
await game.classicMode.startBattle();
|
expect(game.scene.getEnemyField()).toHaveLength(2);
|
||||||
|
|
||||||
expect(game.scene.getEnemyField().length).toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* This checks if the Player Pokemon is able to switch out/run away after the Enemy Pokemon with {@linkcode Abilities.ARENA_TRAP}
|
|
||||||
* is forcefully moved out of the field from moves such as Roar {@linkcode Moves.ROAR}
|
|
||||||
*
|
|
||||||
* Note: It should be able to switch out/run away
|
|
||||||
*/
|
|
||||||
it("should lift if pokemon with this ability leaves the field", async () => {
|
it("should lift if pokemon with this ability leaves the field", async () => {
|
||||||
game.override
|
game.override.battleStyle("single").enemyMoveset(Moves.SPLASH).moveset(Moves.ROAR);
|
||||||
.battleStyle("double")
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
.enemyMoveset(Moves.SPLASH)
|
|
||||||
.moveset([Moves.ROAR, Moves.SPLASH])
|
|
||||||
.ability(Abilities.BALL_FETCH);
|
|
||||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.SUDOWOODO, Species.LUNATONE]);
|
|
||||||
|
|
||||||
const [enemy1, enemy2] = game.scene.getEnemyField();
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
const [player1, player2] = game.scene.getPlayerField();
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
vi.spyOn(enemy1, "getAbility").mockReturnValue(allAbilities[Abilities.ARENA_TRAP]);
|
expect(player.isTrapped()).toBe(true);
|
||||||
|
expect(enemy.isOnField()).toBe(true);
|
||||||
|
|
||||||
game.move.select(Moves.ROAR);
|
game.move.select(Moves.ROAR);
|
||||||
game.move.select(Moves.SPLASH, 1);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
// This runs the fist command phase where the moves are selected
|
expect(player.isTrapped()).toBe(false);
|
||||||
await game.toNextTurn();
|
expect(enemy.isOnField()).toBe(false);
|
||||||
// During the next command phase the player pokemons should not be trapped anymore
|
|
||||||
game.move.select(Moves.SPLASH);
|
|
||||||
game.move.select(Moves.SPLASH, 1);
|
|
||||||
await game.toNextTurn();
|
|
||||||
|
|
||||||
expect(player1.isTrapped()).toBe(false);
|
|
||||||
expect(player2.isTrapped()).toBe(false);
|
|
||||||
expect(enemy1.isOnField()).toBe(false);
|
|
||||||
expect(enemy2.isOnField()).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -165,6 +165,8 @@ export default class GameManager {
|
|||||||
* @param mode - The mode to wait for.
|
* @param mode - The mode to wait for.
|
||||||
* @param callback - The callback function to execute on next prompt.
|
* @param callback - The callback function to execute on next prompt.
|
||||||
* @param expireFn - Optional function to determine if the prompt has expired.
|
* @param expireFn - Optional function to determine if the prompt has expired.
|
||||||
|
* @remarks
|
||||||
|
* If multiple callbacks are queued for the same phase, they will be executed in the order they were added.
|
||||||
*/
|
*/
|
||||||
onNextPrompt(
|
onNextPrompt(
|
||||||
phaseTarget: string,
|
phaseTarget: string,
|
||||||
|
Loading…
Reference in New Issue
Block a user