This commit is contained in:
Bertie690 2025-06-30 18:38:47 -05:00 committed by GitHub
commit 0336801b2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -12,22 +12,24 @@ import { BattlerIndex } from "#enums/battler-index";
import { TrickRoomTag } from "#app/data/arena-tag"; import { TrickRoomTag } from "#app/data/arena-tag";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { TurnCommand } from "#app/battle";
export class TurnStartPhase extends FieldPhase { export class TurnStartPhase extends FieldPhase {
public readonly phaseName = "TurnStartPhase"; public readonly phaseName = "TurnStartPhase";
/** /**
* This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array. * This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array.
* It also checks for Trick Room and reverses the array if it is present. * It also checks for Trick Room and reverses the array if it is present.
* @returns {@linkcode BattlerIndex[]} the battle indices of all pokemon on the field ordered by speed * @returns An array of {@linkcode BattlerIndex}es containing all on-field Pokemon sorted in speed order.
*/ */
getSpeedOrder(): BattlerIndex[] { getSpeedOrder(): BattlerIndex[] {
const playerField = globalScene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; const playerField = globalScene.getPlayerField().filter(p => p.isActive());
const enemyField = globalScene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; const enemyField = globalScene.getEnemyField().filter(p => p.isActive());
// We shuffle the list before sorting so speed ties produce random results // Shuffle the list before sorting so speed ties produce random results
let orderedTargets: Pokemon[] = playerField.concat(enemyField); // This is seeded with the current turn to prevent turn order varying
// We seed it with the current turn to prevent an inconsistency where it // based on how long since you last reloaded.
// was varying based on how long since you last reloaded let orderedTargets = (playerField as Pokemon[]).concat(enemyField);
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
orderedTargets = randSeedShuffle(orderedTargets); orderedTargets = randSeedShuffle(orderedTargets);
@ -36,25 +38,25 @@ export class TurnStartPhase extends FieldPhase {
globalScene.waveSeed, globalScene.waveSeed,
); );
// Next, a check for Trick Room is applied to determine sort order. // Check for Trick Room and reverse sort order if active.
// Notably, Pokerogue does NOT have the "outspeed trick room" glitch at >1809 spd.
const speedReversed = new BooleanHolder(false); const speedReversed = new BooleanHolder(false);
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed); globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
// Adjust the sort function based on whether Trick Room is active.
orderedTargets.sort((a: Pokemon, b: Pokemon) => { orderedTargets.sort((a: Pokemon, b: Pokemon) => {
const aSpeed = a?.getEffectiveStat(Stat.SPD) ?? 0; const aSpeed = a.getEffectiveStat(Stat.SPD);
const bSpeed = b?.getEffectiveStat(Stat.SPD) ?? 0; const bSpeed = b.getEffectiveStat(Stat.SPD);
return speedReversed.value ? aSpeed - bSpeed : bSpeed - aSpeed; return speedReversed.value ? aSpeed - bSpeed : bSpeed - aSpeed;
}); });
return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : BattlerIndex.PLAYER)); return orderedTargets.map(t => t.getFieldIndex() + (t.isEnemy() ? BattlerIndex.ENEMY : BattlerIndex.PLAYER));
} }
/** /**
* This takes the result of getSpeedOrder and applies priority / bypass speed attributes to it. * This takes the result of getSpeedOrder and applies priority / bypass speed attributes to it.
* This also considers the priority levels of various commands and changes the result of getSpeedOrder based on such. * This also considers the priority levels of various commands and changes the result of `getSpeedOrder` based on such.
* @returns {@linkcode BattlerIndex[]} the final sequence of commands for this turn * @returns An array of {@linkcode BattlerIndex}es containing all on-field Pokemon sorted in action order.
*/ */
getCommandOrder(): BattlerIndex[] { getCommandOrder(): BattlerIndex[] {
let moveOrder = this.getSpeedOrder(); let moveOrder = this.getSpeedOrder();
@ -115,7 +117,8 @@ export class TurnStartPhase extends FieldPhase {
} }
} }
// If there is no difference between the move's calculated priorities, the game checks for differences in battlerBypassSpeed and returns the result. // If there is no difference between the move's calculated priorities,
// check for differences in battlerBypassSpeed and returns the result.
if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
return battlerBypassSpeed[a].value ? -1 : 1; return battlerBypassSpeed[a].value ? -1 : 1;
} }
@ -136,8 +139,6 @@ export class TurnStartPhase extends FieldPhase {
const field = globalScene.getField(); const field = globalScene.getField();
const moveOrder = this.getCommandOrder(); const moveOrder = this.getCommandOrder();
let orderIndex = 0;
for (const o of this.getSpeedOrder()) { for (const o of this.getSpeedOrder()) {
const pokemon = field[o]; const pokemon = field[o];
const preTurnCommand = globalScene.currentBattle.preTurnCommands[o]; const preTurnCommand = globalScene.currentBattle.preTurnCommands[o];
@ -154,90 +155,22 @@ export class TurnStartPhase extends FieldPhase {
const phaseManager = globalScene.phaseManager; const phaseManager = globalScene.phaseManager;
for (const o of moveOrder) { moveOrder.forEach((o, index) => {
const pokemon = field[o]; const pokemon = field[o];
const turnCommand = globalScene.currentBattle.turnCommands[o]; const turnCommand = globalScene.currentBattle.turnCommands[o];
if (turnCommand?.skip) { if (!turnCommand || turnCommand.skip) {
continue;
}
switch (turnCommand?.command) {
case Command.FIGHT: {
const queuedMove = turnCommand.move;
pokemon.turnData.order = orderIndex++;
if (!queuedMove) {
continue;
}
const move =
pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) ??
new PokemonMove(queuedMove.move);
if (move.getMove().hasAttr("MoveHeaderAttr")) {
phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move);
}
if (pokemon.isPlayer() && turnCommand.cursor === -1) {
phaseManager.pushNew(
"MovePhase",
pokemon,
turnCommand.targets || turnCommand.move!.targets,
move,
turnCommand.move!.useMode,
); //TODO: is the bang correct here?
} else {
phaseManager.pushNew(
"MovePhase",
pokemon,
turnCommand.targets || turnCommand.move!.targets,
move,
queuedMove.useMode,
); // TODO: is the bang correct here?
}
break;
}
case Command.BALL:
phaseManager.unshiftNew("AttemptCapturePhase", turnCommand.targets![0] % 2, turnCommand.cursor!); //TODO: is the bang correct here?
break;
case Command.POKEMON:
{
const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH;
phaseManager.unshiftNew(
"SwitchSummonPhase",
switchType,
pokemon.getFieldIndex(),
turnCommand.cursor!,
true,
pokemon.isPlayer(),
);
}
break;
case Command.RUN:
{
let runningPokemon = pokemon;
if (globalScene.currentBattle.double) {
const playerActivePokemon = field.filter(pokemon => {
if (pokemon) {
return pokemon.isPlayer() && pokemon.isActive();
}
return; return;
}
// TODO: Remove `turnData.order` -
// it is used exclusively for Fusion Flare/Bolt
// and uses a really jank implementation
if (turnCommand.command === Command.FIGHT) {
pokemon.turnData.order = index;
}
this.handleTurnCommand(turnCommand, pokemon);
}); });
// if only one pokemon is alive, use that one
if (playerActivePokemon.length > 1) {
// find which active pokemon has faster speed
const fasterPokemon =
playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD)
? playerActivePokemon[0]
: playerActivePokemon[1];
// check if either active pokemon has the ability "Run Away"
const hasRunAway = playerActivePokemon.find(p => p.hasAbility(AbilityId.RUN_AWAY));
runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon;
}
}
phaseManager.unshiftNew("AttemptRunPhase", runningPokemon.getFieldIndex());
}
break;
}
}
phaseManager.pushNew("WeatherEffectPhase"); phaseManager.pushNew("WeatherEffectPhase");
phaseManager.pushNew("BerryPhase"); phaseManager.pushNew("BerryPhase");
@ -254,4 +187,67 @@ export class TurnStartPhase extends FieldPhase {
*/ */
this.end(); this.end();
} }
private handleTurnCommand(turnCommand: TurnCommand, pokemon: Pokemon) {
switch (turnCommand?.command) {
case Command.FIGHT:
this.handleFightCommand(turnCommand, pokemon);
break;
case Command.BALL:
globalScene.phaseManager.unshiftNew("AttemptCapturePhase", turnCommand.targets![0] % 2, turnCommand.cursor!); //TODO: is the bang correct here?
break;
case Command.POKEMON:
globalScene.phaseManager.unshiftNew(
"SwitchSummonPhase",
turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH,
pokemon.getFieldIndex(),
turnCommand.cursor!, // TODO: Is this bang correct?
true,
pokemon.isPlayer(),
);
break;
case Command.RUN:
{
const playerActivePokemon = globalScene.getPokemonAllowedInBattle();
if (!globalScene.currentBattle.double || playerActivePokemon.length === 1) {
// If not in doubles, attempt to run with the currently active Pokemon.
globalScene.phaseManager.unshiftNew("AttemptRunPhase", pokemon.getFieldIndex());
return;
}
// Use the fastest first pokemon we find with Run Away, or else the faster of the 2 player pokemon.
// This intentionally does not check for Trick Room.
// TODO: This phase should not take a pokemon at all
const sortedPkmn = playerActivePokemon.sort((p1, p2) => p1.getStat(Stat.SPD) - p2.getStat(Stat.SPD));
const runningPokemon = sortedPkmn.find(p => p.hasAbility(AbilityId.RUN_AWAY)) ?? sortedPkmn[0];
globalScene.phaseManager.unshiftNew("AttemptRunPhase", runningPokemon.getFieldIndex());
}
break;
}
}
private handleFightCommand(turnCommand: TurnCommand, pokemon: Pokemon) {
const queuedMove = turnCommand.move;
if (!queuedMove) {
return;
}
// TODO: This seems somewhat dubious
const move =
pokemon.getMoveset().find(m => m.moveId === queuedMove.move && m.ppUsed < m.getMovePp()) ??
new PokemonMove(queuedMove.move);
if (move.getMove().hasAttr("MoveHeaderAttr")) {
globalScene.phaseManager.unshiftNew("MoveHeaderPhase", pokemon, move);
}
globalScene.phaseManager.pushNew(
"MovePhase",
pokemon,
turnCommand.targets ?? queuedMove.targets,
move,
queuedMove.useMode,
);
}
} }