mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-06 08:07:42 +00:00
* Create Getters, Setters, and Types * Work on `pokemon.ts` * Adjust Types, Refactor `White Herb` Modifier * Migrate `TempBattleStat` Usage * Refactor `PokemonBaseStatModifier` Slightly * Remove `BattleStat`, Use "Stat Stages" & New Names * Address Phase `integers` * Finalize `BattleStat` Removal * Address Minor Manual NITs * Apply Own Review Suggestions * Fix Syntax Error * Add Docs * Overhaul X Items * Implement Guard and Power Split with Unit Tests * Add Several Unit Tests and Fixes * Implement Speed Swap with Unit Tests * Fix Keys in Summary Menu * Fix Starf Berry Raising EVA and ACC * Fix Contrary & Simple, Verify with Unit Tests * Implement Power & Guard Swap with Unit Tests * Add Move Effect Message to Speed Swap * Add Move Effect Message to Power & Guard Split * Add Localization Entries * Adjust Last X Item Unit Test * Overhaul X Items Unit Tests * Finish Missing Docs * Revamp Crit-Based Unit Tests & Dire Hit * Address Initial NITs * Apply NIT Batch Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Fix Moody Test * Address Multiple Messages for `ProtectStatAbAttr` * Change `ignoreOverride` to `bypassSummonData` * Adjust Italian Localization Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> * Fix Moody --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com>
227 lines
11 KiB
TypeScript
227 lines
11 KiB
TypeScript
import BattleScene from "#app/battle-scene";
|
|
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability";
|
|
import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move";
|
|
import { Abilities } from "#app/enums/abilities";
|
|
import { Stat } from "#app/enums/stat";
|
|
import Pokemon, { PokemonMove } from "#app/field/pokemon";
|
|
import { BypassSpeedChanceModifier } from "#app/modifier/modifier";
|
|
import { Command } from "#app/ui/command-ui-handler";
|
|
import * as Utils from "#app/utils";
|
|
import { AttemptCapturePhase } from "./attempt-capture-phase";
|
|
import { AttemptRunPhase } from "./attempt-run-phase";
|
|
import { BerryPhase } from "./berry-phase";
|
|
import { FieldPhase } from "./field-phase";
|
|
import { MoveHeaderPhase } from "./move-header-phase";
|
|
import { MovePhase } from "./move-phase";
|
|
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
|
import { SwitchSummonPhase } from "./switch-summon-phase";
|
|
import { TurnEndPhase } from "./turn-end-phase";
|
|
import { WeatherEffectPhase } from "./weather-effect-phase";
|
|
import { BattlerIndex } from "#app/battle";
|
|
import { TrickRoomTag } from "#app/data/arena-tag";
|
|
|
|
export class TurnStartPhase extends FieldPhase {
|
|
constructor(scene: BattleScene) {
|
|
super(scene);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @returns {@linkcode BattlerIndex[]} the battle indices of all pokemon on the field ordered by speed
|
|
*/
|
|
getSpeedOrder(): BattlerIndex[] {
|
|
const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
|
|
const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
|
|
|
|
// We shuffle the list before sorting so speed ties produce random results
|
|
let orderedTargets: Pokemon[] = playerField.concat(enemyField);
|
|
// We seed it with the current turn to prevent an inconsistency where it
|
|
// was varying based on how long since you last reloaded
|
|
this.scene.executeWithSeedOffset(() => {
|
|
orderedTargets = Utils.randSeedShuffle(orderedTargets);
|
|
}, this.scene.currentBattle.turn, this.scene.waveSeed);
|
|
|
|
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
|
|
const aSpeed = a?.getEffectiveStat(Stat.SPD) || 0;
|
|
const bSpeed = b?.getEffectiveStat(Stat.SPD) || 0;
|
|
|
|
return bSpeed - aSpeed;
|
|
});
|
|
|
|
// Next, a check for Trick Room is applied. If Trick Room is present, the order is reversed.
|
|
const speedReversed = new Utils.BooleanHolder(false);
|
|
this.scene.arena.applyTags(TrickRoomTag, speedReversed);
|
|
|
|
if (speedReversed.value) {
|
|
orderedTargets = orderedTargets.reverse();
|
|
}
|
|
|
|
return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : BattlerIndex.PLAYER));
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @returns {@linkcode BattlerIndex[]} the final sequence of commands for this turn
|
|
*/
|
|
getCommandOrder(): BattlerIndex[] {
|
|
let moveOrder = this.getSpeedOrder();
|
|
// The creation of the battlerBypassSpeed object contains checks for the ability Quick Draw and the held item Quick Claw
|
|
// The ability Mycelium Might disables Quick Claw's activation when using a status move
|
|
// This occurs before the main loop because of battles with more than two Pokemon
|
|
const battlerBypassSpeed = {};
|
|
|
|
this.scene.getField(true).filter(p => p.summonData).map(p => {
|
|
const bypassSpeed = new Utils.BooleanHolder(false);
|
|
const canCheckHeldItems = new Utils.BooleanHolder(true);
|
|
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed);
|
|
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems);
|
|
if (canCheckHeldItems.value) {
|
|
this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
|
}
|
|
battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
|
|
});
|
|
|
|
// The function begins sorting orderedTargets based on command priority, move priority, and possible speed bypasses.
|
|
// Non-FIGHT commands (SWITCH, BALL, RUN) have a higher command priority and will always occur before any FIGHT commands.
|
|
moveOrder = moveOrder.slice(0);
|
|
moveOrder.sort((a, b) => {
|
|
const aCommand = this.scene.currentBattle.turnCommands[a];
|
|
const bCommand = this.scene.currentBattle.turnCommands[b];
|
|
|
|
if (aCommand?.command !== bCommand?.command) {
|
|
if (aCommand?.command === Command.FIGHT) {
|
|
return 1;
|
|
} else if (bCommand?.command === Command.FIGHT) {
|
|
return -1;
|
|
}
|
|
} else if (aCommand?.command === Command.FIGHT) {
|
|
const aMove = allMoves[aCommand.move!.move];
|
|
const bMove = allMoves[bCommand!.move!.move];
|
|
|
|
// The game now considers priority and applies the relevant move and ability attributes
|
|
const aPriority = new Utils.IntegerHolder(aMove.priority);
|
|
const bPriority = new Utils.IntegerHolder(bMove.priority);
|
|
|
|
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority);
|
|
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority);
|
|
|
|
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority);
|
|
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority);
|
|
|
|
// The game now checks for differences in priority levels.
|
|
// If the moves share the same original priority bracket, it can check for differences in battlerBypassSpeed and return the result.
|
|
// This conditional is used to ensure that Quick Claw can still activate with abilities like Stall and Mycelium Might (attack moves only)
|
|
// Otherwise, the game returns the user of the move with the highest priority.
|
|
const isSameBracket = Math.ceil(aPriority.value) - Math.ceil(bPriority.value) === 0;
|
|
if (aPriority.value !== bPriority.value) {
|
|
if (isSameBracket && battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
|
|
return battlerBypassSpeed[a].value ? -1 : 1;
|
|
}
|
|
return aPriority.value < bPriority.value ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
// If there is no difference between the move's calculated priorities, the game checks for differences in battlerBypassSpeed and returns the result.
|
|
if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
|
|
return battlerBypassSpeed[a].value ? -1 : 1;
|
|
}
|
|
|
|
const aIndex = moveOrder.indexOf(a);
|
|
const bIndex = moveOrder.indexOf(b);
|
|
|
|
return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0;
|
|
});
|
|
return moveOrder;
|
|
}
|
|
|
|
start() {
|
|
super.start();
|
|
|
|
const field = this.scene.getField();
|
|
const moveOrder = this.getCommandOrder();
|
|
|
|
let orderIndex = 0;
|
|
|
|
for (const o of moveOrder) {
|
|
|
|
const pokemon = field[o];
|
|
const turnCommand = this.scene.currentBattle.turnCommands[o];
|
|
|
|
if (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) || new PokemonMove(queuedMove.move);
|
|
if (move.getMove().hasAttr(MoveHeaderAttr)) {
|
|
this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
|
|
}
|
|
if (pokemon.isPlayer()) {
|
|
if (turnCommand.cursor === -1) {
|
|
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here?
|
|
} else {
|
|
const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here?
|
|
this.scene.pushPhase(playerPhase);
|
|
}
|
|
} else {
|
|
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here?
|
|
}
|
|
break;
|
|
case Command.BALL:
|
|
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here?
|
|
break;
|
|
case Command.POKEMON:
|
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here?
|
|
break;
|
|
case Command.RUN:
|
|
let runningPokemon = pokemon;
|
|
if (this.scene.currentBattle.double) {
|
|
const playerActivePokemon = field.filter(pokemon => {
|
|
if (!!pokemon) {
|
|
return pokemon.isPlayer() && pokemon.isActive();
|
|
} else {
|
|
return;
|
|
}
|
|
});
|
|
// 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(Abilities.RUN_AWAY));
|
|
runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon;
|
|
}
|
|
}
|
|
this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
|
|
|
for (const o of moveOrder) {
|
|
if (field[o].status && field[o].status.isPostTurn()) {
|
|
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
|
}
|
|
}
|
|
|
|
this.scene.pushPhase(new BerryPhase(this.scene));
|
|
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
|
|
|
/**
|
|
* this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front
|
|
* of the queue and dequeues to start the next phase
|
|
* this is important since stuff like SwitchSummon, AttemptRun, AttemptCapture Phases break the "flow" and should take precedence
|
|
*/
|
|
this.end();
|
|
}
|
|
}
|