mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-15 03:49:33 +02:00
Merge branch 'beta' into test-matchers-v2
This commit is contained in:
commit
1977c3e6b8
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@ -11,6 +11,7 @@ on:
|
|||||||
- beta
|
- beta
|
||||||
merge_group:
|
merge_group:
|
||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-path-change-filter:
|
check-path-change-filter:
|
||||||
|
@ -4,6 +4,7 @@ import { defaultStarterSpecies } from "#app/constants";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { pokemonEvolutions } from "#balance/pokemon-evolutions";
|
import { pokemonEvolutions } from "#balance/pokemon-evolutions";
|
||||||
import { speciesStarterCosts } from "#balance/starters";
|
import { speciesStarterCosts } from "#balance/starters";
|
||||||
|
import { getEggTierForSpecies } from "#data/egg";
|
||||||
import { pokemonFormChanges } from "#data/pokemon-forms";
|
import { pokemonFormChanges } from "#data/pokemon-forms";
|
||||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||||
import { getPokemonSpeciesForm } from "#data/pokemon-species";
|
import { getPokemonSpeciesForm } from "#data/pokemon-species";
|
||||||
@ -11,6 +12,7 @@ import { BattleType } from "#enums/battle-type";
|
|||||||
import { ChallengeType } from "#enums/challenge-type";
|
import { ChallengeType } from "#enums/challenge-type";
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
import { TypeColor, TypeShadow } from "#enums/color";
|
import { TypeColor, TypeShadow } from "#enums/color";
|
||||||
|
import { EggTier } from "#enums/egg-type";
|
||||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||||
import { ModifierTier } from "#enums/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import type { MoveId } from "#enums/move-id";
|
import type { MoveId } from "#enums/move-id";
|
||||||
@ -683,11 +685,14 @@ export class SingleTypeChallenge extends Challenge {
|
|||||||
*/
|
*/
|
||||||
export class FreshStartChallenge extends Challenge {
|
export class FreshStartChallenge extends Challenge {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(Challenges.FRESH_START, 1);
|
super(Challenges.FRESH_START, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean {
|
applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean {
|
||||||
if (!defaultStarterSpecies.includes(pokemon.speciesId)) {
|
if (
|
||||||
|
(this.value === 1 && !defaultStarterSpecies.includes(pokemon.speciesId)) ||
|
||||||
|
(this.value === 2 && getEggTierForSpecies(pokemon) >= EggTier.EPIC)
|
||||||
|
) {
|
||||||
valid.value = false;
|
valid.value = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -695,15 +700,12 @@ export class FreshStartChallenge extends Challenge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyStarterCost(species: SpeciesId, cost: NumberHolder): boolean {
|
applyStarterCost(species: SpeciesId, cost: NumberHolder): boolean {
|
||||||
if (defaultStarterSpecies.includes(species)) {
|
cost.value = speciesStarterCosts[species];
|
||||||
cost.value = speciesStarterCosts[species];
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStarterModify(pokemon: Pokemon): boolean {
|
applyStarterModify(pokemon: Pokemon): boolean {
|
||||||
pokemon.abilityIndex = 0; // Always base ability, not hidden ability
|
pokemon.abilityIndex = pokemon.abilityIndex % 2; // Always base ability, if you set it to hidden it wraps to first ability
|
||||||
pokemon.passive = false; // Passive isn't unlocked
|
pokemon.passive = false; // Passive isn't unlocked
|
||||||
pokemon.nature = Nature.HARDY; // Neutral nature
|
pokemon.nature = Nature.HARDY; // Neutral nature
|
||||||
pokemon.moveset = pokemon.species
|
pokemon.moveset = pokemon.species
|
||||||
@ -715,7 +717,22 @@ export class FreshStartChallenge extends Challenge {
|
|||||||
pokemon.luck = 0; // No luck
|
pokemon.luck = 0; // No luck
|
||||||
pokemon.shiny = false; // Not shiny
|
pokemon.shiny = false; // Not shiny
|
||||||
pokemon.variant = 0; // Not shiny
|
pokemon.variant = 0; // Not shiny
|
||||||
pokemon.formIndex = 0; // Froakie should be base form
|
if (pokemon.species.speciesId === SpeciesId.ZYGARDE && pokemon.formIndex >= 2) {
|
||||||
|
pokemon.formIndex -= 2; // Sets 10%-PC to 10%-AB and 50%-PC to 50%-AB
|
||||||
|
} else if (
|
||||||
|
pokemon.formIndex > 0 &&
|
||||||
|
[
|
||||||
|
SpeciesId.PIKACHU,
|
||||||
|
SpeciesId.EEVEE,
|
||||||
|
SpeciesId.PICHU,
|
||||||
|
SpeciesId.ROTOM,
|
||||||
|
SpeciesId.MELOETTA,
|
||||||
|
SpeciesId.FROAKIE,
|
||||||
|
SpeciesId.ROCKRUFF,
|
||||||
|
].includes(pokemon.species.speciesId)
|
||||||
|
) {
|
||||||
|
pokemon.formIndex = 0; // These mons are set to form 0 because they're meant to be unlocks or mid-run form changes
|
||||||
|
}
|
||||||
pokemon.ivs = [15, 15, 15, 15, 15, 15]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0)
|
pokemon.ivs = [15, 15, 15, 15, 15, 15]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0)
|
||||||
pokemon.teraType = pokemon.species.type1; // Always primary tera type
|
pokemon.teraType = pokemon.species.type1; // Always primary tera type
|
||||||
return true;
|
return true;
|
||||||
|
@ -213,7 +213,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* TODO: Stop treating this like a unique ID and stop treating 0 as no pokemon
|
* TODO: Stop treating this like a unique ID and stop treating 0 as no pokemon
|
||||||
*/
|
*/
|
||||||
public id: number;
|
public id: number;
|
||||||
public name: string;
|
|
||||||
public nickname: string;
|
public nickname: string;
|
||||||
public species: PokemonSpecies;
|
public species: PokemonSpecies;
|
||||||
public formIndex: number;
|
public formIndex: number;
|
||||||
@ -5664,7 +5663,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PlayerPokemon extends Pokemon {
|
export class PlayerPokemon extends Pokemon {
|
||||||
protected battleInfo: PlayerBattleInfo;
|
protected declare battleInfo: PlayerBattleInfo;
|
||||||
public compatibleTms: MoveId[];
|
public compatibleTms: MoveId[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -6193,7 +6192,7 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class EnemyPokemon extends Pokemon {
|
export class EnemyPokemon extends Pokemon {
|
||||||
protected battleInfo: EnemyBattleInfo;
|
protected declare battleInfo: EnemyBattleInfo;
|
||||||
public trainerSlot: TrainerSlot;
|
public trainerSlot: TrainerSlot;
|
||||||
public aiType: AiType;
|
public aiType: AiType;
|
||||||
public bossSegments: number;
|
public bossSegments: number;
|
||||||
|
@ -30,7 +30,6 @@ export class Trainer extends Phaser.GameObjects.Container {
|
|||||||
public config: TrainerConfig;
|
public config: TrainerConfig;
|
||||||
public variant: TrainerVariant;
|
public variant: TrainerVariant;
|
||||||
public partyTemplateIndex: number;
|
public partyTemplateIndex: number;
|
||||||
public name: string;
|
|
||||||
public partnerName: string;
|
public partnerName: string;
|
||||||
public nameKey: string;
|
public nameKey: string;
|
||||||
public partnerNameKey: string | undefined;
|
public partnerNameKey: string | undefined;
|
||||||
|
@ -462,7 +462,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier {
|
|||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
*/
|
*/
|
||||||
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
|
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
|
||||||
public override type: DoubleBattleChanceBoosterModifierType;
|
public declare type: DoubleBattleChanceBoosterModifierType;
|
||||||
|
|
||||||
match(modifier: Modifier): boolean {
|
match(modifier: Modifier): boolean {
|
||||||
return modifier instanceof DoubleBattleChanceBoosterModifier && modifier.getMaxBattles() === this.getMaxBattles();
|
return modifier instanceof DoubleBattleChanceBoosterModifier && modifier.getMaxBattles() === this.getMaxBattles();
|
||||||
@ -936,7 +936,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
|
|||||||
* Currently used by Shuckle Juice item
|
* Currently used by Shuckle Juice item
|
||||||
*/
|
*/
|
||||||
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||||
public override type: PokemonBaseStatTotalModifierType;
|
public declare type: PokemonBaseStatTotalModifierType;
|
||||||
public isTransferable = false;
|
public isTransferable = false;
|
||||||
public statModifier: 10 | -15;
|
public statModifier: 10 | -15;
|
||||||
|
|
||||||
@ -2074,7 +2074,7 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TerrastalizeModifier extends ConsumablePokemonModifier {
|
export class TerrastalizeModifier extends ConsumablePokemonModifier {
|
||||||
public override type: TerastallizeModifierType;
|
public declare type: TerastallizeModifierType;
|
||||||
public teraType: PokemonType;
|
public teraType: PokemonType;
|
||||||
|
|
||||||
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: PokemonType) {
|
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: PokemonType) {
|
||||||
@ -2318,7 +2318,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TmModifier extends ConsumablePokemonModifier {
|
export class TmModifier extends ConsumablePokemonModifier {
|
||||||
public override type: TmModifierType;
|
public declare type: TmModifierType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies {@linkcode TmModifier}
|
* Applies {@linkcode TmModifier}
|
||||||
@ -2365,7 +2365,7 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class EvolutionItemModifier extends ConsumablePokemonModifier {
|
export class EvolutionItemModifier extends ConsumablePokemonModifier {
|
||||||
public override type: EvolutionItemModifierType;
|
public declare type: EvolutionItemModifierType;
|
||||||
/**
|
/**
|
||||||
* Applies {@linkcode EvolutionItemModifier}
|
* Applies {@linkcode EvolutionItemModifier}
|
||||||
* @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item
|
* @param playerPokemon The {@linkcode PlayerPokemon} that should evolve via item
|
||||||
@ -2530,7 +2530,7 @@ export class ExpBoosterModifier extends PersistentModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonExpBoosterModifier extends PokemonHeldItemModifier {
|
export class PokemonExpBoosterModifier extends PokemonHeldItemModifier {
|
||||||
public override type: PokemonExpBoosterModifierType;
|
public declare type: PokemonExpBoosterModifierType;
|
||||||
|
|
||||||
private boostMultiplier: number;
|
private boostMultiplier: number;
|
||||||
|
|
||||||
@ -2627,7 +2627,7 @@ export class ExpBalanceModifier extends PersistentModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier {
|
export class PokemonFriendshipBoosterModifier extends PokemonHeldItemModifier {
|
||||||
public override type: PokemonFriendshipBoosterModifierType;
|
public declare type: PokemonFriendshipBoosterModifierType;
|
||||||
|
|
||||||
matchType(modifier: Modifier): boolean {
|
matchType(modifier: Modifier): boolean {
|
||||||
return modifier instanceof PokemonFriendshipBoosterModifier;
|
return modifier instanceof PokemonFriendshipBoosterModifier;
|
||||||
@ -2684,7 +2684,7 @@ export class PokemonNatureWeightModifier extends PokemonHeldItemModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier {
|
export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier {
|
||||||
public override type: PokemonMoveAccuracyBoosterModifierType;
|
public declare type: PokemonMoveAccuracyBoosterModifierType;
|
||||||
private accuracyAmount: number;
|
private accuracyAmount: number;
|
||||||
|
|
||||||
constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) {
|
constructor(type: PokemonMoveAccuracyBoosterModifierType, pokemonId: number, accuracy: number, stackCount?: number) {
|
||||||
@ -2736,7 +2736,7 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
||||||
public override type: PokemonMultiHitModifierType;
|
public declare type: PokemonMultiHitModifierType;
|
||||||
|
|
||||||
matchType(modifier: Modifier): boolean {
|
matchType(modifier: Modifier): boolean {
|
||||||
return modifier instanceof PokemonMultiHitModifier;
|
return modifier instanceof PokemonMultiHitModifier;
|
||||||
@ -2817,7 +2817,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier {
|
export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier {
|
||||||
public override type: FormChangeItemModifierType;
|
public declare type: FormChangeItemModifierType;
|
||||||
public formChangeItem: FormChangeItem;
|
public formChangeItem: FormChangeItem;
|
||||||
public active: boolean;
|
public active: boolean;
|
||||||
public isTransferable = false;
|
public isTransferable = false;
|
||||||
|
@ -2,7 +2,6 @@ import type { TurnCommand } from "#app/battle";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { speciesStarterCosts } from "#balance/starters";
|
import { speciesStarterCosts } from "#balance/starters";
|
||||||
import type { EncoreTag } from "#data/battler-tags";
|
|
||||||
import { TrappedTag } from "#data/battler-tags";
|
import { TrappedTag } from "#data/battler-tags";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
@ -22,59 +21,77 @@ import type { MoveTargetSet } from "#moves/move";
|
|||||||
import { getMoveTargets } from "#moves/move-utils";
|
import { getMoveTargets } from "#moves/move-utils";
|
||||||
import { FieldPhase } from "#phases/field-phase";
|
import { FieldPhase } from "#phases/field-phase";
|
||||||
import type { TurnMove } from "#types/turn-move";
|
import type { TurnMove } from "#types/turn-move";
|
||||||
import { isNullOrUndefined } from "#utils/common";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
|
||||||
export class CommandPhase extends FieldPhase {
|
export class CommandPhase extends FieldPhase {
|
||||||
public readonly phaseName = "CommandPhase";
|
public readonly phaseName = "CommandPhase";
|
||||||
protected fieldIndex: number;
|
protected fieldIndex: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the command phase is handling a switch command
|
||||||
|
*/
|
||||||
|
private isSwitch = false;
|
||||||
|
|
||||||
constructor(fieldIndex: number) {
|
constructor(fieldIndex: number) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.fieldIndex = fieldIndex;
|
this.fieldIndex = fieldIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
/**
|
||||||
super.start();
|
* Resets the cursor to the position of {@linkcode Command.FIGHT} if any of the following are true
|
||||||
|
* - The setting to remember the last action is not enabled
|
||||||
globalScene.updateGameInfo();
|
* - This is the first turn of a mystery encounter, trainer battle, or the END biome
|
||||||
|
* - The cursor is currently on the POKEMON command
|
||||||
|
*/
|
||||||
|
private resetCursorIfNeeded(): void {
|
||||||
const commandUiHandler = globalScene.ui.handlers[UiMode.COMMAND];
|
const commandUiHandler = globalScene.ui.handlers[UiMode.COMMAND];
|
||||||
|
const { arena, commandCursorMemory, currentBattle } = globalScene;
|
||||||
|
const { battleType, turn } = currentBattle;
|
||||||
|
const { biomeType } = arena;
|
||||||
|
|
||||||
// If one of these conditions is true, we always reset the cursor to Command.FIGHT
|
// If one of these conditions is true, we always reset the cursor to Command.FIGHT
|
||||||
const cursorResetEvent =
|
const cursorResetEvent =
|
||||||
globalScene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER ||
|
battleType === BattleType.MYSTERY_ENCOUNTER || battleType === BattleType.TRAINER || biomeType === BiomeId.END;
|
||||||
globalScene.currentBattle.battleType === BattleType.TRAINER ||
|
|
||||||
globalScene.arena.biomeType === BiomeId.END;
|
|
||||||
|
|
||||||
if (commandUiHandler) {
|
if (!commandUiHandler) {
|
||||||
if (
|
return;
|
||||||
(globalScene.currentBattle.turn === 1 && (!globalScene.commandCursorMemory || cursorResetEvent)) ||
|
}
|
||||||
commandUiHandler.getCursor() === Command.POKEMON
|
if (
|
||||||
) {
|
(turn === 1 && (!commandCursorMemory || cursorResetEvent)) ||
|
||||||
commandUiHandler.setCursor(Command.FIGHT);
|
commandUiHandler.getCursor() === Command.POKEMON
|
||||||
} else {
|
) {
|
||||||
commandUiHandler.setCursor(commandUiHandler.getCursor());
|
commandUiHandler.setCursor(Command.FIGHT);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submethod of {@linkcode start} that validates field index logic for nonzero field indices.
|
||||||
|
* Must only be called if the field index is nonzero.
|
||||||
|
*/
|
||||||
|
private handleFieldIndexLogic(): void {
|
||||||
|
// If we somehow are attempting to check the right pokemon but there's only one pokemon out
|
||||||
|
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
|
||||||
|
// TODO: Prevent this from happening in the first place
|
||||||
|
if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) {
|
||||||
|
this.fieldIndex = FieldPosition.CENTER;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fieldIndex) {
|
const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1];
|
||||||
// If we somehow are attempting to check the right pokemon but there's only one pokemon out
|
if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) {
|
||||||
// Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching
|
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||||
if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) {
|
command: allyCommand?.command,
|
||||||
this.fieldIndex = FieldPosition.CENTER;
|
skip: true,
|
||||||
} else {
|
};
|
||||||
const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1];
|
|
||||||
if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) {
|
|
||||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
|
||||||
command: allyCommand?.command,
|
|
||||||
skip: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submethod of {@linkcode start} that sets the turn command to skip if this pokemon
|
||||||
|
* is commanding its ally via {@linkcode AbilityId.COMMANDER}.
|
||||||
|
*/
|
||||||
|
private checkCommander(): void {
|
||||||
// If the Pokemon has applied Commander's effects to its ally, skip this command
|
// If the Pokemon has applied Commander's effects to its ally, skip this command
|
||||||
if (
|
if (
|
||||||
globalScene.currentBattle?.double &&
|
globalScene.currentBattle?.double &&
|
||||||
@ -86,377 +103,521 @@ export class CommandPhase extends FieldPhase {
|
|||||||
skip: true,
|
skip: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP.
|
/**
|
||||||
const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag | undefined;
|
* Clear out all unusable moves in front of the currently acting pokemon's move queue.
|
||||||
if (encoreTag) {
|
*/
|
||||||
this.getPokemon().lapseTag(BattlerTagType.ENCORE);
|
// TODO: Refactor move queue handling to ensure that this method is not necessary.
|
||||||
}
|
private clearUnusuableMoves(): void {
|
||||||
|
const playerPokemon = this.getPokemon();
|
||||||
if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) {
|
|
||||||
return this.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
|
||||||
|
|
||||||
const moveQueue = playerPokemon.getMoveQueue();
|
const moveQueue = playerPokemon.getMoveQueue();
|
||||||
|
if (moveQueue.length === 0) {
|
||||||
while (
|
return;
|
||||||
moveQueue.length &&
|
|
||||||
moveQueue[0] &&
|
|
||||||
moveQueue[0].move &&
|
|
||||||
!isVirtual(moveQueue[0].useMode) &&
|
|
||||||
(!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) ||
|
|
||||||
!playerPokemon
|
|
||||||
.getMoveset()
|
|
||||||
[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(
|
|
||||||
playerPokemon,
|
|
||||||
isIgnorePP(moveQueue[0].useMode),
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
moveQueue.shift();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor this. I did a few simple find/replace matches but this is just ABHORRENTLY structured
|
let entriesToDelete = 0;
|
||||||
if (moveQueue.length > 0) {
|
const moveset = playerPokemon.getMoveset();
|
||||||
const queuedMove = moveQueue[0];
|
for (const queuedMove of moveQueue) {
|
||||||
if (!queuedMove.move) {
|
const movesetQueuedMove = moveset.find(m => m.moveId === queuedMove.move);
|
||||||
this.handleCommand(Command.FIGHT, -1, MoveUseMode.NORMAL);
|
|
||||||
} else {
|
|
||||||
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
|
||||||
if (
|
|
||||||
(moveIndex > -1 &&
|
|
||||||
playerPokemon.getMoveset()[moveIndex].isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))) ||
|
|
||||||
isVirtual(queuedMove.useMode)
|
|
||||||
) {
|
|
||||||
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove);
|
|
||||||
} else {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (
|
if (
|
||||||
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
queuedMove.move !== MoveId.NONE &&
|
||||||
globalScene.currentBattle.mysteryEncounter?.skipToFightInput
|
!isVirtual(queuedMove.useMode) &&
|
||||||
|
!movesetQueuedMove?.isUsable(playerPokemon, isIgnorePP(queuedMove.useMode))
|
||||||
) {
|
) {
|
||||||
globalScene.ui.clearText();
|
entriesToDelete++;
|
||||||
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
|
||||||
} else {
|
} else {
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (entriesToDelete) {
|
||||||
|
moveQueue.splice(0, entriesToDelete);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Remove `args` and clean this thing up
|
* Attempt to execute the first usable move in this Pokemon's move queue
|
||||||
* Code will need to be copied over from pkty except replacing the `virtual` and `ignorePP` args with a corresponding `MoveUseMode`.
|
* @returns Whether a queued move was successfully set to be executed.
|
||||||
*/
|
*/
|
||||||
handleCommand(command: Command, cursor: number, ...args: any[]): boolean {
|
private tryExecuteQueuedMove(): boolean {
|
||||||
|
this.clearUnusuableMoves();
|
||||||
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
const playerPokemon = globalScene.getPlayerField()[this.fieldIndex];
|
||||||
|
const moveQueue = playerPokemon.getMoveQueue();
|
||||||
|
|
||||||
|
if (moveQueue.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queuedMove = moveQueue[0];
|
||||||
|
if (queuedMove.move === MoveId.NONE) {
|
||||||
|
this.handleCommand(Command.FIGHT, -1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const moveIndex = playerPokemon.getMoveset().findIndex(m => m.moveId === queuedMove.move);
|
||||||
|
if (!isVirtual(queuedMove.useMode) && moveIndex === -1) {
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
} else {
|
||||||
|
this.handleCommand(Command.FIGHT, moveIndex, queuedMove.useMode, queuedMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override start(): void {
|
||||||
|
super.start();
|
||||||
|
|
||||||
|
globalScene.updateGameInfo();
|
||||||
|
this.resetCursorIfNeeded();
|
||||||
|
|
||||||
|
if (this.fieldIndex) {
|
||||||
|
this.handleFieldIndexLogic();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkCommander();
|
||||||
|
|
||||||
|
const playerPokemon = this.getPokemon();
|
||||||
|
|
||||||
|
// Note: It is OK to call this if the target is not under the effect of encore; it will simply do nothing.
|
||||||
|
playerPokemon.lapseTag(BattlerTagType.ENCORE);
|
||||||
|
|
||||||
|
if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) {
|
||||||
|
this.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tryExecuteQueuedMove()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
||||||
|
globalScene.currentBattle.mysteryEncounter?.skipToFightInput
|
||||||
|
) {
|
||||||
|
globalScene.ui.clearText();
|
||||||
|
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
||||||
|
} else {
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submethod of {@linkcode handleFightCommand} responsible for queuing the appropriate
|
||||||
|
* error message when a move cannot be used.
|
||||||
|
* @param user - The pokemon using the move
|
||||||
|
* @param cursor - The index of the move in the moveset
|
||||||
|
*/
|
||||||
|
private queueFightErrorMessage(user: PlayerPokemon, cursor: number) {
|
||||||
|
const move = user.getMoveset()[cursor];
|
||||||
|
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||||
|
|
||||||
|
// Decides between a Disabled, Not Implemented, or No PP translation message
|
||||||
|
const errorMessage = user.isMoveRestricted(move.moveId, user)
|
||||||
|
? user.getRestrictingTag(move.moveId, user)!.selectionDeniedText(user, move.moveId)
|
||||||
|
: move.getName().endsWith(" (N)")
|
||||||
|
? "battle:moveNotImplemented"
|
||||||
|
: "battle:moveNoPP";
|
||||||
|
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
|
||||||
|
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t(errorMessage, { moveName: moveName }),
|
||||||
|
null,
|
||||||
|
() => {
|
||||||
|
globalScene.ui.clearText();
|
||||||
|
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for {@linkcode handleFightCommand} that returns the moveID for the phase
|
||||||
|
* based on the move passed in or the cursor.
|
||||||
|
*
|
||||||
|
* Does not check if the move is usable or not, that should be handled by the caller.
|
||||||
|
*/
|
||||||
|
private computeMoveId(playerPokemon: PlayerPokemon, cursor: number, move: TurnMove | undefined): MoveId {
|
||||||
|
return move?.move ?? (cursor > -1 ? playerPokemon.getMoveset()[cursor]?.moveId : MoveId.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the logic for executing a fight-related command
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* - Validates whether the move can be used, using struggle if not
|
||||||
|
* - Constructs the turn command and inserts it into the battle's turn commands
|
||||||
|
*
|
||||||
|
* @param command - The command to handle (FIGHT or TERA)
|
||||||
|
* @param cursor - The index that the cursor is placed on, or -1 if no move can be selected.
|
||||||
|
* @param ignorePP - Whether to ignore PP when checking if the move can be used.
|
||||||
|
* @param move - The move to force the command to use, if any.
|
||||||
|
*/
|
||||||
|
private handleFightCommand(
|
||||||
|
command: Command.FIGHT | Command.TERA,
|
||||||
|
cursor: number,
|
||||||
|
useMode: MoveUseMode = MoveUseMode.NORMAL,
|
||||||
|
move?: TurnMove,
|
||||||
|
): boolean {
|
||||||
|
const playerPokemon = this.getPokemon();
|
||||||
|
const ignorePP = isIgnorePP(useMode);
|
||||||
|
|
||||||
|
let canUse = cursor === -1 || playerPokemon.trySelectMove(cursor, ignorePP);
|
||||||
|
|
||||||
|
// Ternary here ensures we don't compute struggle conditions unless necessary
|
||||||
|
const useStruggle = canUse
|
||||||
|
? false
|
||||||
|
: cursor > -1 && !playerPokemon.getMoveset().some(m => m.isUsable(playerPokemon));
|
||||||
|
|
||||||
|
canUse ||= useStruggle;
|
||||||
|
|
||||||
|
if (!canUse) {
|
||||||
|
this.queueFightErrorMessage(playerPokemon, cursor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveId = useStruggle ? MoveId.STRUGGLE : this.computeMoveId(playerPokemon, cursor, move);
|
||||||
|
|
||||||
|
const turnCommand: TurnCommand = {
|
||||||
|
command: Command.FIGHT,
|
||||||
|
cursor,
|
||||||
|
move: { move: moveId, targets: [], useMode },
|
||||||
|
args: [useMode, move],
|
||||||
|
};
|
||||||
|
const preTurnCommand: TurnCommand = {
|
||||||
|
command,
|
||||||
|
targets: [this.fieldIndex],
|
||||||
|
skip: command === Command.FIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveTargets: MoveTargetSet =
|
||||||
|
move === undefined
|
||||||
|
? getMoveTargets(playerPokemon, moveId)
|
||||||
|
: {
|
||||||
|
targets: move.targets,
|
||||||
|
multiple: move.targets.length > 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (moveId === MoveId.NONE) {
|
||||||
|
turnCommand.targets = [this.fieldIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Move:",
|
||||||
|
MoveId[moveId],
|
||||||
|
"Move targets:",
|
||||||
|
moveTargets,
|
||||||
|
"\nPlayer Pokemon:",
|
||||||
|
getPokemonNameWithAffix(playerPokemon),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moveTargets.targets.length > 1 && moveTargets.multiple) {
|
||||||
|
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) {
|
||||||
|
turnCommand.move.targets = moveTargets.targets;
|
||||||
|
} else if (
|
||||||
|
turnCommand.move &&
|
||||||
|
playerPokemon.getTag(BattlerTagType.CHARGING) &&
|
||||||
|
playerPokemon.getMoveQueue().length >= 1
|
||||||
|
) {
|
||||||
|
turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets;
|
||||||
|
} else {
|
||||||
|
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
|
||||||
|
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the mode in preparation to show the text, and then show the text.
|
||||||
|
* Only works for parameterless i18next keys.
|
||||||
|
* @param key - The i18next key for the text to show
|
||||||
|
*/
|
||||||
|
private queueShowText(key: string): void {
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||||
|
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t(key),
|
||||||
|
null,
|
||||||
|
() => {
|
||||||
|
globalScene.ui.showText("", 0);
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for {@linkcode handleBallCommand} that checks if a pokeball can be thrown
|
||||||
|
* and displays the appropriate error message.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* The pokeball may not be thrown if any of the following are true:
|
||||||
|
* - It is a trainer battle
|
||||||
|
* - The player is in the {@linkcode BiomeId.END | End} biome and
|
||||||
|
* - it is not classic mode; or
|
||||||
|
* - the fresh start challenge is active; or
|
||||||
|
* - the player has not caught the target before and the player is still missing more than one starter
|
||||||
|
* - The player is in a mystery encounter that disallows catching the pokemon
|
||||||
|
* @returns Whether a pokeball can be thrown
|
||||||
|
*/
|
||||||
|
private checkCanUseBall(): boolean {
|
||||||
|
const { arena, currentBattle, gameData, gameMode } = globalScene;
|
||||||
|
const { battleType } = currentBattle;
|
||||||
|
const { biomeType } = arena;
|
||||||
|
const { isClassic } = gameMode;
|
||||||
|
const { dexData } = gameData;
|
||||||
|
|
||||||
|
const someUncaughtSpeciesOnField = globalScene
|
||||||
|
.getEnemyField()
|
||||||
|
.some(p => p.isActive() && !dexData[p.species.speciesId].caughtAttr);
|
||||||
|
const missingMultipleStarters =
|
||||||
|
gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1;
|
||||||
|
if (
|
||||||
|
biomeType === BiomeId.END &&
|
||||||
|
(!isClassic || gameMode.isFreshStartChallenge() || (someUncaughtSpeciesOnField && missingMultipleStarters))
|
||||||
|
) {
|
||||||
|
this.queueShowText("battle:noPokeballForce");
|
||||||
|
} else if (battleType === BattleType.TRAINER) {
|
||||||
|
this.queueShowText("battle:noPokeballTrainer");
|
||||||
|
} else if (currentBattle.isBattleMysteryEncounter() && !currentBattle.mysteryEncounter!.catchAllowed) {
|
||||||
|
this.queueShowText("battle:noPokeballMysteryEncounter");
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for {@linkcode handleCommand} that handles the logic when the selected command is to use a pokeball.
|
||||||
|
*
|
||||||
|
* @param cursor - The index of the pokeball to use
|
||||||
|
* @returns Whether the command was successfully initiated
|
||||||
|
*/
|
||||||
|
private handleBallCommand(cursor: number): boolean {
|
||||||
|
const targets = globalScene
|
||||||
|
.getEnemyField()
|
||||||
|
.filter(p => p.isActive(true))
|
||||||
|
.map(p => p.getBattlerIndex());
|
||||||
|
if (targets.length > 1) {
|
||||||
|
this.queueShowText("battle:noPokeballMulti");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.checkCanUseBall()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numBallTypes = 5;
|
||||||
|
if (cursor < numBallTypes) {
|
||||||
|
const targetPokemon = globalScene.getEnemyPokemon();
|
||||||
|
if (
|
||||||
|
targetPokemon?.isBoss() &&
|
||||||
|
targetPokemon?.bossSegmentIndex >= 1 &&
|
||||||
|
// TODO: Decouple this hardcoded exception for wonder guard and just check the target...
|
||||||
|
!targetPokemon?.hasAbility(AbilityId.WONDER_GUARD, false, true) &&
|
||||||
|
cursor < PokeballType.MASTER_BALL
|
||||||
|
) {
|
||||||
|
this.queueShowText("battle:noPokeballStrong");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
||||||
|
command: Command.BALL,
|
||||||
|
cursor: cursor,
|
||||||
|
};
|
||||||
|
globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets;
|
||||||
|
if (this.fieldIndex) {
|
||||||
|
globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submethod of {@linkcode tryLeaveField} to handle the logic for effects that prevent the pokemon from leaving the field
|
||||||
|
* due to trapping abilities or effects.
|
||||||
|
*
|
||||||
|
* This method queues the proper messages in the case of trapping abilities or effects.
|
||||||
|
*
|
||||||
|
* @returns Whether the pokemon is currently trapped
|
||||||
|
*/
|
||||||
|
private handleTrap(): boolean {
|
||||||
|
const playerPokemon = this.getPokemon();
|
||||||
|
const trappedAbMessages: string[] = [];
|
||||||
|
const isSwitch = this.isSwitch;
|
||||||
|
if (!playerPokemon.isTrapped(trappedAbMessages)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (trappedAbMessages.length > 0) {
|
||||||
|
if (isSwitch) {
|
||||||
|
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||||
|
}
|
||||||
|
globalScene.ui.showText(
|
||||||
|
trappedAbMessages[0],
|
||||||
|
null,
|
||||||
|
() => {
|
||||||
|
globalScene.ui.showText("", 0);
|
||||||
|
if (isSwitch) {
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const trapTag = playerPokemon.getTag(TrappedTag);
|
||||||
|
const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER);
|
||||||
|
|
||||||
|
if (!isSwitch) {
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
globalScene.ui.setMode(UiMode.MESSAGE);
|
||||||
|
}
|
||||||
|
if (trapTag) {
|
||||||
|
this.showNoEscapeText(trapTag, false);
|
||||||
|
} else if (fairyLockTag) {
|
||||||
|
this.showNoEscapeText(fairyLockTag, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common helper method that attempts to have the pokemon leave the field.
|
||||||
|
* Checks for trapping abilities and effects.
|
||||||
|
*
|
||||||
|
* @param cursor - The index of the option that the cursor is on
|
||||||
|
* @returns Whether the pokemon is able to leave the field, indicating the command phase should end
|
||||||
|
*/
|
||||||
|
private tryLeaveField(cursor?: number, isBatonSwitch = false): boolean {
|
||||||
|
const currentBattle = globalScene.currentBattle;
|
||||||
|
|
||||||
|
if (isBatonSwitch || !this.handleTrap()) {
|
||||||
|
currentBattle.turnCommands[this.fieldIndex] = this.isSwitch
|
||||||
|
? {
|
||||||
|
command: Command.POKEMON,
|
||||||
|
cursor,
|
||||||
|
args: [isBatonSwitch],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
command: Command.RUN,
|
||||||
|
};
|
||||||
|
if (!this.isSwitch && this.fieldIndex) {
|
||||||
|
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for {@linkcode handleCommand} that handles the logic when the selected command is RUN.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Checks if the player is allowed to flee, and if not, queues the appropriate message.
|
||||||
|
*
|
||||||
|
* The player cannot flee if:
|
||||||
|
* - The player is in the {@linkcode BiomeId.END | End} biome
|
||||||
|
* - The player is in a trainer battle
|
||||||
|
* - The player is in a mystery encounter that disallows fleeing
|
||||||
|
* - The player's pokemon is trapped by an ability or effect
|
||||||
|
* @returns Whether the pokemon is able to leave the field, indicating the command phase should end
|
||||||
|
*/
|
||||||
|
private handleRunCommand(): boolean {
|
||||||
|
const { currentBattle, arena } = globalScene;
|
||||||
|
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed ?? true;
|
||||||
|
if (arena.biomeType === BiomeId.END || !mysteryEncounterFleeAllowed) {
|
||||||
|
this.queueShowText("battle:noEscapeForce");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
currentBattle.battleType === BattleType.TRAINER ||
|
||||||
|
currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE
|
||||||
|
) {
|
||||||
|
this.queueShowText("battle:noEscapeTrainer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = this.tryLeaveField();
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a message indicating that the pokemon cannot escape, and then return to the command phase.
|
||||||
|
*/
|
||||||
|
private showNoEscapeText(tag: any, isSwitch: boolean): void {
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t("battle:noEscapePokemon", {
|
||||||
|
pokemonName:
|
||||||
|
tag.sourceId && globalScene.getPokemonById(tag.sourceId)
|
||||||
|
? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!)
|
||||||
|
: "",
|
||||||
|
moveName: tag.getMoveName(),
|
||||||
|
escapeVerb: i18next.t(isSwitch ? "battle:escapeVerbSwitch" : "battle:escapeVerbFlee"),
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
() => {
|
||||||
|
globalScene.ui.showText("", 0);
|
||||||
|
if (!isSwitch) {
|
||||||
|
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overloads for handleCommand to provide a more specific signature for the different options
|
||||||
|
/**
|
||||||
|
* Process the command phase logic based on the selected command
|
||||||
|
*
|
||||||
|
* @param command - The kind of command to handle
|
||||||
|
* @param cursor - The index of option that the cursor is on, or -1 if no option is selected
|
||||||
|
* @param useMode - The mode to use for the move, if applicable. For switches, a boolean that specifies whether the switch is a Baton switch.
|
||||||
|
* @param move - For {@linkcode Command.FIGHT}, the move to use
|
||||||
|
* @returns Whether the command was successful
|
||||||
|
*/
|
||||||
|
handleCommand(command: Command.FIGHT | Command.TERA, cursor: number, useMode?: MoveUseMode, move?: TurnMove): boolean;
|
||||||
|
handleCommand(command: Command.BALL, cursor: number): boolean;
|
||||||
|
handleCommand(command: Command.POKEMON, cursor: number, useBaton: boolean): boolean;
|
||||||
|
handleCommand(command: Command.RUN, cursor: number): boolean;
|
||||||
|
handleCommand(command: Command, cursor: number, useMode?: boolean | MoveUseMode, move?: TurnMove): boolean;
|
||||||
|
|
||||||
|
public handleCommand(
|
||||||
|
command: Command,
|
||||||
|
cursor: number,
|
||||||
|
useMode: boolean | MoveUseMode = false,
|
||||||
|
move?: TurnMove,
|
||||||
|
): boolean {
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
// TODO: We don't need 2 args for this - moveUseMode is carried over from queuedMove
|
|
||||||
case Command.TERA:
|
case Command.TERA:
|
||||||
case Command.FIGHT: {
|
case Command.FIGHT:
|
||||||
let useStruggle = false;
|
success = this.handleFightCommand(command, cursor, typeof useMode === "boolean" ? undefined : useMode, move);
|
||||||
const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined;
|
|
||||||
if (
|
|
||||||
cursor === -1 ||
|
|
||||||
playerPokemon.trySelectMove(cursor, isIgnorePP(args[0] as MoveUseMode)) ||
|
|
||||||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)
|
|
||||||
) {
|
|
||||||
let moveId: MoveId;
|
|
||||||
if (useStruggle) {
|
|
||||||
moveId = MoveId.STRUGGLE;
|
|
||||||
} else if (turnMove !== undefined) {
|
|
||||||
moveId = turnMove.move;
|
|
||||||
} else if (cursor > -1) {
|
|
||||||
moveId = playerPokemon.getMoveset()[cursor].moveId;
|
|
||||||
} else {
|
|
||||||
moveId = MoveId.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const turnCommand: TurnCommand = {
|
|
||||||
command: Command.FIGHT,
|
|
||||||
cursor: cursor,
|
|
||||||
move: { move: moveId, targets: [], useMode: args[0] },
|
|
||||||
args: args,
|
|
||||||
};
|
|
||||||
const preTurnCommand: TurnCommand = {
|
|
||||||
command: command,
|
|
||||||
targets: [this.fieldIndex],
|
|
||||||
skip: command === Command.FIGHT,
|
|
||||||
};
|
|
||||||
const moveTargets: MoveTargetSet =
|
|
||||||
turnMove === undefined
|
|
||||||
? getMoveTargets(playerPokemon, moveId)
|
|
||||||
: {
|
|
||||||
targets: turnMove.targets,
|
|
||||||
multiple: turnMove.targets.length > 1,
|
|
||||||
};
|
|
||||||
if (!moveId) {
|
|
||||||
turnCommand.targets = [this.fieldIndex];
|
|
||||||
}
|
|
||||||
console.log(moveTargets, getPokemonNameWithAffix(playerPokemon));
|
|
||||||
if (moveTargets.targets.length > 1 && moveTargets.multiple) {
|
|
||||||
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
|
||||||
}
|
|
||||||
if (turnCommand.move && (moveTargets.targets.length <= 1 || moveTargets.multiple)) {
|
|
||||||
turnCommand.move.targets = moveTargets.targets;
|
|
||||||
} else if (
|
|
||||||
turnCommand.move &&
|
|
||||||
playerPokemon.getTag(BattlerTagType.CHARGING) &&
|
|
||||||
playerPokemon.getMoveQueue().length >= 1
|
|
||||||
) {
|
|
||||||
turnCommand.move.targets = playerPokemon.getMoveQueue()[0].targets;
|
|
||||||
} else {
|
|
||||||
globalScene.phaseManager.unshiftNew("SelectTargetPhase", this.fieldIndex);
|
|
||||||
}
|
|
||||||
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
|
|
||||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
|
|
||||||
success = true;
|
|
||||||
} else if (cursor < playerPokemon.getMoveset().length) {
|
|
||||||
const move = playerPokemon.getMoveset()[cursor];
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
|
|
||||||
// Decides between a Disabled, Not Implemented, or No PP translation message
|
|
||||||
const errorMessage = playerPokemon.isMoveRestricted(move.moveId, playerPokemon)
|
|
||||||
? playerPokemon
|
|
||||||
.getRestrictingTag(move.moveId, playerPokemon)!
|
|
||||||
.selectionDeniedText(playerPokemon, move.moveId)
|
|
||||||
: move.getName().endsWith(" (N)")
|
|
||||||
? "battle:moveNotImplemented"
|
|
||||||
: "battle:moveNoPP";
|
|
||||||
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
|
|
||||||
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t(errorMessage, { moveName: moveName }),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.clearText();
|
|
||||||
globalScene.ui.setMode(UiMode.FIGHT, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
case Command.BALL:
|
||||||
case Command.BALL: {
|
success = this.handleBallCommand(cursor);
|
||||||
const notInDex =
|
|
||||||
globalScene
|
|
||||||
.getEnemyField()
|
|
||||||
.filter(p => p.isActive(true))
|
|
||||||
.some(p => !globalScene.gameData.dexData[p.species.speciesId].caughtAttr) &&
|
|
||||||
globalScene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1;
|
|
||||||
if (
|
|
||||||
globalScene.arena.biomeType === BiomeId.END &&
|
|
||||||
(!globalScene.gameMode.isClassic || globalScene.gameMode.isFreshStartChallenge() || notInDex)
|
|
||||||
) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noPokeballForce"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else if (globalScene.currentBattle.battleType === BattleType.TRAINER) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noPokeballTrainer"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
globalScene.currentBattle.isBattleMysteryEncounter() &&
|
|
||||||
!globalScene.currentBattle.mysteryEncounter!.catchAllowed
|
|
||||||
) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noPokeballMysteryEncounter"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const targets = globalScene
|
|
||||||
.getEnemyField()
|
|
||||||
.filter(p => p.isActive(true))
|
|
||||||
.map(p => p.getBattlerIndex());
|
|
||||||
if (targets.length > 1) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noPokeballMulti"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else if (cursor < 5) {
|
|
||||||
const targetPokemon = globalScene.getEnemyField().find(p => p.isActive(true));
|
|
||||||
if (
|
|
||||||
targetPokemon?.isBoss() &&
|
|
||||||
targetPokemon?.bossSegmentIndex >= 1 &&
|
|
||||||
!targetPokemon?.hasAbility(AbilityId.WONDER_GUARD, false, true) &&
|
|
||||||
cursor < PokeballType.MASTER_BALL
|
|
||||||
) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noPokeballStrong"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
globalScene.currentBattle.turnCommands[this.fieldIndex] = {
|
|
||||||
command: Command.BALL,
|
|
||||||
cursor: cursor,
|
|
||||||
};
|
|
||||||
globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets;
|
|
||||||
if (this.fieldIndex) {
|
|
||||||
globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case Command.POKEMON:
|
case Command.POKEMON:
|
||||||
case Command.RUN: {
|
this.isSwitch = true;
|
||||||
const isSwitch = command === Command.POKEMON;
|
success = this.tryLeaveField(cursor, typeof useMode === "boolean" ? useMode : undefined);
|
||||||
const { currentBattle, arena } = globalScene;
|
this.isSwitch = false;
|
||||||
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
|
|
||||||
if (
|
|
||||||
!isSwitch &&
|
|
||||||
(arena.biomeType === BiomeId.END ||
|
|
||||||
(!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))
|
|
||||||
) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noEscapeForce"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
!isSwitch &&
|
|
||||||
(currentBattle.battleType === BattleType.TRAINER ||
|
|
||||||
currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)
|
|
||||||
) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noEscapeTrainer"),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const batonPass = isSwitch && (args[0] as boolean);
|
|
||||||
const trappedAbMessages: string[] = [];
|
|
||||||
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
|
|
||||||
currentBattle.turnCommands[this.fieldIndex] = isSwitch
|
|
||||||
? { command: Command.POKEMON, cursor: cursor, args: args }
|
|
||||||
: { command: Command.RUN };
|
|
||||||
success = true;
|
|
||||||
if (!isSwitch && this.fieldIndex) {
|
|
||||||
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
|
|
||||||
}
|
|
||||||
} else if (trappedAbMessages.length > 0) {
|
|
||||||
if (!isSwitch) {
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
}
|
|
||||||
globalScene.ui.showText(
|
|
||||||
trappedAbMessages[0],
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
if (!isSwitch) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const trapTag = playerPokemon.getTag(TrappedTag);
|
|
||||||
const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER);
|
|
||||||
|
|
||||||
if (!trapTag && !fairyLockTag) {
|
|
||||||
i18next.t(`battle:noEscape${isSwitch ? "Switch" : "Flee"}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!isSwitch) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE);
|
|
||||||
}
|
|
||||||
const showNoEscapeText = (tag: any) => {
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("battle:noEscapePokemon", {
|
|
||||||
pokemonName:
|
|
||||||
tag.sourceId && globalScene.getPokemonById(tag.sourceId)
|
|
||||||
? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!)
|
|
||||||
: "",
|
|
||||||
moveName: tag.getMoveName(),
|
|
||||||
escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee"),
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.showText("", 0);
|
|
||||||
if (!isSwitch) {
|
|
||||||
globalScene.ui.setMode(UiMode.COMMAND, this.fieldIndex);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (trapTag) {
|
|
||||||
showNoEscapeText(trapTag);
|
|
||||||
} else if (fairyLockTag) {
|
|
||||||
showNoEscapeText(fairyLockTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
case Command.RUN:
|
||||||
|
success = this.handleRunCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -890,7 +890,7 @@ export const achvs = {
|
|||||||
100,
|
100,
|
||||||
c =>
|
c =>
|
||||||
c instanceof FreshStartChallenge &&
|
c instanceof FreshStartChallenge &&
|
||||||
c.value > 0 &&
|
c.value === 1 &&
|
||||||
!globalScene.gameMode.challenges.some(
|
!globalScene.gameMode.challenges.some(
|
||||||
c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0,
|
c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0,
|
||||||
),
|
),
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2023",
|
||||||
"module": "ES2020",
|
"module": "ES2022",
|
||||||
// Modifying this option requires all values to be set manually because the defaults get overridden
|
// Modifying this option requires all values to be set manually because the defaults get overridden
|
||||||
// Values other than "ES2024.Promise" taken from https://github.com/microsoft/TypeScript/blob/main/src/lib/es2020.full.d.ts
|
// Values other than "ES2024.Promise" taken from https://github.com/microsoft/TypeScript/blob/main/src/lib/es2023.full.d.ts
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020",
|
"ES2023",
|
||||||
"ES2024.Promise",
|
"ES2024.Promise",
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.AsyncIterable",
|
"DOM.AsyncIterable",
|
||||||
|
Loading…
Reference in New Issue
Block a user