Merge remote-tracking branch 'upstream/beta' into more-tests

This commit is contained in:
Bertie690 2025-06-09 22:48:46 -04:00
commit 4e9655e129
399 changed files with 8083 additions and 5711 deletions

View File

@ -31,7 +31,6 @@
"src/overrides.ts", "src/overrides.ts",
// TODO: these files are too big and complex, ignore them until their respective refactors // TODO: these files are too big and complex, ignore them until their respective refactors
"src/data/moves/move.ts", "src/data/moves/move.ts",
"src/data/abilities/ability.ts",
// this file is just too big: // this file is just too big:
"src/data/balance/tms.ts" "src/data/balance/tms.ts"

View File

@ -1,172 +0,0 @@
/**
* This script creates a test boilerplate file in the appropriate
* directory based on the type selected.
* @example npm run create-test
*/
import fs from "fs";
import inquirer from "inquirer";
import path from "path";
import { fileURLToPath } from "url";
// Get the directory name of the current module file
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const typeChoices = ["Move", "Ability", "Item", "Mystery Encounter"];
/**
* Prompts the user to select a type via list.
* @returns {Promise<{selectedOption: string}>} the selected type
*/
async function promptTestType() {
const typeAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create:",
choices: [...typeChoices, "EXIT"],
},
]);
if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting...");
return process.exit();
}
if (!typeChoices.includes(typeAnswer.selectedOption)) {
console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
return await promptTestType();
}
return typeAnswer;
}
/**
* Prompts the user to provide a file name.
* @param {string} selectedType
* @returns {Promise<{userInput: string}>} the selected file name
*/
async function promptFileName(selectedType) {
const fileNameAnswer = await inquirer.prompt([
{
type: "input",
name: "userInput",
message: `Please provide the name of the ${selectedType}:`,
},
]);
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
console.error("Please provide a valid file name!");
return await promptFileName(selectedType);
}
return fileNameAnswer;
}
/**
* Runs the interactive create-test "CLI"
* @returns {Promise<void>}
*/
async function runInteractive() {
const typeAnswer = await promptTestType();
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption);
const type = typeAnswer.selectedOption.toLowerCase();
// Convert fileName from kebab-case or camelCase to snake_case
const fileName = fileNameAnswer.userInput
.replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores
.replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case
.replace(/\s+/g, "_") // Replace spaces with underscores
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, char => char.toUpperCase());
// Determine the directory based on the type
let dir;
let description;
switch (type) {
case "move":
dir = path.join(__dirname, "test", "moves");
description = `Moves - ${formattedName}`;
break;
case "ability":
dir = path.join(__dirname, "test", "abilities");
description = `Abilities - ${formattedName}`;
break;
case "item":
dir = path.join(__dirname, "test", "items");
description = `Items - ${formattedName}`;
break;
case "mystery encounter":
dir = path.join(__dirname, "test", "mystery-encounter", "encounters");
description = `Mystery Encounter - ${formattedName}`;
break;
default:
console.error(`Invalid type. Please use one of the following: ${typeChoices.join(", ")}.`);
process.exit(1);
}
// Define the content template
const content = `import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("${description}", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should do X", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
expect(true).toBe(true);
});
});
`;
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create the file with the given name
const filePath = path.join(dir, `${fileName}.test.ts`);
if (fs.existsSync(filePath)) {
console.error(`File "${fileName}.test.ts" already exists.`);
process.exit(1);
}
// Write the template content to the file
fs.writeFileSync(filePath, content, "utf8");
console.log(`File created at: ${filePath}`);
}
runInteractive();

View File

@ -145,6 +145,5 @@
</div> </div>
<script type="module" src="./src/main.ts"></script> <script type="module" src="./src/main.ts"></script>
<script src="./src/touch-controls.ts" type="module"></script> <script src="./src/touch-controls.ts" type="module"></script>
<script src="./src/debug.js" type="module"></script>
</body> </body>
</html> </html>

View File

@ -13,6 +13,7 @@
"test:cov": "vitest run --coverage --no-isolate", "test:cov": "vitest run --coverage --no-isolate",
"test:watch": "vitest watch --coverage --no-isolate", "test:watch": "vitest watch --coverage --no-isolate",
"test:silent": "vitest run --silent --no-isolate", "test:silent": "vitest run --silent --no-isolate",
"test:create": "node scripts/create-test/create-test.js",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
@ -21,7 +22,6 @@
"docs": "typedoc", "docs": "typedoc",
"depcruise": "depcruise src", "depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js",
"postinstall": "npx lefthook install && npx lefthook run post-merge", "postinstall": "npx lefthook install && npx lefthook run post-merge",
"update-version:patch": "npm version patch --force --no-git-tag-version", "update-version:patch": "npm version patch --force --no-git-tag-version",
"update-version:minor": "npm version minor --force --no-git-tag-version", "update-version:minor": "npm version minor --force --no-git-tag-version",

View File

@ -0,0 +1,147 @@
/**
* This script creates a test boilerplate file in the appropriate
* directory based on the type selected.
* @example npm run test:create
*/
import chalk from "chalk";
import inquirer from "inquirer";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
//#region Constants
const version = "2.0.1";
// Get the directory name of the current module file
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.join(__dirname, "..", "..");
const boilerplateFilePath = path.join(__dirname, "test-boilerplate.ts");
const choices = [
{ label: "Move", dir: "moves" },
{ label: "Ability", dir: "abilities" },
{ label: "Item", dir: "items" },
{ label: "Mystery Encounter", dir: "mystery-encounter/encounters" },
{ label: "Utils", dir: "utils" },
{ label: "UI", dir: "ui" },
];
//#endregion
//#region Functions
/**
* Get the path to a given folder in the test directory
* @param {...string} folders the subfolders to append to the base path
* @returns {string} the path to the requested folder
*/
function getTestFolderPath(...folders) {
return path.join(projectRoot, "test", ...folders);
}
/**
* Prompts the user to select a type via list.
* @returns {Promise<{selectedOption: {label: string, dir: string}}>} the selected type
*/
async function promptTestType() {
const typeAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create:",
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"],
},
]);
if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting...");
return process.exit();
}
if (!choices.some(choice => choice.dir === typeAnswer.selectedOption.dir)) {
console.error(`Please provide a valid type: (${choices.map(choice => choice.label).join(", ")})!`);
return await promptTestType();
}
return typeAnswer;
}
/**
* Prompts the user to provide a file name.
* @param {string} selectedType
* @returns {Promise<{userInput: string}>} the selected file name
*/
async function promptFileName(selectedType) {
/** @type {{userInput: string}} */
const fileNameAnswer = await inquirer.prompt([
{
type: "input",
name: "userInput",
message: `Please provide the name of the ${selectedType}:`,
},
]);
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
console.error("Please provide a valid file name!");
return await promptFileName(selectedType);
}
return fileNameAnswer;
}
/**
* Runs the interactive test:create "CLI"
* @returns {Promise<void>}
*/
async function runInteractive() {
console.group(chalk.grey(`Create Test - v${version}\n`));
try {
const typeAnswer = await promptTestType();
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption.label);
const type = typeAnswer.selectedOption;
// Convert fileName from snake_case or camelCase to kebab-case
const fileName = fileNameAnswer.userInput
.replace(/_+/g, "-") // Convert snake_case (underscore) to kebab-case (dashes)
.replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case
.replace(/\s+/g, "-") // Replace spaces with dashes
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase());
// Determine the directory based on the type
const dir = getTestFolderPath(type.dir);
const description = `${type.label} - ${formattedName}`;
// Define the content template
const content = fs.readFileSync(boilerplateFilePath, "utf8").replace("{{description}}", description);
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create the file with the given name
const filePath = path.join(dir, `${fileName}.test.ts`);
if (fs.existsSync(filePath)) {
console.error(chalk.red.bold(`\n✗ File "${fileName}.test.ts" already exists!\n`));
process.exit(1);
}
// Write the template content to the file
fs.writeFileSync(filePath, content, "utf8");
console.log(chalk.green.bold(`\n✔ File created at: test/${type.dir}/${fileName}.test.ts\n`));
console.groupEnd();
} catch (err) {
console.error(chalk.red("✗ Error: ", err.message));
}
}
//#endregion
//#region Run
runInteractive();
//#endregion

View File

@ -0,0 +1,43 @@
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("{{description}}", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.ability(AbilityId.BALL_FETCH)
.battleStyle("single")
.disableCrits()
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH)
.startingLevel(100)
.enemyLevel(100);
});
it("should do XYZ", async () => {
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
game.move.use(MoveId.SPLASH);
await game.toEndOfTurn();
expect(true).toBe(true);
});
});

View File

@ -1,6 +1,7 @@
/** export interface DexData {
* Dex entry for a single Pokemon Species [key: number]: DexEntry;
*/ }
export interface DexEntry { export interface DexEntry {
seenAttr: bigint; seenAttr: bigint;
caughtAttr: bigint; caughtAttr: bigint;
@ -10,7 +11,3 @@ export interface DexEntry {
hatchedCount: number; hatchedCount: number;
ivs: number[]; ivs: number[];
} }
export interface DexData {
[key: number]: DexEntry;
}

View File

@ -2,9 +2,6 @@ export interface Localizable {
localize(): void; localize(): void;
} }
export interface TranslationEntries {
[key: string]: string | { [key: string]: string };
}
export interface SimpleTranslationEntries { export interface SimpleTranslationEntries {
[key: string]: string; [key: string]: string;
} }

56
src/@types/move-types.ts Normal file
View File

@ -0,0 +1,56 @@
import type {
AttackMove,
StatusMove,
SelfStatusMove,
ChargingAttackMove,
ChargingSelfStatusMove,
MoveAttrConstructorMap,
MoveAttr,
} from "#app/data/moves/move";
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
export type * from "#app/data/moves/move";
/**
* Map of move subclass names to their respective classes.
* Does not include the ChargeMove subclasses. For that, use `ChargingMoveClassMap`.
*
* @privateremarks
* The `never` field (`declare private _: never`) in some classes is necessary
* to ensure typescript does not improperly narrow a failed `is` guard to `never`.
*
* For example, if we did not have the never, and wrote
* ```
* function Foo(move: Move) {
* if (move.is("AttackMove")) {
*
* } else if (move.is("StatusMove")) { // typescript errors on the `is`, saying that `move` is `never`
*
* }
* ```
*/
export type MoveClassMap = {
AttackMove: AttackMove;
StatusMove: StatusMove;
SelfStatusMove: SelfStatusMove;
};
/**
* Union type of all move subclass names
*/
export type MoveKindString = "AttackMove" | "StatusMove" | "SelfStatusMove";
/**
* Map of move attribute names to attribute instances.
*/
export type MoveAttrMap = {
[K in keyof MoveAttrConstructorMap]: InstanceType<MoveAttrConstructorMap[K]>;
};
/**
* Union type of all move attribute names as strings.
*/
export type MoveAttrString = keyof MoveAttrMap;
export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove;

View File

@ -1,286 +1,25 @@
import type { MoveAnim } from "#app/data/battle-anims"; import type { PhaseConstructorMap } from "#app/phase-manager";
import type { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
import type { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
import type { AttemptRunPhase } from "#app/phases/attempt-run-phase";
import type { BattleEndPhase } from "#app/phases/battle-end-phase";
import type { BerryPhase } from "#app/phases/berry-phase";
import type { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import type { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import type { CommandPhase } from "#app/phases/command-phase";
import type { CommonAnimPhase } from "#app/phases/common-anim-phase";
import type { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import type { EggHatchPhase } from "#app/phases/egg-hatch-phase";
import type { EggLapsePhase } from "#app/phases/egg-lapse-phase";
import type { EggSummaryPhase } from "#app/phases/egg-summary-phase";
import type { EncounterPhase } from "#app/phases/encounter-phase";
import type { EndCardPhase } from "#app/phases/end-card-phase";
import type { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
import type { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import type { EvolutionPhase } from "#app/phases/evolution-phase";
import type { ExpPhase } from "#app/phases/exp-phase";
import type { FaintPhase } from "#app/phases/faint-phase";
import type { FormChangePhase } from "#app/phases/form-change-phase";
import type { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase";
import type { GameOverPhase } from "#app/phases/game-over-phase";
import type { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import type { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase";
import type { LearnMovePhase } from "#app/phases/learn-move-phase";
import type { LevelCapPhase } from "#app/phases/level-cap-phase";
import type { LevelUpPhase } from "#app/phases/level-up-phase";
import type { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase";
import type { LoginPhase } from "#app/phases/login-phase";
import type { MessagePhase } from "#app/phases/message-phase";
import type { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import type { MoneyRewardPhase } from "#app/phases/money-reward-phase";
import type { MoveAnimPhase } from "#app/phases/move-anim-phase";
import type { MoveChargePhase } from "#app/phases/move-charge-phase";
import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
import type { MoveEndPhase } from "#app/phases/move-end-phase";
import type { MoveHeaderPhase } from "#app/phases/move-header-phase";
import type { MovePhase } from "#app/phases/move-phase";
import type {
MysteryEncounterPhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterBattlePhase,
MysteryEncounterRewardsPhase,
PostMysteryEncounterPhase,
MysteryEncounterBattleStartCleanupPhase,
} from "#app/phases/mystery-encounter-phases";
import type { NewBattlePhase } from "#app/phases/new-battle-phase";
import type { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase";
import type { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import type { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import type { PartyExpPhase } from "#app/phases/party-exp-phase";
import type { PartyHealPhase } from "#app/phases/party-heal-phase";
import type { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import type { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import type { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import type { PostGameOverPhase } from "#app/phases/post-game-over-phase";
import type { PostSummonPhase } from "#app/phases/post-summon-phase";
import type { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
import type { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import type { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import type { ResetStatusPhase } from "#app/phases/reset-status-phase";
import type { ReturnPhase } from "#app/phases/return-phase";
import type { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase";
import type { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase";
import type { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import type { SelectBiomePhase } from "#app/phases/select-biome-phase";
import type { SelectChallengePhase } from "#app/phases/select-challenge-phase";
import type { SelectGenderPhase } from "#app/phases/select-gender-phase";
import type { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import type { SelectStarterPhase } from "#app/phases/select-starter-phase";
import type { SelectTargetPhase } from "#app/phases/select-target-phase";
import type { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import type { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import type { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import type { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import type { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import type { SummonMissingPhase } from "#app/phases/summon-missing-phase";
import type { SummonPhase } from "#app/phases/summon-phase";
import type { SwitchBiomePhase } from "#app/phases/switch-biome-phase";
import type { SwitchPhase } from "#app/phases/switch-phase";
import type { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import type { TeraPhase } from "#app/phases/tera-phase";
import type { TitlePhase } from "#app/phases/title-phase";
import type { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import type { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase";
import type { TurnEndPhase } from "#app/phases/turn-end-phase";
import type { TurnInitPhase } from "#app/phases/turn-init-phase";
import type { TurnStartPhase } from "#app/phases/turn-start-phase";
import type { UnavailablePhase } from "#app/phases/unavailable-phase";
import type { UnlockPhase } from "#app/phases/unlock-phase";
import type { VictoryPhase } from "#app/phases/victory-phase";
import type { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
export type PhaseClass = // Intentionally export the types of everything in phase-manager, as this file is meant to be
| typeof AddEnemyBuffModifierPhase // the centralized place for type definitions for the phase system.
| typeof AttemptCapturePhase export type * from "#app/phase-manager";
| typeof AttemptRunPhase
| typeof BattleEndPhase
| typeof BerryPhase
| typeof CheckStatusEffectPhase
| typeof CheckSwitchPhase
| typeof CommandPhase
| typeof CommonAnimPhase
| typeof DamageAnimPhase
| typeof EggHatchPhase
| typeof EggLapsePhase
| typeof EggSummaryPhase
| typeof EncounterPhase
| typeof EndCardPhase
| typeof EndEvolutionPhase
| typeof EnemyCommandPhase
| typeof EvolutionPhase
| typeof FormChangePhase
| typeof ExpPhase
| typeof FaintPhase
| typeof FormChangePhase
| typeof GameOverPhase
| typeof GameOverModifierRewardPhase
| typeof HideAbilityPhase
| typeof HidePartyExpBarPhase
| typeof LearnMovePhase
| typeof LevelUpPhase
| typeof LevelCapPhase
| typeof LoadMoveAnimPhase
| typeof LoginPhase
| typeof MessagePhase
| typeof ModifierRewardPhase
| typeof MoneyRewardPhase
| typeof MoveAnimPhase
| typeof MoveChargePhase
| typeof MoveEffectPhase
| typeof MoveEndPhase
| typeof MoveHeaderPhase
| typeof MovePhase
| typeof MysteryEncounterPhase
| typeof MysteryEncounterOptionSelectedPhase
| typeof MysteryEncounterBattlePhase
| typeof MysteryEncounterRewardsPhase
| typeof MysteryEncounterBattleStartCleanupPhase
| typeof MysteryEncounterRewardsPhase
| typeof PostMysteryEncounterPhase
| typeof NewBattlePhase
| typeof NewBiomeEncounterPhase
| typeof NextEncounterPhase
| typeof ObtainStatusEffectPhase
| typeof PartyExpPhase
| typeof PartyHealPhase
| typeof PokemonAnimPhase
| typeof PokemonHealPhase
| typeof PokemonTransformPhase
| typeof PostGameOverPhase
| typeof PostSummonPhase
| typeof PostTurnStatusEffectPhase
| typeof QuietFormChangePhase
| typeof ReloadSessionPhase
| typeof ResetStatusPhase
| typeof ReturnPhase
| typeof RevivalBlessingPhase
| typeof RibbonModifierRewardPhase
| typeof ScanIvsPhase
| typeof SelectBiomePhase
| typeof SelectChallengePhase
| typeof SelectGenderPhase
| typeof SelectModifierPhase
| typeof SelectStarterPhase
| typeof SelectTargetPhase
| typeof ShinySparklePhase
| typeof ShowAbilityPhase
| typeof ShowTrainerPhase
| typeof ShowPartyExpBarPhase
| typeof StatStageChangePhase
| typeof SummonMissingPhase
| typeof SummonPhase
| typeof SwitchBiomePhase
| typeof SwitchPhase
| typeof SwitchSummonPhase
| typeof TeraPhase
| typeof TitlePhase
| typeof ToggleDoublePositionPhase
| typeof TrainerVictoryPhase
| typeof TurnEndPhase
| typeof TurnInitPhase
| typeof TurnStartPhase
| typeof UnavailablePhase
| typeof UnlockPhase
| typeof VictoryPhase
| typeof WeatherEffectPhase;
/** Typescript map used to map a string phase to the actual phase type */ // This file includes helpful types for the phase system.
// It intentionally imports the phase constructor map from the phase manager (and re-exports it)
/**
* Map of phase names to constructors for said phase
*/
export type PhaseMap = { export type PhaseMap = {
AddEnemyBuffModifierPhase: AddEnemyBuffModifierPhase; [K in keyof PhaseConstructorMap]: InstanceType<PhaseConstructorMap[K]>;
AttemptCapturePhase: AttemptCapturePhase;
AttemptRunPhase: AttemptRunPhase;
BattleEndPhase: BattleEndPhase;
BerryPhase: BerryPhase;
CheckStatusEffectPhase: CheckStatusEffectPhase;
CheckSwitchPhase: CheckSwitchPhase;
CommandPhase: CommandPhase;
CommonAnimPhase: CommonAnimPhase;
DamageAnimPhase: DamageAnimPhase;
EggHatchPhase: EggHatchPhase;
EggLapsePhase: EggLapsePhase;
EggSummaryPhase: EggSummaryPhase;
EncounterPhase: EncounterPhase;
EndCardPhase: EndCardPhase;
EndEvolutionPhase: EndEvolutionPhase;
EnemyCommandPhase: EnemyCommandPhase;
EvolutionPhase: EvolutionPhase;
ExpPhase: ExpPhase;
FaintPhase: FaintPhase;
FormChangePhase: FormChangePhase;
GameOverPhase: GameOverPhase;
GameOverModifierRewardPhase: GameOverModifierRewardPhase;
HideAbilityPhase: HideAbilityPhase;
HidePartyExpBarPhase: HidePartyExpBarPhase;
LearnMovePhase: LearnMovePhase;
LevelCapPhase: LevelCapPhase;
LevelUpPhase: LevelUpPhase;
LoadMoveAnimPhase: LoadMoveAnimPhase;
LoginPhase: LoginPhase;
MessagePhase: MessagePhase;
ModifierRewardPhase: ModifierRewardPhase;
MoneyRewardPhase: MoneyRewardPhase;
MoveAnimPhase: MoveAnimPhase<MoveAnim>;
MoveChargePhase: MoveChargePhase;
MoveEffectPhase: MoveEffectPhase;
MoveEndPhase: MoveEndPhase;
MoveHeaderPhase: MoveHeaderPhase;
MovePhase: MovePhase;
MysteryEncounterPhase: MysteryEncounterPhase;
MysteryEncounterOptionSelectedPhase: MysteryEncounterOptionSelectedPhase;
MysteryEncounterBattlePhase: MysteryEncounterBattlePhase;
MysteryEncounterBattleStartCleanupPhase: MysteryEncounterBattleStartCleanupPhase;
MysteryEncounterRewardsPhase: MysteryEncounterRewardsPhase;
PostMysteryEncounterPhase: PostMysteryEncounterPhase;
NewBattlePhase: NewBattlePhase;
NewBiomeEncounterPhase: NewBiomeEncounterPhase;
NextEncounterPhase: NextEncounterPhase;
ObtainStatusEffectPhase: ObtainStatusEffectPhase;
PartyExpPhase: PartyExpPhase;
PartyHealPhase: PartyHealPhase;
PokemonAnimPhase: PokemonAnimPhase;
PokemonHealPhase: PokemonHealPhase;
PokemonTransformPhase: PokemonTransformPhase;
PostGameOverPhase: PostGameOverPhase;
PostSummonPhase: PostSummonPhase;
PostTurnStatusEffectPhase: PostTurnStatusEffectPhase;
QuietFormChangePhase: QuietFormChangePhase;
ReloadSessionPhase: ReloadSessionPhase;
ResetStatusPhase: ResetStatusPhase;
ReturnPhase: ReturnPhase;
RevivalBlessingPhase: RevivalBlessingPhase;
RibbonModifierRewardPhase: RibbonModifierRewardPhase;
ScanIvsPhase: ScanIvsPhase;
SelectBiomePhase: SelectBiomePhase;
SelectChallengePhase: SelectChallengePhase;
SelectGenderPhase: SelectGenderPhase;
SelectModifierPhase: SelectModifierPhase;
SelectStarterPhase: SelectStarterPhase;
SelectTargetPhase: SelectTargetPhase;
ShinySparklePhase: ShinySparklePhase;
ShowAbilityPhase: ShowAbilityPhase;
ShowPartyExpBarPhase: ShowPartyExpBarPhase;
ShowTrainerPhase: ShowTrainerPhase;
StatStageChangePhase: StatStageChangePhase;
SummonMissingPhase: SummonMissingPhase;
SummonPhase: SummonPhase;
SwitchBiomePhase: SwitchBiomePhase;
SwitchPhase: SwitchPhase;
SwitchSummonPhase: SwitchSummonPhase;
TeraPhase: TeraPhase;
TitlePhase: TitlePhase;
ToggleDoublePositionPhase: ToggleDoublePositionPhase;
TrainerVictoryPhase: TrainerVictoryPhase;
TurnEndPhase: TurnEndPhase;
TurnInitPhase: TurnInitPhase;
TurnStartPhase: TurnStartPhase;
UnavailablePhase: UnavailablePhase;
UnlockPhase: UnlockPhase;
VictoryPhase: VictoryPhase;
WeatherEffectPhase: WeatherEffectPhase;
}; };
/**
* Union type of all phase constructors.
*/
export type PhaseClass = PhaseConstructorMap[keyof PhaseConstructorMap];
/**
* Union type of all phase names as strings.
*/
export type PhaseString = keyof PhaseMap; export type PhaseString = keyof PhaseMap;

View File

@ -2,8 +2,8 @@ import type { EnemyPokemon } from "#app/field/pokemon";
import type { PersistentModifier } from "#app/modifier/modifier"; import type { PersistentModifier } from "#app/modifier/modifier";
import type { PartyMemberStrength } from "#enums/party-member-strength"; import type { PartyMemberStrength } from "#enums/party-member-strength";
import type { SpeciesId } from "#enums/species-id"; import type { SpeciesId } from "#enums/species-id";
import type { TrainerConfig } from "./trainer-config"; import type { TrainerConfig } from "../data/trainers/trainer-config";
import type { TrainerPartyTemplate } from "./TrainerPartyTemplate"; import type { TrainerPartyTemplate } from "../data/trainers/TrainerPartyTemplate";
export type PartyTemplateFunc = () => TrainerPartyTemplate; export type PartyTemplateFunc = () => TrainerPartyTemplate;
export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon; export type PartyMemberFunc = (level: number, strength: PartyMemberStrength) => EnemyPokemon;

View File

@ -80,13 +80,15 @@ import type { FixedBattleConfig } from "#app/battle";
import Battle from "#app/battle"; import Battle from "#app/battle";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode"; import { getGameMode } from "#app/game-mode";
import { GameModes } from "#enums/game-modes";
import FieldSpritePipeline from "#app/pipelines/field-sprite"; import FieldSpritePipeline from "#app/pipelines/field-sprite";
import SpritePipeline from "#app/pipelines/sprite"; import SpritePipeline from "#app/pipelines/sprite";
import PartyExpBar from "#app/ui/party-exp-bar"; import PartyExpBar from "#app/ui/party-exp-bar";
import type { TrainerSlot } from "./enums/trainer-slot"; import type { TrainerSlot } from "./enums/trainer-slot";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer from "#app/field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import type TrainerData from "#app/system/trainer-data"; import type TrainerData from "#app/system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
@ -101,14 +103,12 @@ import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
import { addUiThemeOverrides } from "#app/ui/ui-theme"; import { addUiThemeOverrides } from "#app/ui/ui-theme";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { import type { SpeciesFormChangeTrigger } from "./data/pokemon-forms/form-change-triggers";
FormChangeItem, import { pokemonFormChanges } from "#app/data/pokemon-forms";
pokemonFormChanges, import { SpeciesFormChangeTimeOfDayTrigger } from "./data/pokemon-forms/form-change-triggers";
SpeciesFormChangeManualTrigger, import { SpeciesFormChangeManualTrigger } from "./data/pokemon-forms/form-change-triggers";
SpeciesFormChangeTimeOfDayTrigger, import { FormChangeItem } from "#enums/form-change-item";
} from "#app/data/pokemon-forms";
import { FormChangePhase } from "#app/phases/form-change-phase";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler"; import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler";
@ -120,7 +120,7 @@ import { SceneBase } from "#app/scene-base";
import CandyBar from "#app/ui/candy-bar"; import CandyBar from "#app/ui/candy-bar";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { variantData, clearVariantData } from "#app/sprites/variant"; import { variantData, clearVariantData } from "#app/sprites/variant";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { InputsController } from "#app/inputs-controller"; import { InputsController } from "#app/inputs-controller";
import { UiInputs } from "#app/ui-inputs"; import { UiInputs } from "#app/ui-inputs";
@ -142,20 +142,7 @@ import i18next from "i18next";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { battleSpecDialogue } from "#app/data/dialogue"; import { battleSpecDialogue } from "#app/data/dialogue";
import { LoadingScene } from "#app/loading-scene"; import { LoadingScene } from "#app/loading-scene";
import { LevelCapPhase } from "#app/phases/level-cap-phase";
import { LoginPhase } from "#app/phases/login-phase";
import { MessagePhase } from "#app/phases/message-phase";
import type { MovePhase } from "#app/phases/move-phase"; import type { MovePhase } from "#app/phases/move-phase";
import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase";
import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { TitlePhase } from "#app/phases/title-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { import {
@ -169,22 +156,19 @@ import {
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { ExpPhase } from "#app/phases/exp-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { ExpGainsSpeed } from "#enums/exp-gains-speed";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { initGlobalScene } from "#app/global-scene"; import { initGlobalScene } from "#app/global-scene";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import { expSpriteKeys } from "./sprites/sprite-keys"; import { expSpriteKeys } from "./sprites/sprite-keys";
import { hasExpSprite } from "./sprites/sprite-utils"; import { hasExpSprite } from "./sprites/sprite-utils";
import { timedEventManager } from "./global-event-manager"; import { timedEventManager } from "./global-event-manager";
import { starterColors } from "./global-vars/starter-colors"; import { starterColors } from "./global-vars/starter-colors";
import { startingWave } from "./starting-wave"; import { startingWave } from "./starting-wave";
import { PhaseManager } from "./phase-manager";
const DEBUG_RNG = false; const DEBUG_RNG = false;
@ -297,18 +281,8 @@ export default class BattleScene extends SceneBase {
public gameData: GameData; public gameData: GameData;
public sessionSlotId: number; public sessionSlotId: number;
/** PhaseQueue: dequeue/remove the first element to get the next phase */ /** Manager for the phases active in the battle scene */
public phaseQueue: Phase[]; public readonly phaseManager: PhaseManager;
public conditionalQueue: Array<[() => boolean, Phase]>;
/** PhaseQueuePrepend: is a temp storage of what will be added to PhaseQueue */
private phaseQueuePrepend: Phase[];
/** overrides default of inserting phases to end of phaseQueuePrepend array, useful or inserting Phases "out of order" */
private phaseQueuePrependSpliceIndex: number;
private nextCommandPhaseQueue: Phase[];
private currentPhase: Phase | null;
private standbyPhase: Phase | null;
public field: Phaser.GameObjects.Container; public field: Phaser.GameObjects.Container;
public fieldUI: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container;
public charSprite: CharSprite; public charSprite: CharSprite;
@ -396,11 +370,7 @@ export default class BattleScene extends SceneBase {
constructor() { constructor() {
super("battle"); super("battle");
this.phaseQueue = []; this.phaseManager = new PhaseManager();
this.phaseQueuePrepend = [];
this.conditionalQueue = [];
this.phaseQueuePrependSpliceIndex = -1;
this.nextCommandPhaseQueue = [];
this.eventManager = new TimedEventManager(); this.eventManager = new TimedEventManager();
this.updateGameInfo(); this.updateGameInfo();
initGlobalScene(this); initGlobalScene(this);
@ -716,10 +686,10 @@ export default class BattleScene extends SceneBase {
).then(() => loadMoveAnimAssets(defaultMoves, true)), ).then(() => loadMoveAnimAssets(defaultMoves, true)),
this.initStarterColors(), this.initStarterColors(),
]).then(() => { ]).then(() => {
this.pushPhase(new LoginPhase()); this.phaseManager.pushNew("LoginPhase");
this.pushPhase(new TitlePhase()); this.phaseManager.pushNew("TitlePhase");
this.shiftPhase(); this.phaseManager.shiftPhase();
}); });
} }
@ -811,6 +781,7 @@ export default class BattleScene extends SceneBase {
} }
} }
// TODO: Add a `getPartyOnSide` function for getting the party of a pokemon
public getPlayerParty(): PlayerPokemon[] { public getPlayerParty(): PlayerPokemon[] {
return this.party; return this.party;
} }
@ -898,7 +869,7 @@ export default class BattleScene extends SceneBase {
if (allyPokemon?.isActive(true)) { if (allyPokemon?.isActive(true)) {
let targetingMovePhase: MovePhase; let targetingMovePhase: MovePhase;
do { do {
targetingMovePhase = this.findPhase( targetingMovePhase = this.phaseManager.findPhase(
mp => mp =>
mp.is("MovePhase") && mp.is("MovePhase") &&
mp.targets.length === 1 && mp.targets.length === 1 &&
@ -1276,7 +1247,7 @@ export default class BattleScene extends SceneBase {
duration: 250, duration: 250,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
onComplete: () => { onComplete: () => {
this.clearPhaseQueue(); this.phaseManager.clearPhaseQueue();
this.ui.freeUIData(); this.ui.freeUIData();
this.uiContainer.remove(this.ui, true); this.uiContainer.remove(this.ui, true);
@ -1449,7 +1420,7 @@ export default class BattleScene extends SceneBase {
} }
if (lastBattle?.double && !newDouble) { if (lastBattle?.double && !newDouble) {
this.tryRemovePhase((p: Phase) => p.is("SwitchPhase")); this.phaseManager.tryRemovePhase((p: Phase) => p.is("SwitchPhase"));
for (const p of this.getPlayerField()) { for (const p of this.getPlayerField()) {
p.lapseTag(BattlerTagType.COMMANDED); p.lapseTag(BattlerTagType.COMMANDED);
} }
@ -1491,7 +1462,7 @@ export default class BattleScene extends SceneBase {
playerField.forEach((pokemon, p) => { playerField.forEach((pokemon, p) => {
if (pokemon.isOnField()) { if (pokemon.isOnField()) {
this.pushPhase(new ReturnPhase(p)); this.phaseManager.pushNew("ReturnPhase", p);
} }
}); });
@ -1508,7 +1479,7 @@ export default class BattleScene extends SceneBase {
} }
if (!this.trainer.visible) { if (!this.trainer.visible) {
this.pushPhase(new ShowTrainerPhase()); this.phaseManager.pushNew("ShowTrainerPhase");
} }
} }
@ -1517,13 +1488,13 @@ export default class BattleScene extends SceneBase {
} }
if (!this.gameMode.hasRandomBiomes && !isNewBiome) { if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase()); this.phaseManager.pushNew("NextEncounterPhase");
} else { } else {
this.pushPhase(new NewBiomeEncounterPhase()); this.phaseManager.pushNew("NewBiomeEncounterPhase");
const newMaxExpLevel = this.getMaxExpLevel(); const newMaxExpLevel = this.getMaxExpLevel();
if (newMaxExpLevel > maxExpLevel) { if (newMaxExpLevel > maxExpLevel) {
this.pushPhase(new LevelCapPhase()); this.phaseManager.pushNew("LevelCapPhase");
} }
} }
} }
@ -1587,7 +1558,9 @@ export default class BattleScene extends SceneBase {
return 0; return 0;
} }
const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes(this.getCurrentPhase()?.phaseName ?? ""); const isEggPhase: boolean = ["EggLapsePhase", "EggHatchPhase"].includes(
this.phaseManager.getCurrentPhase()?.phaseName ?? "",
);
if ( if (
// Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros. // Give trainers with specialty types an appropriately-typed form for Wormadam, Rotom, Arceus, Oricorio, Silvally, or Paldean Tauros.
@ -2614,286 +2587,6 @@ export default class BattleScene extends SceneBase {
} }
} }
/* Phase Functions */
getCurrentPhase(): Phase | null {
return this.currentPhase;
}
getStandbyPhase(): Phase | null {
return this.standbyPhase;
}
/**
* Adds a phase to the conditional queue and ensures it is executed only when the specified condition is met.
*
* This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling
* situations like abilities and entry hazards that depend on specific game states.
*
* @param {Phase} phase - The phase to be added to the conditional queue.
* @param {() => boolean} condition - A function that returns a boolean indicating whether the phase should be executed.
*
*/
pushConditionalPhase(phase: Phase, condition: () => boolean): void {
this.conditionalQueue.push([condition, phase]);
}
/**
* Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false
* @param phase {@linkcode Phase} the phase to add
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
*/
pushPhase(phase: Phase, defer = false): void {
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
}
/**
* Adds Phase(s) to the end of phaseQueuePrepend, or at phaseQueuePrependSpliceIndex
* @param phases {@linkcode Phase} the phase(s) to add
*/
unshiftPhase(...phases: Phase[]): void {
if (this.phaseQueuePrependSpliceIndex === -1) {
this.phaseQueuePrepend.push(...phases);
} else {
this.phaseQueuePrepend.splice(this.phaseQueuePrependSpliceIndex, 0, ...phases);
}
}
/**
* Clears the phaseQueue
*/
clearPhaseQueue(): void {
this.phaseQueue.splice(0, this.phaseQueue.length);
}
/**
* Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index
*/
clearAllPhases(): void {
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
queue.splice(0, queue.length);
}
this.currentPhase = null;
this.standbyPhase = null;
this.clearPhaseQueueSplice();
}
/**
* Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases
*/
setPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length;
}
/**
* Resets phaseQueuePrependSpliceIndex to -1, implies that calls to unshiftPhase will insert at end of phaseQueuePrepend
*/
clearPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = -1;
}
/**
* Is called by each Phase implementations "end()" by default
* We dump everything from phaseQueuePrepend to the start of of phaseQueue
* then removes first Phase and starts it
*/
shiftPhase(): void {
if (this.standbyPhase) {
this.currentPhase = this.standbyPhase;
this.standbyPhase = null;
return;
}
if (this.phaseQueuePrependSpliceIndex > -1) {
this.clearPhaseQueueSplice();
}
if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length) {
const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
}
}
if (!this.phaseQueue.length) {
this.populatePhaseQueue();
// Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued
if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase
if (conditionalPhase?.[0]()) {
// If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]);
} else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
}
}
if (this.currentPhase) {
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
this.currentPhase.start();
}
}
overridePhase(phase: Phase): boolean {
if (this.standbyPhase) {
return false;
}
this.standbyPhase = this.currentPhase;
this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;");
phase.start();
return true;
}
/**
* Find a specific {@linkcode Phase} in the phase queue.
*
* @param phaseFilter filter function to use to find the wanted phase
* @returns the found phase or undefined if none found
*/
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
return this.phaseQueue.find(phaseFilter) as P;
}
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue[phaseIndex] = phase;
return true;
}
return false;
}
tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found.
* @param phaseFilter filter function
*/
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueuePrepend.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
* @param phase {@linkcode Phase} the phase to be added
* @param targetPhase {@linkcode Phase} the type of phase to search for in phaseQueue
* @returns boolean if a targetPhase was found and added
*/
prependToPhase(phase: Phase | Phase[], targetPhase: Constructor<Phase>): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase);
if (targetIndex !== -1) {
this.phaseQueue.splice(targetIndex, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Tries to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
* @param phase {@linkcode Phase} the phase(s) to be added
* @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue}
* @returns `true` if a `targetPhase` was found to append to
*/
appendToPhase(phase: Phase | Phase[], targetPhase: Constructor<Phase>): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase);
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
* @param message string for MessagePhase
* @param callbackDelay optional param for MessagePhase constructor
* @param prompt optional param for MessagePhase constructor
* @param promptDelay optional param for MessagePhase constructor
* @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue
*/
queueMessage(
message: string,
callbackDelay?: number | null,
prompt?: boolean | null,
promptDelay?: number | null,
defer?: boolean | null,
) {
const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay);
if (!defer) {
// adds to the end of PhaseQueuePrepend
this.unshiftPhase(phase);
} else {
//remember that pushPhase adds it to nextCommandPhaseQueue
this.pushPhase(phase);
}
}
/**
* Queues an ability bar flyout phase
* @param pokemon The pokemon who has the ability
* @param passive Whether the ability is a passive
* @param show Whether to show or hide the bar
*/
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
this.clearPhaseQueueSplice();
}
/**
* Hides the ability bar if it is currently visible
*/
public hideAbilityBar(): void {
if (this.abilityBar.isVisible()) {
this.unshiftPhase(new HideAbilityPhase());
}
}
/**
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order)
*/
populatePhaseQueue(): void {
if (this.nextCommandPhaseQueue.length) {
this.phaseQueue.push(...this.nextCommandPhaseQueue);
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
}
this.phaseQueue.push(new TurnInitPhase());
}
addMoney(amount: number): void { addMoney(amount: number): void {
this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER); this.money = Math.min(this.money + amount, Number.MAX_SAFE_INTEGER);
this.updateMoneyText(); this.updateMoneyText();
@ -2941,7 +2634,7 @@ export default class BattleScene extends SceneBase {
} }
} else if (!virtual) { } else if (!virtual) {
const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier); const defaultModifierType = getDefaultModifierTypeForTier(modifier.type.tier);
this.queueMessage( this.phaseManager.queueMessage(
i18next.t("battle:itemStackFull", { i18next.t("battle:itemStackFull", {
fullItemName: modifier.type.name, fullItemName: modifier.type.name,
itemName: defaultModifierType.name, itemName: defaultModifierType.name,
@ -3086,9 +2779,9 @@ export default class BattleScene extends SceneBase {
const removeOld = itemModifier.stackCount === 0; const removeOld = itemModifier.stackCount === 0;
if (!removeOld || !source || this.removeModifier(itemModifier, !source.isPlayer())) { if (!removeOld || !source || this.removeModifier(itemModifier, source.isEnemy())) {
const addModifier = () => { const addModifier = () => {
if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { if (!matchingModifier || this.removeModifier(matchingModifier, target.isEnemy())) {
if (target.isPlayer()) { if (target.isPlayer()) {
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
if (source && itemLost) { if (source && itemLost) {
@ -3492,17 +3185,17 @@ export default class BattleScene extends SceneBase {
} }
if (matchingFormChange) { if (matchingFormChange) {
let phase: Phase; let phase: Phase;
if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet) { if (pokemon.isPlayer() && !matchingFormChange.quiet) {
phase = new FormChangePhase(pokemon, matchingFormChange, modal); phase = this.phaseManager.create("FormChangePhase", pokemon, matchingFormChange, modal);
} else { } else {
phase = new QuietFormChangePhase(pokemon, matchingFormChange); phase = this.phaseManager.create("QuietFormChangePhase", pokemon, matchingFormChange);
} }
if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet && modal) { if (pokemon.isPlayer() && !matchingFormChange.quiet && modal) {
this.overridePhase(phase); this.phaseManager.overridePhase(phase);
} else if (delayed) { } else if (delayed) {
this.pushPhase(phase); this.phaseManager.pushPhase(phase);
} else { } else {
this.unshiftPhase(phase); this.phaseManager.unshiftPhase(phase);
} }
return true; return true;
} }
@ -3517,11 +3210,12 @@ export default class BattleScene extends SceneBase {
fieldAssets?: Phaser.GameObjects.Sprite[], fieldAssets?: Phaser.GameObjects.Sprite[],
delayed = false, delayed = false,
): boolean { ): boolean {
const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets); const phaseManager = this.phaseManager;
const phase: Phase = phaseManager.create("PokemonAnimPhase", battleAnimType, pokemon, fieldAssets);
if (delayed) { if (delayed) {
this.pushPhase(phase); phaseManager.pushPhase(phase);
} else { } else {
this.unshiftPhase(phase); phaseManager.unshiftPhase(phase);
} }
return true; return true;
} }
@ -3595,7 +3289,7 @@ export default class BattleScene extends SceneBase {
activePokemon = activePokemon.concat(this.getEnemyParty()); activePokemon = activePokemon.concat(this.getEnemyParty());
for (const p of activePokemon) { for (const p of activePokemon) {
keys.push(p.getSpriteKey(true)); keys.push(p.getSpriteKey(true));
if (p instanceof PlayerPokemon) { if (p.isPlayer()) {
keys.push(p.getBattleSpriteKey(true, true)); keys.push(p.getBattleSpriteKey(true, true));
} }
keys.push(p.species.getCryKey(p.formIndex)); keys.push(p.species.getCryKey(p.formIndex));
@ -3611,7 +3305,7 @@ export default class BattleScene extends SceneBase {
* @param pokemon The (enemy) pokemon * @param pokemon The (enemy) pokemon
*/ */
initFinalBossPhaseTwo(pokemon: Pokemon): void { initFinalBossPhaseTwo(pokemon: Pokemon): void {
if (pokemon instanceof EnemyPokemon && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) { if (pokemon.isEnemy() && pokemon.isBoss() && !pokemon.formIndex && pokemon.bossSegmentIndex < 1) {
this.fadeOutBgm(fixedInt(2000), false); this.fadeOutBgm(fixedInt(2000), false);
this.ui.showDialogue( this.ui.showDialogue(
battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin,
@ -3629,19 +3323,19 @@ export default class BattleScene extends SceneBase {
this.currentBattle.double = true; this.currentBattle.double = true;
const availablePartyMembers = this.getPlayerParty().filter(p => p.isAllowedInBattle()); const availablePartyMembers = this.getPlayerParty().filter(p => p.isAllowedInBattle());
if (availablePartyMembers.length > 1) { if (availablePartyMembers.length > 1) {
this.pushPhase(new ToggleDoublePositionPhase(true)); this.phaseManager.pushNew("ToggleDoublePositionPhase", true);
if (!availablePartyMembers[1].isOnField()) { if (!availablePartyMembers[1].isOnField()) {
this.pushPhase(new SummonPhase(1)); this.phaseManager.pushNew("SummonPhase", 1);
} }
} }
this.shiftPhase(); this.phaseManager.shiftPhase();
}, },
); );
return; return;
} }
this.shiftPhase(); this.phaseManager.shiftPhase();
} }
/** /**
@ -3753,10 +3447,10 @@ export default class BattleScene extends SceneBase {
if (exp) { if (exp) {
const partyMemberIndex = party.indexOf(expPartyMembers[pm]); const partyMemberIndex = party.indexOf(expPartyMembers[pm]);
this.unshiftPhase( this.phaseManager.unshiftPhase(
expPartyMembers[pm].isOnField() expPartyMembers[pm].isOnField()
? new ExpPhase(partyMemberIndex, exp) ? this.phaseManager.create("ExpPhase", partyMemberIndex, exp)
: new ShowPartyExpBarPhase(partyMemberIndex, exp), : this.phaseManager.create("ShowPartyExpBarPhase", partyMemberIndex, exp),
); );
} }
} }

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Command } from "./ui/command-ui-handler"; import type { Command } from "#enums/command";
import { import {
randomString, randomString,
getEnumValues, getEnumValues,
@ -8,8 +8,10 @@ import {
shiftCharCodes, shiftCharCodes,
randSeedItem, randSeedItem,
randInt, randInt,
randSeedFloat,
} from "#app/utils/common"; } from "#app/utils/common";
import Trainer, { TrainerVariant } from "./field/trainer"; import Trainer from "./field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import type { GameMode } from "./game-mode"; import type { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
@ -32,14 +34,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { BattlerIndex } from "#enums/battler-index";
export enum BattlerIndex {
ATTACKER = -1,
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2,
}
export interface TurnCommand { export interface TurnCommand {
command: Command; command: Command;
@ -150,7 +145,7 @@ export default class Battle {
randSeedGaussForLevel(value: number): number { randSeedGaussForLevel(value: number): number {
let rand = 0; let rand = 0;
for (let i = value; i > 0; i--) { for (let i = value; i > 0; i--) {
rand += Phaser.Math.RND.realInRange(0, 1); rand += randSeedFloat();
} }
return rand / value; return rand / value;
} }
@ -205,7 +200,7 @@ export default class Battle {
const message = i18next.t("battle:moneyPickedUp", { const message = i18next.t("battle:moneyPickedUp", {
moneyAmount: formattedMoneyAmount, moneyAmount: formattedMoneyAmount,
}); });
globalScene.queueMessage(message, undefined, true); globalScene.phaseManager.queueMessage(message, undefined, true);
globalScene.currentBattle.moneyScattered = 0; globalScene.currentBattle.moneyScattered = 0;
} }

View File

@ -1,3 +1,5 @@
import { SpeciesId } from "#enums/species-id";
/** The maximum size of the player's party */ /** The maximum size of the player's party */
export const PLAYER_PARTY_MAX_SIZE: number = 6; export const PLAYER_PARTY_MAX_SIZE: number = 6;
@ -17,3 +19,38 @@ export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180
/** The raw percentage power boost for type boost items*/ /** The raw percentage power boost for type boost items*/
export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20; export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20;
/**
* The default species that a new player can choose from
*/
export const defaultStarterSpecies: SpeciesId[] = [
SpeciesId.BULBASAUR,
SpeciesId.CHARMANDER,
SpeciesId.SQUIRTLE,
SpeciesId.CHIKORITA,
SpeciesId.CYNDAQUIL,
SpeciesId.TOTODILE,
SpeciesId.TREECKO,
SpeciesId.TORCHIC,
SpeciesId.MUDKIP,
SpeciesId.TURTWIG,
SpeciesId.CHIMCHAR,
SpeciesId.PIPLUP,
SpeciesId.SNIVY,
SpeciesId.TEPIG,
SpeciesId.OSHAWOTT,
SpeciesId.CHESPIN,
SpeciesId.FENNEKIN,
SpeciesId.FROAKIE,
SpeciesId.ROWLET,
SpeciesId.LITTEN,
SpeciesId.POPPLIO,
SpeciesId.GROOKEY,
SpeciesId.SCORBUNNY,
SpeciesId.SOBBLE,
SpeciesId.SPRIGATITO,
SpeciesId.FUECOCO,
SpeciesId.QUAXLY,
];
export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary

View File

@ -2,7 +2,7 @@ import { AbilityId } from "#enums/ability-id";
import type { AbAttrCondition } from "#app/@types/ability-types"; import type { AbAttrCondition } from "#app/@types/ability-types";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import i18next from "i18next"; import i18next from "i18next";
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import type { Constructor } from "#app/utils/common"; import type { Constructor } from "#app/utils/common";
export class Ability implements Localizable { export class Ability implements Localizable {

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,9 @@ import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult } from "#app/field/pokemon"; import { HitResult } from "#enums/hit-result";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { import {
BlockNonDirectDamageAbAttr, BlockNonDirectDamageAbAttr,
InfiltratorAbAttr, InfiltratorAbAttr,
@ -20,22 +20,14 @@ import {
applyOnLoseAbAttrs, applyOnLoseAbAttrs,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { CommonAnim } from "#enums/move-anims-common";
import i18next from "i18next"; import i18next from "i18next";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
export enum ArenaTagSide {
BOTH,
PLAYER,
ENEMY,
}
export abstract class ArenaTag { export abstract class ArenaTag {
constructor( constructor(
@ -54,7 +46,7 @@ export abstract class ArenaTag {
onRemove(_arena: Arena, quiet = false): void { onRemove(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
{ moveName: this.getMoveName() }, { moveName: this.getMoveName() },
@ -126,7 +118,7 @@ export class MistTag extends ArenaTag {
const source = globalScene.getPokemonById(this.sourceId); const source = globalScene.getPokemonById(this.sourceId);
if (!quiet && source) { if (!quiet && source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:mistOnAdd", { i18next.t("arenaTag:mistOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -161,7 +153,7 @@ export class MistTag extends ArenaTag {
cancelled.value = true; cancelled.value = true;
if (!simulated) { if (!simulated) {
globalScene.queueMessage(i18next.t("arenaTag:mistApply")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mistApply"));
} }
return true; return true;
@ -239,7 +231,7 @@ class ReflectTag extends WeakenMoveScreenTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -259,7 +251,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -282,7 +274,7 @@ class AuroraVeilTag extends WeakenMoveScreenTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -318,7 +310,7 @@ export class ConditionalProtectTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
{ moveName: super.getMoveName() }, { moveName: super.getMoveName() },
@ -355,7 +347,7 @@ export class ConditionalProtectTag extends ArenaTag {
isProtected.value = true; isProtected.value = true;
if (!simulated) { if (!simulated) {
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(); new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:conditionalProtectApply", { i18next.t("arenaTag:conditionalProtectApply", {
moveName: super.getMoveName(), moveName: super.getMoveName(),
pokemonNameWithAffix: getPokemonNameWithAffix(defender), pokemonNameWithAffix: getPokemonNameWithAffix(defender),
@ -381,7 +373,7 @@ export class ConditionalProtectTag extends ArenaTag {
*/ */
const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => { const QuickGuardConditionFunc: ProtectConditionFunc = (_arena, moveId) => {
const move = allMoves[moveId]; const move = allMoves[moveId];
const effectPhase = globalScene.getCurrentPhase(); const effectPhase = globalScene.phaseManager.getCurrentPhase();
if (effectPhase?.is("MoveEffectPhase")) { if (effectPhase?.is("MoveEffectPhase")) {
const attacker = effectPhase.getUserPokemon(); const attacker = effectPhase.getUserPokemon();
@ -458,7 +450,7 @@ class MatBlockTag extends ConditionalProtectTag {
if (this.sourceId) { if (this.sourceId) {
const source = globalScene.getPokemonById(this.sourceId); const source = globalScene.getPokemonById(this.sourceId);
if (source) { if (source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:matBlockOnAdd", { i18next.t("arenaTag:matBlockOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -517,7 +509,7 @@ export class NoCritTag extends ArenaTag {
/** Queues a message upon adding this effect to the field */ /** Queues a message upon adding this effect to the field */
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, { i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, {
moveName: this.getMoveName(), moveName: this.getMoveName(),
}), }),
@ -527,7 +519,7 @@ export class NoCritTag extends ArenaTag {
/** Queues a message upon removing this effect from the field */ /** Queues a message upon removing this effect from the field */
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct? const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:noCritOnRemove", { i18next.t("arenaTag:noCritOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined), pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
moveName: this.getMoveName(), moveName: this.getMoveName(),
@ -567,8 +559,8 @@ class WishTag extends ArenaTag {
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
const target = globalScene.getField()[this.battlerIndex]; const target = globalScene.getField()[this.battlerIndex];
if (target?.isActive(true)) { if (target?.isActive(true)) {
globalScene.queueMessage(this.triggerMessage); globalScene.phaseManager.queueMessage(this.triggerMessage);
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false)); globalScene.phaseManager.unshiftNew("PokemonHealPhase", target.getBattlerIndex(), this.healHp, null, true, false);
} }
} }
} }
@ -621,11 +613,11 @@ class MudSportTag extends WeakenMoveTypeTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:mudSportOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mudSportOnAdd"));
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:mudSportOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:mudSportOnRemove"));
} }
} }
@ -639,11 +631,11 @@ class WaterSportTag extends WeakenMoveTypeTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:waterSportOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:waterSportOnAdd"));
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:waterSportOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:waterSportOnRemove"));
} }
} }
@ -659,7 +651,7 @@ export class IonDelugeTag extends ArenaTag {
/** Queues an on-add message */ /** Queues an on-add message */
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd"));
} }
onRemove(_arena: Arena): void {} // Removes default on-remove message onRemove(_arena: Arena): void {} // Removes default on-remove message
@ -758,7 +750,7 @@ class SpikesTag extends ArenaTrapTag {
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) { if (!quiet && source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:spikesOnAdd", { i18next.t("arenaTag:spikesOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -781,7 +773,7 @@ class SpikesTag extends ArenaTrapTag {
const damageHpRatio = 1 / (10 - 2 * this.layers); const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:spikesActivateTrap", { i18next.t("arenaTag:spikesActivateTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
@ -811,7 +803,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) { if (!quiet && source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:toxicSpikesOnAdd", { i18next.t("arenaTag:toxicSpikesOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -834,7 +826,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
if (pokemon.isOfType(PokemonType.POISON)) { if (pokemon.isOfType(PokemonType.POISON)) {
this.neutralized = true; this.neutralized = true;
if (globalScene.arena.removeTag(this.tagType)) { if (globalScene.arena.removeTag(this.tagType)) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { i18next.t("arenaTag:toxicSpikesActivateTrapPoison", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName(), moveName: this.getMoveName(),
@ -891,8 +883,13 @@ export class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena); const ret = super.lapse(arena);
if (!ret) { if (!ret) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true), "MoveEffectPhase",
this.sourceId!,
[this.targetIndex],
allMoves[this.sourceMove!],
false,
true,
); // TODO: are those bangs correct? ); // TODO: are those bangs correct?
} }
@ -917,7 +914,7 @@ class StealthRockTag extends ArenaTrapTag {
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) { if (!quiet && source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stealthRockOnAdd", { i18next.t("arenaTag:stealthRockOnAdd", {
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
}), }),
@ -971,7 +968,7 @@ class StealthRockTag extends ArenaTrapTag {
} }
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stealthRockActivateTrap", { i18next.t("arenaTag:stealthRockActivateTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
@ -1001,7 +998,7 @@ class StickyWebTag extends ArenaTrapTag {
super.onAdd(arena); super.onAdd(arena);
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (!quiet && source) { if (!quiet && source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stickyWebOnAdd", { i18next.t("arenaTag:stickyWebOnAdd", {
moveName: this.getMoveName(), moveName: this.getMoveName(),
opponentDesc: source.getOpponentDescriptor(), opponentDesc: source.getOpponentDescriptor(),
@ -1020,25 +1017,24 @@ class StickyWebTag extends ArenaTrapTag {
} }
if (!cancelled.value) { if (!cancelled.value) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:stickyWebActivateTrap", { i18next.t("arenaTag:stickyWebActivateTrap", {
pokemonName: pokemon.getNameToRender(), pokemonName: pokemon.getNameToRender(),
}), }),
); );
const stages = new NumberHolder(-1); const stages = new NumberHolder(-1);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase( "StatStageChangePhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
false, false,
[Stat.SPD], [Stat.SPD],
stages.value, stages.value,
true, true,
false, false,
true, true,
null, null,
false, false,
true, true,
),
); );
return true; return true;
} }
@ -1074,7 +1070,7 @@ export class TrickRoomTag extends ArenaTag {
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
if (source) { if (source) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:trickRoomOnAdd", { i18next.t("arenaTag:trickRoomOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -1083,7 +1079,7 @@ export class TrickRoomTag extends ArenaTag {
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:trickRoomOnRemove"));
} }
} }
@ -1098,7 +1094,7 @@ export class GravityTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
globalScene.getField(true).forEach(pokemon => { globalScene.getField(true).forEach(pokemon => {
if (pokemon !== null) { if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.FLOATING); pokemon.removeTag(BattlerTagType.FLOATING);
@ -1111,7 +1107,7 @@ export class GravityTag extends ArenaTag {
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:gravityOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnRemove"));
} }
} }
@ -1127,7 +1123,7 @@ class TailwindTag extends ArenaTag {
onAdd(_arena: Arena, quiet = false): void { onAdd(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1136,31 +1132,33 @@ class TailwindTag extends ArenaTag {
const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable! const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? []; const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? [];
const phaseManager = globalScene.phaseManager;
for (const pokemon of party) { for (const pokemon of party) {
// Apply the CHARGED tag to party members with the WIND_POWER ability // Apply the CHARGED tag to party members with the WIND_POWER ability
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) { if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
pokemon.addTag(BattlerTagType.CHARGED); pokemon.addTag(BattlerTagType.CHARGED);
globalScene.queueMessage( phaseManager.queueMessage(
i18next.t("abilityTriggers:windPowerCharged", { i18next.t("abilityTriggers:windPowerCharged", {
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
moveName: this.getMoveName(), moveName: this.getMoveName(),
}), }),
); );
} }
// Raise attack by one stage if party member has WIND_RIDER ability // Raise attack by one stage if party member has WIND_RIDER ability
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
if (pokemon.hasAbility(AbilityId.WIND_RIDER)) { if (pokemon.hasAbility(AbilityId.WIND_RIDER)) {
globalScene.queueAbilityDisplay(pokemon, false, true); phaseManager.queueAbilityDisplay(pokemon, false, true);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true)); phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true);
globalScene.queueAbilityDisplay(pokemon, false, false); phaseManager.queueAbilityDisplay(pokemon, false, false);
} }
} }
} }
onRemove(_arena: Arena, quiet = false): void { onRemove(_arena: Arena, quiet = false): void {
if (!quiet) { if (!quiet) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1179,11 +1177,11 @@ class HappyHourTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:happyHourOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:happyHourOnAdd"));
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:happyHourOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:happyHourOnRemove"));
} }
} }
@ -1193,7 +1191,7 @@ class SafeguardTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1201,7 +1199,7 @@ class SafeguardTag extends ArenaTag {
} }
onRemove(_arena: Arena): void { onRemove(_arena: Arena): void {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1237,7 +1235,7 @@ class ImprisonTag extends ArenaTrapTag {
p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId); p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId);
} }
}); });
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battlerTags:imprisonOnAdd", { i18next.t("battlerTags:imprisonOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix: getPokemonNameWithAffix(source),
}), }),
@ -1294,7 +1292,7 @@ class FireGrassPledgeTag extends ArenaTag {
override onAdd(_arena: Arena): void { override onAdd(_arena: Arena): void {
// "A sea of fire enveloped your/the opposing team!" // "A sea of fire enveloped your/the opposing team!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1309,14 +1307,17 @@ class FireGrassPledgeTag extends ArenaTag {
.filter(pokemon => !pokemon.isOfType(PokemonType.FIRE) && !pokemon.switchOutStatus) .filter(pokemon => !pokemon.isOfType(PokemonType.FIRE) && !pokemon.switchOutStatus)
.forEach(pokemon => { .forEach(pokemon => {
// "{pokemonNameWithAffix} was hurt by the sea of fire!" // "{pokemonNameWithAffix} was hurt by the sea of fire!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:fireGrassPledgeLapse", { i18next.t("arenaTag:fireGrassPledgeLapse", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
// TODO: Replace this with a proper animation // TODO: Replace this with a proper animation
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM), "CommonAnimPhase",
pokemon.getBattlerIndex(),
pokemon.getBattlerIndex(),
CommonAnim.MAGMA_STORM,
); );
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
}); });
@ -1339,7 +1340,7 @@ class WaterFirePledgeTag extends ArenaTag {
override onAdd(_arena: Arena): void { override onAdd(_arena: Arena): void {
// "A rainbow appeared in the sky on your/the opposing team's side!" // "A rainbow appeared in the sky on your/the opposing team's side!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1373,7 +1374,7 @@ class GrassWaterPledgeTag extends ArenaTag {
override onAdd(_arena: Arena): void { override onAdd(_arena: Arena): void {
// "A swamp enveloped your/the opposing team!" // "A swamp enveloped your/the opposing team!"
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t( i18next.t(
`arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, `arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`,
), ),
@ -1394,7 +1395,7 @@ export class FairyLockTag extends ArenaTag {
} }
onAdd(_arena: Arena): void { onAdd(_arena: Arena): void {
globalScene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:fairyLockOnAdd"));
} }
} }
@ -1451,7 +1452,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
public override onRemove(_arena: Arena, quiet = false) { public override onRemove(_arena: Arena, quiet = false) {
this.beingRemoved = true; this.beingRemoved = true;
if (!quiet) { if (!quiet) {
globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); globalScene.phaseManager.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove"));
} }
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
@ -1472,7 +1473,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
private playActivationMessage(pokemon: Pokemon | null) { private playActivationMessage(pokemon: Pokemon | null) {
if (pokemon) { if (pokemon) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("arenaTag:neutralizingGasOnAdd", { i18next.t("arenaTag:neutralizingGasOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),

View File

@ -5988,6 +5988,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.FEZANDIPITI, SpeciesId.FEZANDIPITI,
SpeciesId.ARCHALUDON, SpeciesId.ARCHALUDON,
SpeciesId.IRON_CROWN, SpeciesId.IRON_CROWN,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_RATICATE, SpeciesId.ALOLA_RATICATE,
SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_RAICHU,
SpeciesId.ALOLA_SANDSLASH, SpeciesId.ALOLA_SANDSLASH,
@ -16248,6 +16249,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.CALYREX, SpeciesId.CALYREX,
SpeciesId.SANDY_SHOCKS, SpeciesId.SANDY_SHOCKS,
SpeciesId.IRON_JUGULIS, SpeciesId.IRON_JUGULIS,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_DUGTRIO, SpeciesId.ALOLA_DUGTRIO,
SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWPOKE,
SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWBRO,
@ -39466,6 +39468,8 @@ export const tmSpecies: TmSpecies = {
SpeciesId.FARFETCHD, SpeciesId.FARFETCHD,
SpeciesId.DODUO, SpeciesId.DODUO,
SpeciesId.DODRIO, SpeciesId.DODRIO,
SpeciesId.DEWGONG,
SpeciesId.GRIMER,
SpeciesId.MUK, SpeciesId.MUK,
SpeciesId.GASTLY, SpeciesId.GASTLY,
SpeciesId.HAUNTER, SpeciesId.HAUNTER,
@ -39477,6 +39481,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.CUBONE, SpeciesId.CUBONE,
SpeciesId.MAROWAK, SpeciesId.MAROWAK,
SpeciesId.HITMONLEE, SpeciesId.HITMONLEE,
SpeciesId.HITMONCHAN,
SpeciesId.LICKITUNG, SpeciesId.LICKITUNG,
SpeciesId.TANGELA, SpeciesId.TANGELA,
SpeciesId.GOLDEEN, SpeciesId.GOLDEEN,
@ -48806,6 +48811,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.GARGANACL, SpeciesId.GARGANACL,
SpeciesId.GLIMMET, SpeciesId.GLIMMET,
SpeciesId.GLIMMORA, SpeciesId.GLIMMORA,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_GEODUDE, SpeciesId.ALOLA_GEODUDE,
SpeciesId.ALOLA_GRAVELER, SpeciesId.ALOLA_GRAVELER,
SpeciesId.ALOLA_GOLEM, SpeciesId.ALOLA_GOLEM,
@ -53077,6 +53083,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.MIRAIDON, SpeciesId.MIRAIDON,
SpeciesId.ARCHALUDON, SpeciesId.ARCHALUDON,
SpeciesId.IRON_CROWN, SpeciesId.IRON_CROWN,
SpeciesId.TERAPAGOS,
[ [
SpeciesId.WORMADAM, SpeciesId.WORMADAM,
"trash", "trash",

View File

@ -1,111 +1,15 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
import type { BattlerIndex } from "../battle"; import type { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SubstituteTag } from "./battler-tags"; import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils/common"; import { isNullOrUndefined } from "../utils/common";
import Phaser from "phaser"; import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
export enum AnimFrameTarget {
USER,
TARGET,
GRAPHIC,
}
enum AnimFocus {
TARGET = 1,
USER,
USER_TARGET,
SCREEN,
}
enum AnimBlendType {
NORMAL,
ADD,
SUBTRACT,
}
export enum ChargeAnim {
FLY_CHARGING = 1000,
BOUNCE_CHARGING,
DIG_CHARGING,
FUTURE_SIGHT_CHARGING,
DIVE_CHARGING,
SOLAR_BEAM_CHARGING,
SHADOW_FORCE_CHARGING,
SKULL_BASH_CHARGING,
FREEZE_SHOCK_CHARGING,
SKY_DROP_CHARGING,
SKY_ATTACK_CHARGING,
ICE_BURN_CHARGING,
DOOM_DESIRE_CHARGING,
RAZOR_WIND_CHARGING,
PHANTOM_FORCE_CHARGING,
GEOMANCY_CHARGING,
SHADOW_BLADE_CHARGING,
SOLAR_BLADE_CHARGING,
BEAK_BLAST_CHARGING,
METEOR_BEAM_CHARGING,
ELECTRO_SHOT_CHARGING,
}
export enum CommonAnim {
USE_ITEM = 2000,
HEALTH_UP,
TERASTALLIZE,
POISON = 2010,
TOXIC,
PARALYSIS,
SLEEP,
FROZEN,
BURN,
CONFUSION,
ATTRACT,
BIND,
WRAP,
CURSE_NO_GHOST,
LEECH_SEED,
FIRE_SPIN,
PROTECT,
COVET,
WHIRLPOOL,
BIDE,
SAND_TOMB,
QUICK_GUARD,
WIDE_GUARD,
CURSE,
MAGMA_STORM,
CLAMP,
SNAP_TRAP,
THUNDER_CAGE,
INFESTATION,
ORDER_UP_CURLY,
ORDER_UP_DROOPY,
ORDER_UP_STRETCHY,
RAGING_BULL_FIRE,
RAGING_BULL_WATER,
SALT_CURE,
POWDER,
SUNNY = 2100,
RAIN,
SANDSTORM,
HAIL,
SNOW,
WIND,
HEAVY_RAIN,
HARSH_SUN,
STRONG_WINDS,
MISTY_TERRAIN = 2110,
ELECTRIC_TERRAIN,
GRASSY_TERRAIN,
PSYCHIC_TERRAIN,
LOCK_ON = 2120,
}
export class AnimConfig { export class AnimConfig {
public id: number; public id: number;
@ -531,7 +435,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
if (moveAnims.get(move) !== null) { if (moveAnims.get(move) !== null) {
const chargeAnimSource = allMoves[move].isChargingMove() const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move] ? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); : (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) { if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) {
return; return;
} }
@ -542,12 +446,11 @@ export function initMoveAnim(move: MoveId): Promise<void> {
} }
} else { } else {
moveAnims.set(move, null); moveAnims.set(move, null);
const defaultMoveAnim = const defaultMoveAnim = allMoves[move].is("AttackMove")
allMoves[move] instanceof AttackMove ? MoveId.TACKLE
? MoveId.TACKLE : allMoves[move].is("SelfStatusMove")
: allMoves[move] instanceof SelfStatusMove ? MoveId.FOCUS_ENERGY
? MoveId.FOCUS_ENERGY : MoveId.TAIL_WHIP;
: MoveId.TAIL_WHIP;
const fetchAnimAndResolve = (move: MoveId) => { const fetchAnimAndResolve = (move: MoveId) => {
globalScene globalScene
@ -570,7 +473,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
} }
const chargeAnimSource = allMoves[move].isChargingMove() const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move] ? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); : (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource) { if (chargeAnimSource) {
initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve()); initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve());
} else { } else {
@ -703,7 +606,7 @@ export function loadMoveAnimAssets(moveIds: MoveId[], startLoad?: boolean): Prom
for (const moveId of moveIds) { for (const moveId of moveIds) {
const chargeAnimSource = allMoves[moveId].isChargingMove() const chargeAnimSource = allMoves[moveId].isChargingMove()
? allMoves[moveId] ? allMoves[moveId]
: (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]); : (allMoves[moveId].getAttrs("DelayedAttackAttr")[0] ?? allMoves[moveId].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource) { if (chargeAnimSource) {
const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim); const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,13 @@
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon"; import { HitResult } from "#enums/hit-result";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
import { import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability";
DoubleBerryEffectAbAttr,
ReduceBerryUseThresholdAbAttr,
applyAbAttrs,
} from "./abilities/ability";
import i18next from "i18next"; import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat, type BattleStat } from "#app/enums/stat"; import { Stat, type BattleStat } from "#app/enums/stat";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export function getBerryName(berryType: BerryType): string { export function getBerryName(berryType: BerryType): string {
@ -79,23 +73,22 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
{ {
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),
hpHealed.value, hpHealed.value,
i18next.t("battle:hpHealBerry", { i18next.t("battle:hpHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(consumer), pokemonNameWithAffix: getPokemonNameWithAffix(consumer),
berryName: getBerryName(berryType), berryName: getBerryName(berryType),
}), }),
true, true,
),
); );
} }
break; break;
case BerryType.LUM: case BerryType.LUM:
{ {
if (consumer.status) { if (consumer.status) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
getStatusEffectHealText(consumer.status.effect, getPokemonNameWithAffix(consumer)), getStatusEffectHealText(consumer.status.effect, getPokemonNameWithAffix(consumer)),
); );
} }
@ -113,8 +106,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new NumberHolder(1); const statStages = new NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(consumer.getBattlerIndex(), true, [stat], statStages.value), "StatStageChangePhase",
consumer.getBattlerIndex(),
true,
[stat],
statStages.value,
); );
} }
break; break;
@ -130,8 +127,12 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
const randStat = randSeedInt(Stat.SPD, Stat.ATK); const randStat = randSeedInt(Stat.SPD, Stat.ATK);
const stages = new NumberHolder(2); const stages = new NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages); applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(consumer.getBattlerIndex(), true, [randStat], stages.value), "StatStageChangePhase",
consumer.getBattlerIndex(),
true,
[randStat],
stages.value,
); );
} }
break; break;
@ -144,7 +145,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
consumer.getMoveset().find(m => m.ppUsed < m.getMovePp()); consumer.getMoveset().find(m => m.ppUsed < m.getMovePp());
if (ppRestoreMove) { if (ppRestoreMove) {
ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0); ppRestoreMove.ppUsed = Math.max(ppRestoreMove.ppUsed - 10, 0);
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battle:ppHealBerry", { i18next.t("battle:ppHealBerry", {
pokemonNameWithAffix: getPokemonNameWithAffix(consumer), pokemonNameWithAffix: getPokemonNameWithAffix(consumer),
moveName: ppRestoreMove.getName(), moveName: ppRestoreMove.getName(),

View File

@ -2,17 +2,18 @@ import { BooleanHolder, type NumberHolder, randSeedItem } from "#app/utils/commo
import { deepCopy } from "#app/utils/data"; import { deepCopy } from "#app/utils/data";
import i18next from "i18next"; import i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data"; import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/constants";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "./moves/pokemon-move";
import type { FixedBattleConfig } from "#app/battle"; import type { FixedBattleConfig } from "#app/battle";
import { getRandomTrainerFunc } from "#app/battle"; import { getRandomTrainerFunc } from "#app/battle";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer from "#app/field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
@ -24,93 +25,12 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonFormChanges } from "./pokemon-forms";
import { pokemonEvolutions } from "./balance/pokemon-evolutions"; import { pokemonEvolutions } from "./balance/pokemon-evolutions";
import { ChallengeType } from "#enums/challenge-type";
import type { MoveSourceType } from "#enums/move-source-type";
/** A constant for the default max cost of the starting party before a run */ /** A constant for the default max cost of the starting party before a run */
const DEFAULT_PARTY_MAX_COST = 10; const DEFAULT_PARTY_MAX_COST = 10;
/**
* An enum for all the challenge types. The parameter entries on these describe the
* parameters to use when calling the applyChallenges function.
*/
export enum ChallengeType {
/**
* Challenges which modify what starters you can choose
* @see {@link Challenge.applyStarterChoice}
*/
STARTER_CHOICE,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPoints}
*/
STARTER_POINTS,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPointCost}
*/
STARTER_COST,
/**
* Challenges which modify your starters in some way
* @see {@link Challenge.applyStarterModify}
*/
STARTER_MODIFY,
/**
* Challenges which limit which pokemon you can have in battle.
* @see {@link Challenge.applyPokemonInBattle}
*/
POKEMON_IN_BATTLE,
/**
* Adds or modifies the fixed battles in a run
* @see {@link Challenge.applyFixedBattle}
*/
FIXED_BATTLES,
/**
* Modifies the effectiveness of Type matchups in battle
* @see {@linkcode Challenge.applyTypeEffectiveness}
*/
TYPE_EFFECTIVENESS,
/**
* Modifies what level the AI pokemon are. UNIMPLEMENTED.
*/
AI_LEVEL,
/**
* Modifies how many move slots the AI has. UNIMPLEMENTED.
*/
AI_MOVE_SLOTS,
/**
* Modifies if a pokemon has its passive. UNIMPLEMENTED.
*/
PASSIVE_ACCESS,
/**
* Modifies the game mode settings in some way. UNIMPLEMENTED.
*/
GAME_MODE_MODIFY,
/**
* Modifies what level AI pokemon can access a move. UNIMPLEMENTED.
*/
MOVE_ACCESS,
/**
* Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED.
*/
MOVE_WEIGHT,
/**
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT,
}
/**
* Used for challenge types that modify movesets, these denote the various sources of moves for pokemon.
*/
export enum MoveSourceType {
LEVEL_UP, // Currently unimplemented for move access
RELEARNER, // Relearner moves currently unimplemented
COMMON_TM,
GREAT_TM,
ULTRA_TM,
COMMON_EGG,
RARE_EGG,
}
/** /**
* A challenge object. Exists only to serve as a base class. * A challenge object. Exists only to serve as a base class.
*/ */

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { DexEntry, StarterDataEntry } from "#app/system/game-data"; import type { StarterDataEntry } from "#app/system/game-data";
import type { DexEntry } from "#app/@types/dex-data";
/** /**
* Stores data associated with a specific egg and the hatched pokemon * Stores data associated with a specific egg and the hatched pokemon

View File

@ -0,0 +1,58 @@
/**
* Module holding functions to apply move attributes.
* Must not import anything that is not a type.
*/
import type Pokemon from "#app/field/pokemon";
import type { default as Move, MoveAttr } from "./move";
import type { ChargingMove } from "#app/@types/move-types";
import type { MoveAttrFilter, MoveAttrString } from "#app/@types/move-types";
function applyMoveAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
args: any[],
): void {
move.attrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args));
}
function applyMoveChargeAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
args: any[],
): void {
move.chargeAttrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args));
}
export function applyMoveAttrs(
attrType: MoveAttrString,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args);
}
export function applyFilteredMoveAttrs(
attrFilter: MoveAttrFilter,
user: Pokemon,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function applyMoveChargeAttrs(
attrType: MoveAttrString,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
...args: any[]
): void {
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args);
}

View File

@ -1,5 +1,14 @@
import { MoveTarget } from "#enums/MoveTarget"; import type Pokemon from "#app/field/pokemon";
import type { BattlerIndex } from "#enums/battler-index";
import type { MoveId } from "#enums/move-id";
import type { MoveTargetSet, UserMoveConditionFunc } from "./move";
import type Move from "./move"; import type Move from "./move";
import { NumberHolder, isNullOrUndefined } from "#app/utils/common";
import { MoveTarget } from "#enums/MoveTarget";
import { PokemonType } from "#enums/pokemon-type";
import { allMoves } from "#app/data/data-lists";
import { applyMoveAttrs } from "./apply-attrs";
import { BattlerTagType } from "#enums/battler-tag-type";
/** /**
* Return whether the move targets the field * Return whether the move targets the field
@ -18,3 +27,88 @@ export function isFieldTargeted(move: Move): boolean {
} }
return false; return false;
} }
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet {
const variableTarget = new NumberHolder(0);
user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget));
let moveTarget: MoveTarget | undefined;
if (allMoves[move].hasAttr("VariableTargetAttr")) {
moveTarget = variableTarget.value;
} else if (replaceTarget !== undefined) {
moveTarget = replaceTarget;
} else if (move) {
moveTarget = allMoves[move].moveTarget;
} else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY;
}
const opponents = user.getOpponents(false);
let set: Pokemon[] = [];
let multiple = false;
const ally: Pokemon | undefined = user.getAlly();
switch (moveTarget) {
case MoveTarget.USER:
case MoveTarget.PARTY:
set = [user];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = !isNullOrUndefined(ally) ? opponents.concat([ally]) : opponents;
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents;
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [opponents[user.randBattleSeedInt(opponents.length)]];
break;
case MoveTarget.ATTACKER:
return { targets: [-1 as BattlerIndex], multiple: false };
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = !isNullOrUndefined(ally) ? [ally] : [];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = !isNullOrUndefined(ally) ? [user, ally] : [user];
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = (!isNullOrUndefined(ally) ? [user, ally] : [user]).concat(opponents);
multiple = true;
break;
case MoveTarget.CURSE:
{
const extraTargets = !isNullOrUndefined(ally) ? [ally] : [];
set = user.getTypes(true).includes(PokemonType.GHOST) ? opponents.concat(extraTargets) : [user];
}
break;
}
return {
targets: set
.filter(p => p?.isActive(true))
.map(p => p.getBattlerIndex())
.filter(t => t !== undefined),
multiple,
};
}
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) {
user.getMoveQueue().shift();
}
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
return true;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
import type Pokemon from "#app/field/pokemon";
import { toDmgValue } from "#app/utils/common";
import type { MoveId } from "#enums/move-id";
import { allMoves } from "../data-lists";
import type Move from "./move";
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks things like
* PP Ups recieved, PP used, etc.
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove {
public moveId: MoveId;
public ppUsed: number;
public ppUp: number;
public virtual: boolean;
/**
* If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform).
* This also nullifies all effects of `ppUp`.
*/
public maxPpOverride?: number;
constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) {
this.moveId = moveId;
this.ppUsed = ppUsed;
this.ppUp = ppUp;
this.virtual = virtual;
this.maxPpOverride = maxPpOverride;
}
/**
* Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
*
* @param pokemon - {@linkcode Pokemon} that would be using this move
* @param ignorePp - If `true`, skips the PP check
* @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
*/
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
return false;
}
if (this.getMove().name.endsWith(" (N)")) {
return false;
}
return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1;
}
getMove(): Move {
return allMoves[this.moveId];
}
/**
* Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp}
* @param count Amount of PP to use
*/
usePp(count = 1) {
this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp());
}
getMovePp(): number {
return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5);
}
getPpRatio(): number {
return 1 - this.ppUsed / this.getMovePp();
}
getName(): string {
return this.getMove().name;
}
/**
* Copies an existing move or creates a valid {@linkcode PokemonMove} object from json representing one
* @param source The data for the move to copy; can be a {@linkcode PokemonMove} or JSON object representing one
* @returns A valid {@linkcode PokemonMove} object
*/
static loadMove(source: PokemonMove | any): PokemonMove {
return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride);
}
}

View File

@ -19,7 +19,6 @@ import i18next from "i18next";
import type { IEggOptions } from "#app/data/egg"; import type { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types"; import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -182,7 +181,7 @@ export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.
async () => { async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
// Full heal party // Full heal party
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
const eggOptions: IEggOptions = { const eggOptions: IEggOptions = {
pulled: false, pulled: false,

View File

@ -7,7 +7,8 @@ import {
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -25,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils/common"; import { randInt } from "#app/utils/common";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { import {
applyModifierTypeToPlayerPokemon, applyModifierTypeToPlayerPokemon,
catchPokemon, catchPokemon,
@ -33,9 +34,8 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import i18next from "i18next"; import i18next from "i18next";
@ -237,8 +237,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
); );
}, },
}, },

View File

@ -22,7 +22,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next"; import i18next from "i18next";
@ -137,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterB
}) })
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Give the player a Shiny Charm // Give the player a Shiny Charm
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.SHINY_CHARM);
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
}) })
.build(), .build(),

View File

@ -35,7 +35,6 @@ import { BerryModifier } from "#app/modifier/modifier";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { PERMANENT_STATS, Stat } from "#enums/stat"; import { PERMANENT_STATS, Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
@ -237,8 +236,12 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.boss_enraged`); queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
); );
}; };
setEncounterRewards( setEncounterRewards(

View File

@ -24,9 +24,8 @@ import { TrainerType } from "#enums/trainer-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
@ -766,8 +765,10 @@ function doBugTypeMoveTutor(): Promise<void> {
// Option select complete, handle if they are learning a move // Option select complete, handle if they are learning a move
if (result && result.selectedOptionIndex < moveOptions.length) { if (result && result.selectedOptionIndex < moveOptions.length) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId), "LearnMovePhase",
result.selectedPokemonIndex,
moveOptions[result.selectedOptionIndex].moveId,
); );
} }

View File

@ -37,11 +37,11 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { Ability } from "#app/data/abilities/ability-class"; import { Ability } from "#app/data/abilities/ability-class";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";

View File

@ -1,4 +1,4 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -23,11 +23,10 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -176,13 +175,12 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`); queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase( "StatStageChangePhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
true, true,
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF], [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF],
1, 1,
),
); );
}, },
}, },
@ -245,8 +243,10 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
const onPokemonSelected = (pokemon: PlayerPokemon) => { const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), MoveId.REVELATION_DANCE), "LearnMovePhase",
globalScene.getPlayerParty().indexOf(pokemon),
MoveId.REVELATION_DANCE,
); );
// Play animation again to "learn" the dance // Play animation again to "learn" the dance

View File

@ -16,7 +16,6 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -165,7 +164,7 @@ export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withE
.withOptionPhase(async () => { .withOptionPhase(async () => {
// Give the player 5 Rogue Balls // Give the player 5 Rogue Balls
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.ROGUE_BALL);
// Start encounter with random legendary (7-10 starter strength) that has level additive // Start encounter with random legendary (7-10 starter strength) that has level additive
// If this is a mono-type challenge, always ensure the required type is filtered for // If this is a mono-type challenge, always ensure the required type is filtered for

View File

@ -29,7 +29,6 @@ import {
} from "#app/modifier/modifier"; } from "#app/modifier/modifier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedItem } from "#app/utils/common"; import { randSeedItem } from "#app/utils/common";
@ -65,10 +64,10 @@ const doEventReward = () => {
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
}); });
if (candidates.length > 0) { if (candidates.length > 0) {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]);
} else { } else {
// At max stacks, give a Voucher instead // At max stacks, give a Voucher instead
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER);
} }
} }
}; };
@ -181,7 +180,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN);
doEventReward(); doEventReward();
} }
@ -266,7 +265,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR);
doEventReward(); doEventReward();
} }
} else { } else {
@ -288,7 +287,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH);
doEventReward(); doEventReward();
} }
} }
@ -372,7 +371,7 @@ export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.with
); );
doEventReward(); doEventReward();
} else { } else {
globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM);
doEventReward(); doEventReward();
} }

View File

@ -7,7 +7,8 @@ import {
setEncounterExp, setEncounterExp,
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -24,9 +24,9 @@ import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
@ -44,7 +44,6 @@ import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Ability } from "#app/data/abilities/ability-class"; import { Ability } from "#app/data/abilities/ability-class";
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
@ -92,8 +91,12 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
gender: Gender.MALE, gender: Gender.MALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.SPD],
1,
); );
}, },
}, },
@ -103,8 +106,12 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
gender: Gender.FEMALE, gender: Gender.FEMALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.SPD],
1,
); );
}, },
}, },

View File

@ -32,7 +32,6 @@ import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
@ -76,7 +75,13 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.
queueEncounterMessage(`${namespace}:option.1.stat_boost`); queueEncounterMessage(`${namespace}:option.1.stat_boost`);
// Randomly boost 1 stat 2 stages // Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion // Cannot boost Spd, Acc, or Evasion
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2)); globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[randSeedInt(4, 1)],
2,
);
}, },
}, },
], ],

View File

@ -13,7 +13,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -25,9 +25,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { addPokeballOpenParticles } from "#app/field/anims"; import { addPokeballOpenParticles } from "#app/field/anims";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -411,13 +409,13 @@ function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise<void> {
pokemon.resetSummonData(); pokemon.resetSummonData();
globalScene.time.delayedCall(1000, () => { globalScene.time.delayedCall(1000, () => {
if (pokemon.isShiny()) { if (pokemon.isShiny()) {
globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); globalScene.phaseManager.unshiftNew("ShinySparklePhase", pokemon.getBattlerIndex());
} }
pokemon.resetTurnData(); pokemon.resetTurnData();
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); globalScene.phaseManager.pushNew("PostSummonPhase", pokemon.getBattlerIndex());
resolve(); resolve();
}); });
}, },

View File

@ -33,7 +33,8 @@ import {
} from "#app/utils/common"; } from "#app/utils/common";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { import {
HiddenAbilityRateBoosterModifier, HiddenAbilityRateBoosterModifier,

View File

@ -11,7 +11,7 @@ import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encount
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
const OPTION_1_REQUIRED_MOVE = MoveId.SURF; const OPTION_1_REQUIRED_MOVE = MoveId.SURF;
const OPTION_2_REQUIRED_MOVE = MoveId.FLY; const OPTION_2_REQUIRED_MOVE = MoveId.FLY;

View File

@ -17,7 +17,6 @@ import {
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -189,8 +188,8 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde
const allowedPokemon = globalScene.getPokemonAllowedInBattle(); const allowedPokemon = globalScene.getPokemonAllowedInBattle();
if (allowedPokemon.length === 0) { if (allowedPokemon.length === 0) {
// If there are no longer any legal pokemon in the party, game over. // If there are no longer any legal pokemon in the party, game over.
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
} else { } else {
// Show which Pokemon was KOed, then start battle against Gimmighoul // Show which Pokemon was KOed, then start battle against Gimmighoul
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);

View File

@ -29,8 +29,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
@ -276,7 +274,7 @@ async function summonSafariPokemon() {
const encounter = globalScene.currentBattle.mysteryEncounter!; const encounter = globalScene.currentBattle.mysteryEncounter!;
// Message pokemon remaining // Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining); encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
globalScene.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true); globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken // Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal // Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
@ -325,7 +323,7 @@ async function summonSafariPokemon() {
encounter.misc.pokemon = pokemon; encounter.misc.pokemon = pokemon;
encounter.misc.safariPokemonRemaining -= 1; encounter.misc.safariPokemonRemaining -= 1;
globalScene.unshiftPhase(new SummonPhase(0, false)); globalScene.phaseManager.unshiftNew("SummonPhase", 0, false);
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
@ -336,7 +334,7 @@ async function summonSafariPokemon() {
const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) { if (ivScannerModifier) {
globalScene.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex())); globalScene.phaseManager.pushNew("ScanIvsPhase", pokemon.getBattlerIndex());
} }
} }
@ -559,7 +557,7 @@ async function doEndTurn(cursorIndex: number) {
leaveEncounterWithoutBattle(true); leaveEncounterWithoutBattle(true);
} }
} else { } else {
globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000); globalScene.phaseManager.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect({ initSubsequentOptionSelect({
overrideOptions: safariZoneGameOptions, overrideOptions: safariZoneGameOptions,
startingCursorIndex: cursorIndex, startingCursorIndex: cursorIndex,

View File

@ -21,12 +21,12 @@ import {
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { AiType, PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { AiType } from "#enums/ai-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
@ -155,7 +155,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
async () => { async () => {
// Fall asleep waiting for Snorlax // Fall asleep waiting for Snorlax
// Full heal party // Full heal party
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
queueEncounterMessage(`${namespace}:option.2.rest_result`); queueEncounterMessage(`${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle(); leaveEncounterWithoutBattle();
}, },

View File

@ -27,7 +27,6 @@ import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { import {
@ -227,7 +226,13 @@ async function doBiomeTransitionDialogueAndBattleInit() {
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:boss_enraged`); queueEncounterMessage(`${namespace}:boss_enraged`);
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
);
}, },
}, },
], ],

View File

@ -658,8 +658,8 @@ function onGameOver() {
globalScene.playBgm(globalScene.arena.bgm); globalScene.playBgm(globalScene.arena.bgm);
// Clear any leftover battle phases // Clear any leftover battle phases
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); globalScene.phaseManager.clearPhaseQueueSplice();
// Return enemy Pokemon // Return enemy Pokemon
const pokemon = globalScene.getEnemyPokemon(); const pokemon = globalScene.getEnemyPokemon();

View File

@ -17,17 +17,16 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
@ -116,8 +115,12 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.2.stat_boost`); queueEncounterMessage(`${namespace}:option.2.stat_boost`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, [Stat.DEF, Stat.SPDEF], 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.DEF, Stat.SPDEF],
1,
); );
}, },
}, },

View File

@ -23,13 +23,10 @@ import { Nature } from "#enums/nature";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import i18next from "i18next"; import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -143,7 +140,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter
}, },
async () => { async () => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy // Refuse the challenge, they full heal the party and give the player a Rarer Candy
globalScene.unshiftPhase(new PartyHealPhase(true)); globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
setEncounterRewards({ setEncounterRewards({
guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY],
fillRemaining: false, fillRemaining: false,
@ -209,7 +206,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
for (const pokemon of playerField) { for (const pokemon of playerField) {
pokemon.lapseTag(BattlerTagType.COMMANDED); pokemon.lapseTag(BattlerTagType.COMMANDED);
} }
playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p))); playerField.forEach((_, p) => globalScene.phaseManager.unshiftNew("ReturnPhase", p));
for (const pokemon of globalScene.getPlayerParty()) { for (const pokemon of globalScene.getPlayerParty()) {
// Only trigger form change when Eiscue is in Noice form // Only trigger form change when Eiscue is in Noice form
@ -227,7 +224,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
} }
globalScene.unshiftPhase(new ShowTrainerPhase()); globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
// Hide the trainer and init next battle // Hide the trainer and init next battle
const trainer = globalScene.currentBattle.trainer; const trainer = globalScene.currentBattle.trainer;
// Unassign previous trainer from battle so it isn't destroyed before animation completes // Unassign previous trainer from battle so it isn't destroyed before animation completes

View File

@ -12,7 +12,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data"; import { AbilityAttr } from "#enums/ability-attr";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common"; import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common";
@ -25,7 +25,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import i18next from "i18next"; import i18next from "i18next";
import { getStatKey } from "#enums/stat"; import { getStatKey } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";

View File

@ -24,8 +24,8 @@ import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";

View File

@ -10,7 +10,7 @@ import {
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -29,13 +29,11 @@ import {
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { SelfStatusMove } from "#app/data/moves/move";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
@ -103,8 +101,12 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.stat_boost`); queueEncounterMessage(`${namespace}:option.1.stat_boost`);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1), "StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
); );
}, },
}, },
@ -172,7 +174,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
// Check what type of move the egg move is to determine target // Check what type of move the egg move is to determine target
const pokemonMove = new PokemonMove(eggMove); const pokemonMove = new PokemonMove(eggMove);
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER; const target = move.is("SelfStatusMove") ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
encounter.startOfBattleEffects.push({ encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,

View File

@ -16,7 +16,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common"; import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
@ -39,7 +39,7 @@ import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";

View File

@ -2,7 +2,9 @@ import { globalScene } from "#app/global-scene";
import { allAbilities } from "../data-lists"; import { allAbilities } from "../data-lists";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers";
import { FormChangeItem } from "#enums/form-change-item";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";

View File

@ -1,5 +1,6 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "../moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -20,11 +21,11 @@ import {
StatusEffectRequirement, StatusEffectRequirement,
WaveRangeRequirement, WaveRangeRequirement,
} from "./mystery-encounter-requirements"; } from "./mystery-encounter-requirements";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { GameModes } from "#app/game-mode"; import type { GameModes } from "#enums/game-modes";
import type { EncounterAnim } from "#enums/encounter-anims"; import type { EncounterAnim } from "#enums/encounter-anims";
import type { Challenges } from "#enums/challenges"; import type { Challenges } from "#enums/challenges";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -1,6 +1,6 @@
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -51,7 +51,7 @@ function getTextWithDialogueTokens(keyOrString: string): string | null {
*/ */
export function queueEncounterMessage(contentKey: string): void { export function queueEncounterMessage(contentKey: string): void {
const text: string | null = getEncounterText(contentKey); const text: string | null = getEncounterText(contentKey);
globalScene.queueMessage(text ?? "", null, true); globalScene.phaseManager.queueMessage(text ?? "", null, true);
} }
/** /**

View File

@ -1,5 +1,5 @@
import type Battle from "#app/battle"; import type Battle from "#app/battle";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
@ -8,9 +8,12 @@ import {
WEIGHT_INCREMENT_ON_SPAWN_MISS, WEIGHT_INCREMENT_ON_SPAWN_MISS,
} from "#app/data/mystery-encounters/mystery-encounters"; } from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { AiType, PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { AiType } from "#enums/ai-type";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, FieldPosition, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { FieldPosition } from "#enums/field-position";
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
import { import {
getPartyLuckValue, getPartyLuckValue,
@ -20,12 +23,6 @@ import {
modifierTypes, modifierTypes,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import {
MysteryEncounterBattlePhase,
MysteryEncounterBattleStartCleanupPhase,
MysteryEncounterPhase,
MysteryEncounterRewardsPhase,
} from "#app/phases/mystery-encounter-phases";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
@ -36,7 +33,8 @@ import type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type"; import type { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer from "#app/field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import type { Gender } from "#app/data/gender"; import type { Gender } from "#app/data/gender";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
@ -50,14 +48,7 @@ import type PokemonSpecies from "#app/data/pokemon-species";
import type { IEggOptions } from "#app/data/egg"; import type { IEggOptions } from "#app/data/egg";
import { Egg } from "#app/data/egg"; import { Egg } from "#app/data/egg";
import type { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { MovePhase } from "#app/phases/move-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
import { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -428,7 +419,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
console.log("Moveset:", moveset); console.log("Moveset:", moveset);
}); });
globalScene.pushPhase(new MysteryEncounterBattlePhase(partyConfig.disableSwitch)); globalScene.phaseManager.pushNew("MysteryEncounterBattlePhase", partyConfig.disableSwitch);
await Promise.all(loadEnemyAssets); await Promise.all(loadEnemyAssets);
battle.enemyParty.forEach((enemyPokemon_2, e_1) => { battle.enemyParty.forEach((enemyPokemon_2, e_1) => {
@ -480,7 +471,7 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes
} }
if (showMessage) { if (showMessage) {
if (changeValue < 0) { if (changeValue < 0) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("mysteryEncounterMessages:paid_money", { i18next.t("mysteryEncounterMessages:paid_money", {
amount: -changeValue, amount: -changeValue,
}), }),
@ -488,7 +479,7 @@ export function updatePlayerMoney(changeValue: number, playSound = true, showMes
true, true,
); );
} else { } else {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("mysteryEncounterMessages:receive_money", { i18next.t("mysteryEncounterMessages:receive_money", {
amount: changeValue, amount: changeValue,
}), }),
@ -767,9 +758,9 @@ export function setEncounterRewards(
} }
if (customShopRewards) { if (customShopRewards) {
globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); globalScene.phaseManager.unshiftNew("SelectModifierPhase", 0, undefined, customShopRewards);
} else { } else {
globalScene.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase")); globalScene.phaseManager.tryRemovePhase(p => p.is("MysteryEncounterRewardsPhase"));
} }
if (eggRewards) { if (eggRewards) {
@ -807,7 +798,7 @@ export function setEncounterExp(participantId: number | number[], baseExpValue:
const participantIds = Array.isArray(participantId) ? participantId : [participantId]; const participantIds = Array.isArray(participantId) ? participantId : [participantId];
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds))); globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));
return true; return true;
}; };
@ -829,7 +820,7 @@ export class OptionSelectSettings {
* @param optionSelectSettings * @param optionSelectSettings
*/ */
export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) { export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) {
globalScene.pushPhase(new MysteryEncounterPhase(optionSelectSettings)); globalScene.phaseManager.pushNew("MysteryEncounterPhase", optionSelectSettings);
} }
/** /**
@ -843,8 +834,8 @@ export function leaveEncounterWithoutBattle(
encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE,
) { ) {
globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode; globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.clearPhaseQueueSplice(); globalScene.phaseManager.clearPhaseQueueSplice();
handleMysteryEncounterVictory(addHealPhase); handleMysteryEncounterVictory(addHealPhase);
} }
@ -857,8 +848,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle()); const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) { if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
return; return;
} }
@ -869,8 +860,8 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
return; return;
} }
if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase);
globalScene.pushPhase(new EggLapsePhase()); globalScene.phaseManager.pushNew("EggLapsePhase");
} else if ( } else if (
!globalScene !globalScene
.getEnemyParty() .getEnemyParty()
@ -878,15 +869,15 @@ export function handleMysteryEncounterVictory(addHealPhase = false, doNotContinu
encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true), encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true),
) )
) { ) {
globalScene.pushPhase(new BattleEndPhase(true)); globalScene.phaseManager.pushNew("BattleEndPhase", true);
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
globalScene.pushPhase(new TrainerVictoryPhase()); globalScene.phaseManager.pushNew("TrainerVictoryPhase");
} }
if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase);
if (!encounter.doContinueEncounter) { if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters // Only lapse eggs once for multi-battle encounters
globalScene.pushPhase(new EggLapsePhase()); globalScene.phaseManager.pushNew("EggLapsePhase");
} }
} }
} }
@ -900,8 +891,8 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo
const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle()); const allowedPkm = globalScene.getPlayerParty().filter(pkm => pkm.isAllowedInBattle());
if (allowedPkm.length === 0) { if (allowedPkm.length === 0) {
globalScene.clearPhaseQueue(); globalScene.phaseManager.clearPhaseQueue();
globalScene.unshiftPhase(new GameOverPhase()); globalScene.phaseManager.unshiftNew("GameOverPhase");
return; return;
} }
@ -912,14 +903,14 @@ export function handleMysteryEncounterBattleFailed(addHealPhase = false, doNotCo
return; return;
} }
if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) { if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
globalScene.pushPhase(new BattleEndPhase(false)); globalScene.phaseManager.pushNew("BattleEndPhase", false);
} }
globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); globalScene.phaseManager.pushNew("MysteryEncounterRewardsPhase", addHealPhase);
if (!encounter.doContinueEncounter) { if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters // Only lapse eggs once for multi-battle encounters
globalScene.pushPhase(new EggLapsePhase()); globalScene.phaseManager.pushNew("EggLapsePhase");
} }
} }
@ -1004,12 +995,19 @@ export function handleMysteryEncounterBattleStartEffects() {
} else { } else {
source = globalScene.getEnemyField()[0]; source = globalScene.getEnemyField()[0];
} }
// @ts-ignore: source cannot be undefined globalScene.phaseManager.pushNew(
globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); "MovePhase",
// @ts-expect-error: source is guaranteed to be defined
source,
effect.targets,
effect.move,
effect.followUp,
effect.ignorePp,
);
}); });
// Pseudo turn end phase to reset flinch states, Endure, etc. // Pseudo turn end phase to reset flinch states, Endure, etc.
globalScene.pushPhase(new MysteryEncounterBattleStartCleanupPhase()); globalScene.phaseManager.pushNew("MysteryEncounterBattleStartCleanupPhase");
encounter.startOfBattleEffectsComplete = true; encounter.startOfBattleEffectsComplete = true;
} }

View File

@ -32,7 +32,6 @@ import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import type { PermanentStat } from "#enums/stat"; import type { PermanentStat } from "#enums/stat";
import { VictoryPhase } from "#app/phases/victory-phase";
import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
@ -675,7 +674,7 @@ export async function catchPokemon(
if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) { if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) {
globalScene.getEnemyParty().push(pokemon); globalScene.getEnemyParty().push(pokemon);
} }
globalScene.unshiftPhase(new VictoryPhase(pokemon.id, true)); globalScene.phaseManager.unshiftNew("VictoryPhase", pokemon.id, true);
globalScene.pokemonInfoContainer.hide(); globalScene.pokemonInfoContainer.hide();
if (pokeball) { if (pokeball) {
removePb(pokeball); removePb(pokeball);

View File

@ -1,139 +1,30 @@
import { PokemonFormChangeItemModifier } from "../modifier/modifier";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import type { Constructor, nil } from "#app/utils/common"; import type { Constructor, nil } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { TimeOfDay } from "#enums/time-of-day";
import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { Challenges } from "#app/enums/challenges";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { FormChangeItem } from "#enums/form-change-item";
export enum FormChangeItem { import {
NONE, MeloettaFormChangePostMoveTrigger,
SpeciesDefaultFormMatchTrigger,
ABOMASITE, SpeciesFormChangeAbilityTrigger,
ABSOLITE, SpeciesFormChangeActiveTrigger,
AERODACTYLITE, SpeciesFormChangeCompoundTrigger,
AGGRONITE, SpeciesFormChangeItemTrigger,
ALAKAZITE, SpeciesFormChangeLapseTeraTrigger,
ALTARIANITE, SpeciesFormChangeManualTrigger,
AMPHAROSITE, SpeciesFormChangeMoveLearnedTrigger,
AUDINITE, SpeciesFormChangePreMoveTrigger,
BANETTITE, SpeciesFormChangeRevertWeatherFormTrigger,
BEEDRILLITE, SpeciesFormChangeTeraTrigger,
BLASTOISINITE, type SpeciesFormChangeTrigger,
BLAZIKENITE, SpeciesFormChangeWeatherTrigger,
CAMERUPTITE, } from "./pokemon-forms/form-change-triggers";
CHARIZARDITE_X,
CHARIZARDITE_Y,
DIANCITE,
GALLADITE,
GARCHOMPITE,
GARDEVOIRITE,
GENGARITE,
GLALITITE,
GYARADOSITE,
HERACRONITE,
HOUNDOOMINITE,
KANGASKHANITE,
LATIASITE,
LATIOSITE,
LOPUNNITE,
LUCARIONITE,
MANECTITE,
MAWILITE,
MEDICHAMITE,
METAGROSSITE,
MEWTWONITE_X,
MEWTWONITE_Y,
PIDGEOTITE,
PINSIRITE,
RAYQUAZITE,
SABLENITE,
SALAMENCITE,
SCEPTILITE,
SCIZORITE,
SHARPEDONITE,
SLOWBRONITE,
STEELIXITE,
SWAMPERTITE,
TYRANITARITE,
VENUSAURITE,
BLUE_ORB = 50,
RED_ORB,
ADAMANT_CRYSTAL,
LUSTROUS_GLOBE,
GRISEOUS_CORE,
REVEAL_GLASS,
MAX_MUSHROOMS,
DARK_STONE,
LIGHT_STONE,
PRISON_BOTTLE,
RUSTED_SWORD,
RUSTED_SHIELD,
ICY_REINS_OF_UNITY,
SHADOW_REINS_OF_UNITY,
ULTRANECROZIUM_Z,
SHARP_METEORITE = 100,
HARD_METEORITE,
SMOOTH_METEORITE,
GRACIDEA,
SHOCK_DRIVE,
BURN_DRIVE,
CHILL_DRIVE,
DOUSE_DRIVE,
N_SOLARIZER,
N_LUNARIZER,
WELLSPRING_MASK,
HEARTHFLAME_MASK,
CORNERSTONE_MASK,
FIST_PLATE,
SKY_PLATE,
TOXIC_PLATE,
EARTH_PLATE,
STONE_PLATE,
INSECT_PLATE,
SPOOKY_PLATE,
IRON_PLATE,
FLAME_PLATE,
SPLASH_PLATE,
MEADOW_PLATE,
ZAP_PLATE,
MIND_PLATE,
ICICLE_PLATE,
DRACO_PLATE,
DREAD_PLATE,
PIXIE_PLATE,
BLANK_PLATE, // TODO: Find a potential use for this
LEGEND_PLATE, // TODO: Find a potential use for this
FIGHTING_MEMORY,
FLYING_MEMORY,
POISON_MEMORY,
GROUND_MEMORY,
ROCK_MEMORY,
BUG_MEMORY,
GHOST_MEMORY,
STEEL_MEMORY,
FIRE_MEMORY,
WATER_MEMORY,
GRASS_MEMORY,
ELECTRIC_MEMORY,
PSYCHIC_MEMORY,
ICE_MEMORY,
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
NORMAL_MEMORY, // TODO: Find a potential use for this
}
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean; export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void; export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void;
@ -214,347 +105,6 @@ export class SpeciesFormChangeCondition {
} }
} }
export abstract class SpeciesFormChangeTrigger {
public description = "";
canChange(_pokemon: Pokemon): boolean {
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return this instanceof triggerType;
}
}
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
public description: string = i18next.t("pokemonEvolutions:Forms.ability");
}
export class SpeciesFormChangeCompoundTrigger {
public description = "";
public triggers: SpeciesFormChangeTrigger[];
constructor(...triggers: SpeciesFormChangeTrigger[]) {
this.triggers = triggers;
this.description = this.triggers
.filter(trigger => trigger?.description?.length > 0)
.map(trigger => trigger.description)
.join(", ");
}
canChange(pokemon: Pokemon): boolean {
for (const trigger of this.triggers) {
if (!trigger.canChange(pokemon)) {
return false;
}
}
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return !!this.triggers.find(t => t.hasTriggerType(triggerType));
}
}
export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
public item: FormChangeItem;
public active: boolean;
constructor(item: FormChangeItem, active = true) {
super();
this.item = item;
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.item", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
})
: i18next.t("pokemonEvolutions:Forms.deactivateItem", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(
m =>
m instanceof PokemonFormChangeItemModifier &&
m.pokemonId === pokemon.id &&
m.formChangeItem === this.item &&
m.active === this.active,
);
}
}
export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger {
public timesOfDay: TimeOfDay[];
constructor(...timesOfDay: TimeOfDay[]) {
super();
this.timesOfDay = timesOfDay;
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
}
canChange(_pokemon: Pokemon): boolean {
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
}
}
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
public active: boolean;
constructor(active = false) {
super();
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.enter")
: i18next.t("pokemonEvolutions:Forms.leave");
}
canChange(pokemon: Pokemon): boolean {
return pokemon.isActive(true) === this.active;
}
}
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
public statusEffects: StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects];
}
this.statusEffects = statusEffects;
this.invert = invert;
this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
}
canChange(pokemon: Pokemon): boolean {
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
}
}
export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger {
public move: MoveId;
public known: boolean;
constructor(move: MoveId, known = true) {
super();
this.move = move;
this.known = known;
const moveKey = MoveId[this.move]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as unknown as string;
this.description = known
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
move: i18next.t(`move:${moveKey}.name`),
})
: i18next.t("pokemonEvolutions:Forms.moveForgotten", {
move: i18next.t(`move:${moveKey}.name`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known;
}
}
export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger {
public movePredicate: (m: MoveId) => boolean;
public used: boolean;
constructor(move: MoveId | ((m: MoveId) => boolean), used = true) {
super();
this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move;
this.used = used;
}
}
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.preMove");
canChange(pokemon: Pokemon): boolean {
const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
return !!command?.move && this.movePredicate(command.move.move) === this.used;
}
}
export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.postMove");
canChange(pokemon: Pokemon): boolean {
return (
pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used
);
}
}
export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger {
override canChange(pokemon: Pokemon): boolean {
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false;
}
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) {
return false;
}
return super.canChange(pokemon);
}
}
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
private formKey: string;
constructor(formKey: string) {
super();
this.formKey = formKey;
this.description = "";
}
canChange(pokemon: Pokemon): boolean {
return (
this.formKey ===
pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)]
.formKey
);
}
}
/**
* Class used for triggering form changes based on the user's Tera type.
* Used by Ogerpon and Terapagos.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
description = i18next.t("pokemonEvolutions:Forms.tera");
}
/**
* Class used for triggering form changes based on the user's lapsed Tera type.
* Used by Ogerpon and Terapagos.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {
description = i18next.t("pokemonEvolutions:Forms.teraLapse");
}
/**
* Class used for triggering form changes based on weather.
* Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change */
public ability: AbilityId;
/** The list of weathers that trigger the form change */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weather");
}
/**
* Checks if the Pokemon has the required ability and is in the correct weather while
* the weather or ability is also not suppressed.
* @param {Pokemon} pokemon the pokemon that is trying to do the form change
* @returns `true` if the Pokemon can change forms, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
return (
!isAbilitySuppressed &&
!isWeatherSuppressed &&
pokemon.hasAbility(this.ability) &&
this.weathers.includes(currentWeather)
);
}
}
/**
* Class used for reverting to the original form when the weather runs out
* or when the user loses the ability/is suppressed.
* Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change*/
public ability: AbilityId;
/** The list of weathers that will also trigger a form change to original form */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert");
}
/**
* Checks if the Pokemon has the required ability and the weather is one that will revert
* the Pokemon to its original form or the weather or ability is suppressed
* @param {Pokemon} pokemon the pokemon that is trying to do the form change
* @returns `true` if the Pokemon will revert to its original form, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
if (pokemon.hasAbility(this.ability, false, true)) {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
const summonDataAbility = pokemon.summonData.ability;
const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE;
if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) {
return true;
}
}
return false;
}
}
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) {
return i18next.t("battlePokemonForm:megaChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isGmax) {
return i18next.t("battlePokemonForm:gigantamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isEmax) {
return i18next.t("battlePokemonForm:eternamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
if (pokemon.getAbility().id === AbilityId.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
}
return i18next.t("battlePokemonForm:formChange", { preName });
}
/** /**
* Gives a condition for form changing checking if a species is registered as caught in the player's dex data. * Gives a condition for form changing checking if a species is registered as caught in the player's dex data.
* Used for fusion forms such as Kyurem and Necrozma. * Used for fusion forms such as Kyurem and Necrozma.

View File

@ -0,0 +1,348 @@
import i18next from "i18next";
import type { Constructor } from "#app/utils/common";
import type { TimeOfDay } from "#enums/time-of-day";
import type Pokemon from "#app/field/pokemon";
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import type { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { getPokemonNameWithAffix } from "#app/messages";
import { globalScene } from "#app/global-scene";
import { FormChangeItem } from "#enums/form-change-item";
import { AbilityId } from "#enums/ability-id";
import { Challenges } from "#enums/challenges";
import { MoveId } from "#enums/move-id";
import { SpeciesFormKey } from "#enums/species-form-key";
import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type";
export abstract class SpeciesFormChangeTrigger {
public description = "";
canChange(_pokemon: Pokemon): boolean {
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return this instanceof triggerType;
}
}
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
public description: string = i18next.t("pokemonEvolutions:Forms.ability");
}
export class SpeciesFormChangeCompoundTrigger {
public description = "";
public triggers: SpeciesFormChangeTrigger[];
constructor(...triggers: SpeciesFormChangeTrigger[]) {
this.triggers = triggers;
this.description = this.triggers
.filter(trigger => trigger?.description?.length > 0)
.map(trigger => trigger.description)
.join(", ");
}
canChange(pokemon: Pokemon): boolean {
for (const trigger of this.triggers) {
if (!trigger.canChange(pokemon)) {
return false;
}
}
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return !!this.triggers.find(t => t.hasTriggerType(triggerType));
}
}
export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
public item: FormChangeItem;
public active: boolean;
constructor(item: FormChangeItem, active = true) {
super();
this.item = item;
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.item", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
})
: i18next.t("pokemonEvolutions:Forms.deactivateItem", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(r => {
// Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier
const m = r as PokemonFormChangeItemModifier;
return (
"formChangeItem" in m &&
m.pokemonId === pokemon.id &&
m.formChangeItem === this.item &&
m.active === this.active
);
});
}
}
export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger {
public timesOfDay: TimeOfDay[];
constructor(...timesOfDay: TimeOfDay[]) {
super();
this.timesOfDay = timesOfDay;
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
}
canChange(_pokemon: Pokemon): boolean {
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
}
}
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
public active: boolean;
constructor(active = false) {
super();
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.enter")
: i18next.t("pokemonEvolutions:Forms.leave");
}
canChange(pokemon: Pokemon): boolean {
return pokemon.isActive(true) === this.active;
}
}
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
public statusEffects: StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects];
}
this.statusEffects = statusEffects;
this.invert = invert;
// this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
}
canChange(pokemon: Pokemon): boolean {
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
}
}
export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger {
public move: MoveId;
public known: boolean;
constructor(move: MoveId, known = true) {
super();
this.move = move;
this.known = known;
const moveKey = MoveId[this.move]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as unknown as string;
this.description = known
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
move: i18next.t(`move:${moveKey}.name`),
})
: i18next.t("pokemonEvolutions:Forms.moveForgotten", {
move: i18next.t(`move:${moveKey}.name`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known;
}
}
export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger {
public movePredicate: (m: MoveId) => boolean;
public used: boolean;
constructor(move: MoveId | ((m: MoveId) => boolean), used = true) {
super();
this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move;
this.used = used;
}
}
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.preMove");
canChange(pokemon: Pokemon): boolean {
const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
return !!command?.move && this.movePredicate(command.move.move) === this.used;
}
}
export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.postMove");
canChange(pokemon: Pokemon): boolean {
return (
pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used
);
}
}
export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger {
override canChange(pokemon: Pokemon): boolean {
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false;
}
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) {
return false;
}
return super.canChange(pokemon);
}
}
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
private formKey: string;
constructor(formKey: string) {
super();
this.formKey = formKey;
this.description = "";
}
canChange(pokemon: Pokemon): boolean {
return (
this.formKey ===
pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)]
.formKey
);
}
}
/**
* Class used for triggering form changes based on the user's Tera type.
* Used by Ogerpon and Terapagos.
*/
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {}
/**
* Class used for triggering form changes based on the user's lapsed Tera type.
* Used by Ogerpon and Terapagos.
*/
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {}
/**
* Class used for triggering form changes based on weather.
* Used by Castform and Cherrim.
*/
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change */
public ability: AbilityId;
/** The list of weathers that trigger the form change */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weather");
}
/**
* Checks if the Pokemon has the required ability and is in the correct weather while
* the weather or ability is also not suppressed.
* @param pokemon - The pokemon that is trying to do the form change
* @returns `true` if the Pokemon can change forms, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
return (
!isAbilitySuppressed &&
!isWeatherSuppressed &&
pokemon.hasAbility(this.ability) &&
this.weathers.includes(currentWeather)
);
}
}
/**
* Class used for reverting to the original form when the weather runs out
* or when the user loses the ability/is suppressed.
* Used by Castform and Cherrim.
*/
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change*/
public ability: AbilityId;
/** The list of weathers that will also trigger a form change to original form */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert");
}
/**
* Checks if the Pokemon has the required ability and the weather is one that will revert
* the Pokemon to its original form or the weather or ability is suppressed
* @param {Pokemon} pokemon the pokemon that is trying to do the form change
* @returns `true` if the Pokemon will revert to its original form, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
if (pokemon.hasAbility(this.ability, false, true)) {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
const summonDataAbility = pokemon.summonData.ability;
const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE;
if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) {
return true;
}
}
return false;
}
}
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) {
return i18next.t("battlePokemonForm:megaChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isGmax) {
return i18next.t("battlePokemonForm:gigantamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isEmax) {
return i18next.t("battlePokemonForm:eternamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
if (pokemon.getAbility().id === AbilityId.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
}
return i18next.t("battlePokemonForm:formChange", { preName });
}

View File

@ -1,4 +1,4 @@
import type { Localizable } from "#app/interfaces/locales"; import type { Localizable } from "#app/@types/locales";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
@ -7,8 +7,16 @@ import i18next from "i18next";
import type { AnySound } from "#app/battle-scene"; import type { AnySound } from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { DexAttr, type StarterMoveset } from "#app/system/game-data"; import type { StarterMoveset } from "#app/system/game-data";
import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils/common"; import { DexAttr } from "#enums/dex-attr";
import {
isNullOrUndefined,
capitalizeString,
randSeedInt,
randSeedGauss,
randSeedItem,
randSeedFloat,
} from "#app/utils/common";
import { uncatchableSpecies } from "#app/data/balance/biomes"; import { uncatchableSpecies } from "#app/data/balance/biomes";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate } from "#app/data/exp"; import { GrowthRate } from "#app/data/exp";
@ -750,7 +758,7 @@ export abstract class PokemonSpeciesForm {
let paletteColors: Map<number, number> = new Map(); let paletteColors: Map<number, number> = new Map();
const originalRandom = Math.random; const originalRandom = Math.random;
Math.random = Phaser.Math.RND.frac; Math.random = randSeedFloat;
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
@ -773,6 +781,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
readonly mythical: boolean; readonly mythical: boolean;
readonly species: string; readonly species: string;
readonly growthRate: GrowthRate; readonly growthRate: GrowthRate;
/** The chance (as a decimal) for this Species to be male, or `null` for genderless species */
readonly malePercent: number | null; readonly malePercent: number | null;
readonly genderDiffs: boolean; readonly genderDiffs: boolean;
readonly canChangeForm: boolean; readonly canChangeForm: boolean;
@ -889,7 +898,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
return Gender.GENDERLESS; return Gender.GENDERLESS;
} }
if (Phaser.Math.RND.realInRange(0, 1) <= this.malePercent) { if (randSeedFloat() <= this.malePercent) {
return Gender.MALE; return Gender.MALE;
} }
return Gender.FEMALE; return Gender.FEMALE;
@ -1138,7 +1147,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
} }
} }
if (noEvolutionChance === 1 || Phaser.Math.RND.realInRange(0, 1) < noEvolutionChance) { if (noEvolutionChance === 1 || randSeedFloat() <= noEvolutionChance) {
return this.speciesId; return this.speciesId;
} }
@ -2022,9 +2031,9 @@ export function initSpecies() {
new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, false, null, true), new PokemonForm("Normal", "", PokemonType.GROUND, null, 3.5, 950, AbilityId.DROUGHT, AbilityId.NONE, AbilityId.NONE, 670, 100, 150, 140, 100, 90, 90, 3, 0, 335, false, null, true),
new PokemonForm("Primal", "primal", PokemonType.GROUND, PokemonType.FIRE, 5, 999.7, AbilityId.DESOLATE_LAND, AbilityId.NONE, AbilityId.NONE, 770, 100, 180, 160, 150, 90, 90, 3, 0, 335), new PokemonForm("Primal", "primal", PokemonType.GROUND, PokemonType.FIRE, 5, 999.7, AbilityId.DESOLATE_LAND, AbilityId.NONE, AbilityId.NONE, 770, 100, 180, 160, 150, 90, 90, 3, 0, 335),
), ),
new PokemonSpecies(SpeciesId.RAYQUAZA, 3, false, true, false, "Sky High Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 45, 0, 340, GrowthRate.SLOW, null, false, true, new PokemonSpecies(SpeciesId.RAYQUAZA, 3, false, true, false, "Sky High Pokémon", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 45, 0, 340, false, null, true), new PokemonForm("Normal", "", PokemonType.DRAGON, PokemonType.FLYING, 7, 206.5, AbilityId.AIR_LOCK, AbilityId.NONE, AbilityId.NONE, 680, 105, 150, 90, 150, 90, 95, 3, 0, 340, false, null, true),
new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 10.8, 392, AbilityId.DELTA_STREAM, AbilityId.NONE, AbilityId.NONE, 780, 105, 180, 100, 180, 100, 115, 45, 0, 340), new PokemonForm("Mega", SpeciesFormKey.MEGA, PokemonType.DRAGON, PokemonType.FLYING, 10.8, 392, AbilityId.DELTA_STREAM, AbilityId.NONE, AbilityId.NONE, 780, 105, 180, 100, 180, 100, 115, 3, 0, 340),
), ),
new PokemonSpecies(SpeciesId.JIRACHI, 3, false, false, true, "Wish Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.3, 1.1, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.JIRACHI, 3, false, false, true, "Wish Pokémon", PokemonType.STEEL, PokemonType.PSYCHIC, 0.3, 1.1, AbilityId.SERENE_GRACE, AbilityId.NONE, AbilityId.NONE, 600, 100, 100, 100, 100, 100, 100, 3, 100, 300, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.DEOXYS, 3, false, false, true, "DNA Pokémon", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true, new PokemonSpecies(SpeciesId.DEOXYS, 3, false, false, true, "DNA Pokémon", PokemonType.PSYCHIC, null, 1.7, 60.8, AbilityId.PRESSURE, AbilityId.NONE, AbilityId.NONE, 600, 50, 150, 50, 150, 50, 150, 3, 0, 300, GrowthRate.SLOW, null, false, true,
@ -2270,10 +2279,10 @@ export function initSpecies() {
new PokemonSpecies(SpeciesId.WHIMSICOTT, 5, false, false, false, "Windveiled Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.7, 6.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 480, 60, 67, 85, 77, 75, 116, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.WHIMSICOTT, 5, false, false, false, "Windveiled Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 0.7, 6.6, AbilityId.PRANKSTER, AbilityId.INFILTRATOR, AbilityId.CHLOROPHYLL, 480, 60, 67, 85, 77, 75, 116, 75, 50, 168, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.PETILIL, 5, false, false, false, "Bulb Pokémon", PokemonType.GRASS, null, 0.5, 6.6, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 280, 45, 35, 50, 70, 50, 30, 190, 50, 56, GrowthRate.MEDIUM_FAST, 0, false), new PokemonSpecies(SpeciesId.PETILIL, 5, false, false, false, "Bulb Pokémon", PokemonType.GRASS, null, 0.5, 6.6, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 280, 45, 35, 50, 70, 50, 30, 190, 50, 56, GrowthRate.MEDIUM_FAST, 0, false),
new PokemonSpecies(SpeciesId.LILLIGANT, 5, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 1.1, 16.3, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 480, 70, 60, 75, 110, 75, 90, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false), new PokemonSpecies(SpeciesId.LILLIGANT, 5, false, false, false, "Flowering Pokémon", PokemonType.GRASS, null, 1.1, 16.3, AbilityId.CHLOROPHYLL, AbilityId.OWN_TEMPO, AbilityId.LEAF_GUARD, 480, 70, 60, 75, 110, 75, 90, 75, 50, 168, GrowthRate.MEDIUM_FAST, 0, false),
new PokemonSpecies(SpeciesId.BASCULIN, 5, false, false, false, "Hostile Pokémon", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonSpecies(SpeciesId.BASCULIN, 5, false, false, false, "Hostile Pokémon", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, GrowthRate.MEDIUM_FAST, 50, false, false,
new PokemonForm("Red-Striped Form", "red-striped", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, false, null, true), new PokemonForm("Red-Striped Form", "red-striped", PokemonType.WATER, null, 1, 18, AbilityId.RECKLESS, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true),
new PokemonForm("Blue-Striped Form", "blue-striped", PokemonType.WATER, null, 1, 18, AbilityId.ROCK_HEAD, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, false, null, true), new PokemonForm("Blue-Striped Form", "blue-striped", PokemonType.WATER, null, 1, 18, AbilityId.ROCK_HEAD, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true),
new PokemonForm("White-Striped Form", "white-striped", PokemonType.WATER, null, 1, 18, AbilityId.RATTLED, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 25, 50, 161, false, null, true), new PokemonForm("White-Striped Form", "white-striped", PokemonType.WATER, null, 1, 18, AbilityId.RATTLED, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 460, 70, 92, 65, 80, 55, 98, 190, 50, 161, false, null, true),
), ),
new PokemonSpecies(SpeciesId.SANDILE, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 0.7, 15.2, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 292, 50, 72, 35, 35, 35, 65, 180, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.SANDILE, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 0.7, 15.2, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 292, 50, 72, 35, 35, 35, 65, 180, 50, 58, GrowthRate.MEDIUM_SLOW, 50, false),
new PokemonSpecies(SpeciesId.KROKOROK, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 1, 33.4, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 351, 60, 82, 45, 45, 45, 74, 90, 50, 123, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.KROKOROK, 5, false, false, false, "Desert Croc Pokémon", PokemonType.GROUND, PokemonType.DARK, 1, 33.4, AbilityId.INTIMIDATE, AbilityId.MOXIE, AbilityId.ANGER_POINT, 351, 60, 82, 45, 45, 45, 74, 90, 50, 123, GrowthRate.MEDIUM_SLOW, 50, false),
@ -2740,10 +2749,10 @@ export function initSpecies() {
new PokemonSpecies(SpeciesId.TAPU_LELE, 7, true, false, false, "Land Spirit Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.2, 18.6, AbilityId.PSYCHIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 85, 75, 130, 115, 95, 3, 50, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.TAPU_LELE, 7, true, false, false, "Land Spirit Pokémon", PokemonType.PSYCHIC, PokemonType.FAIRY, 1.2, 18.6, AbilityId.PSYCHIC_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 85, 75, 130, 115, 95, 3, 50, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.TAPU_BULU, 7, true, false, false, "Land Spirit Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1.9, 45.5, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 130, 115, 85, 95, 75, 3, 50, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.TAPU_BULU, 7, true, false, false, "Land Spirit Pokémon", PokemonType.GRASS, PokemonType.FAIRY, 1.9, 45.5, AbilityId.GRASSY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 130, 115, 85, 95, 75, 3, 50, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.TAPU_FINI, 7, true, false, false, "Land Spirit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.3, 21.2, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 75, 115, 95, 130, 85, 3, 50, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.TAPU_FINI, 7, true, false, false, "Land Spirit Pokémon", PokemonType.WATER, PokemonType.FAIRY, 1.3, 21.2, AbilityId.MISTY_SURGE, AbilityId.NONE, AbilityId.TELEPATHY, 570, 70, 75, 115, 95, 130, 85, 3, 50, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.COSMOG, 7, true, false, false, "Nebula Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.1, AbilityId.UNAWARE, AbilityId.NONE, AbilityId.NONE, 200, 43, 29, 31, 29, 31, 37, 45, 0, 40, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.COSMOG, 7, true, false, false, "Nebula Pokémon", PokemonType.PSYCHIC, null, 0.2, 0.1, AbilityId.UNAWARE, AbilityId.NONE, AbilityId.NONE, 200, 43, 29, 31, 29, 31, 37, 3, 0, 40, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.COSMOEM, 7, true, false, false, "Protostar Pokémon", PokemonType.PSYCHIC, null, 0.1, 999.9, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 400, 43, 29, 131, 29, 131, 37, 45, 0, 140, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.COSMOEM, 7, true, false, false, "Protostar Pokémon", PokemonType.PSYCHIC, null, 0.1, 999.9, AbilityId.STURDY, AbilityId.NONE, AbilityId.NONE, 400, 43, 29, 131, 29, 131, 37, 3, 0, 140, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.SOLGALEO, 7, false, true, false, "Sunne Pokémon", PokemonType.PSYCHIC, PokemonType.STEEL, 3.4, 230, AbilityId.FULL_METAL_BODY, AbilityId.NONE, AbilityId.NONE, 680, 137, 137, 107, 113, 89, 97, 45, 0, 340, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.SOLGALEO, 7, false, true, false, "Sunne Pokémon", PokemonType.PSYCHIC, PokemonType.STEEL, 3.4, 230, AbilityId.FULL_METAL_BODY, AbilityId.NONE, AbilityId.NONE, 680, 137, 137, 107, 113, 89, 97, 3, 0, 340, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.LUNALA, 7, false, true, false, "Moone Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 4, 120, AbilityId.SHADOW_SHIELD, AbilityId.NONE, AbilityId.NONE, 680, 137, 113, 89, 137, 107, 97, 45, 0, 340, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.LUNALA, 7, false, true, false, "Moone Pokémon", PokemonType.PSYCHIC, PokemonType.GHOST, 4, 120, AbilityId.SHADOW_SHIELD, AbilityId.NONE, AbilityId.NONE, 680, 137, 113, 89, 137, 107, 97, 3, 0, 340, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.NIHILEGO, 7, true, false, false, "Parasite Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.2, 55.5, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 109, 53, 47, 127, 131, 103, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.NIHILEGO, 7, true, false, false, "Parasite Pokémon", PokemonType.ROCK, PokemonType.POISON, 1.2, 55.5, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 109, 53, 47, 127, 131, 103, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.BUZZWOLE, 7, true, false, false, "Swollen Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 2.4, 333.6, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 107, 139, 139, 53, 53, 79, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.BUZZWOLE, 7, true, false, false, "Swollen Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 2.4, 333.6, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 107, 139, 139, 53, 53, 79, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.PHEROMOSA, 7, true, false, false, "Lissome Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.8, 25, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 71, 137, 37, 137, 37, 151, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.PHEROMOSA, 7, true, false, false, "Lissome Pokémon", PokemonType.BUG, PokemonType.FIGHTING, 1.8, 25, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 71, 137, 37, 137, 37, 151, 45, 0, 285, GrowthRate.SLOW, null, false),
@ -2751,11 +2760,11 @@ export function initSpecies() {
new PokemonSpecies(SpeciesId.CELESTEELA, 7, true, false, false, "Launch Pokémon", PokemonType.STEEL, PokemonType.FLYING, 9.2, 999.9, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 97, 101, 103, 107, 101, 61, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.CELESTEELA, 7, true, false, false, "Launch Pokémon", PokemonType.STEEL, PokemonType.FLYING, 9.2, 999.9, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 97, 101, 103, 107, 101, 61, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.KARTANA, 7, true, false, false, "Drawn Sword Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.3, 0.1, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 59, 181, 131, 59, 31, 109, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.KARTANA, 7, true, false, false, "Drawn Sword Pokémon", PokemonType.GRASS, PokemonType.STEEL, 0.3, 0.1, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 59, 181, 131, 59, 31, 109, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", PokemonType.DARK, PokemonType.DRAGON, 5.5, 888, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false), new PokemonSpecies(SpeciesId.GUZZLORD, 7, true, false, false, "Junkivore Pokémon", PokemonType.DARK, PokemonType.DRAGON, 5.5, 888, AbilityId.BEAST_BOOST, AbilityId.NONE, AbilityId.NONE, 570, 223, 101, 53, 97, 53, 43, 45, 0, 285, GrowthRate.SLOW, null, false),
new PokemonSpecies(SpeciesId.NECROZMA, 7, false, true, false, "Prism Pokémon", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 255, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonSpecies(SpeciesId.NECROZMA, 7, false, true, false, "Prism Pokémon", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, GrowthRate.SLOW, null, false, false,
new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 255, 0, 300, false, null, true), new PokemonForm("Normal", "", PokemonType.PSYCHIC, null, 2.4, 230, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 600, 97, 107, 101, 127, 89, 79, 3, 0, 300, false, null, true),
new PokemonForm("Dusk Mane", "dusk-mane", PokemonType.PSYCHIC, PokemonType.STEEL, 3.8, 460, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 157, 127, 113, 109, 77, 255, 0, 340), new PokemonForm("Dusk Mane", "dusk-mane", PokemonType.PSYCHIC, PokemonType.STEEL, 3.8, 460, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 157, 127, 113, 109, 77, 3, 0, 340),
new PokemonForm("Dawn Wings", "dawn-wings", PokemonType.PSYCHIC, PokemonType.GHOST, 4.2, 350, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 113, 109, 157, 127, 77, 255, 0, 340), new PokemonForm("Dawn Wings", "dawn-wings", PokemonType.PSYCHIC, PokemonType.GHOST, 4.2, 350, AbilityId.PRISM_ARMOR, AbilityId.NONE, AbilityId.NONE, 680, 97, 113, 109, 157, 127, 77, 3, 0, 340),
new PokemonForm("Ultra", "ultra", PokemonType.PSYCHIC, PokemonType.DRAGON, 7.5, 230, AbilityId.NEUROFORCE, AbilityId.NONE, AbilityId.NONE, 754, 97, 167, 97, 167, 97, 129, 255, 0, 377), new PokemonForm("Ultra", "ultra", PokemonType.PSYCHIC, PokemonType.DRAGON, 7.5, 230, AbilityId.NEUROFORCE, AbilityId.NONE, AbilityId.NONE, 754, 97, 167, 97, 167, 97, 129, 3, 0, 377),
), ),
new PokemonSpecies(SpeciesId.MAGEARNA, 7, false, false, true, "Artificial Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false, new PokemonSpecies(SpeciesId.MAGEARNA, 7, false, false, true, "Artificial Pokémon", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, GrowthRate.SLOW, null, false, false,
new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true), new PokemonForm("Normal", "", PokemonType.STEEL, PokemonType.FAIRY, 1, 80.5, AbilityId.SOUL_HEART, AbilityId.NONE, AbilityId.NONE, 600, 80, 95, 115, 130, 115, 65, 3, 0, 300, false, null, true),
@ -2964,15 +2973,15 @@ export function initSpecies() {
new PokemonForm("Ice", "ice", PokemonType.PSYCHIC, PokemonType.ICE, 2.4, 809.1, AbilityId.AS_ONE_GLASTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340), new PokemonForm("Ice", "ice", PokemonType.PSYCHIC, PokemonType.ICE, 2.4, 809.1, AbilityId.AS_ONE_GLASTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 165, 150, 85, 130, 50, 3, 100, 340),
new PokemonForm("Shadow", "shadow", PokemonType.PSYCHIC, PokemonType.GHOST, 2.4, 53.6, AbilityId.AS_ONE_SPECTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340), new PokemonForm("Shadow", "shadow", PokemonType.PSYCHIC, PokemonType.GHOST, 2.4, 53.6, AbilityId.AS_ONE_SPECTRIER, AbilityId.NONE, AbilityId.NONE, 680, 100, 85, 80, 165, 100, 150, 3, 100, 340),
), ),
new PokemonSpecies(SpeciesId.WYRDEER, 8, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.8, 95.1, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 135, 50, 263, GrowthRate.SLOW, 50, false), new PokemonSpecies(SpeciesId.WYRDEER, 8, false, false, false, "Big Horn Pokémon", PokemonType.NORMAL, PokemonType.PSYCHIC, 1.8, 95.1, AbilityId.INTIMIDATE, AbilityId.FRISK, AbilityId.SAP_SIPPER, 525, 103, 105, 72, 105, 75, 65, 45, 50, 263, GrowthRate.SLOW, 50, false),
new PokemonSpecies(SpeciesId.KLEAVOR, 8, false, false, false, "Axe Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.8, 89, AbilityId.SWARM, AbilityId.SHEER_FORCE, AbilityId.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 115, 50, 175, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.KLEAVOR, 8, false, false, false, "Axe Pokémon", PokemonType.BUG, PokemonType.ROCK, 1.8, 89, AbilityId.SWARM, AbilityId.SHEER_FORCE, AbilityId.SHARPNESS, 500, 70, 135, 95, 45, 70, 85, 15, 50, 175, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.URSALUNA, 8, false, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.4, 290, AbilityId.GUTS, AbilityId.BULLETPROOF, AbilityId.UNNERVE, 550, 130, 140, 105, 45, 80, 50, 75, 50, 275, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.URSALUNA, 8, false, false, false, "Peat Pokémon", PokemonType.GROUND, PokemonType.NORMAL, 2.4, 290, AbilityId.GUTS, AbilityId.BULLETPROOF, AbilityId.UNNERVE, 550, 130, 140, 105, 45, 80, 50, 20, 50, 275, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.BASCULEGION, 8, false, false, false, "Big Fish Pokémon", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 135, 50, 265, GrowthRate.MEDIUM_FAST, 50, false, false, new PokemonSpecies(SpeciesId.BASCULEGION, 8, false, false, false, "Big Fish Pokémon", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, GrowthRate.MEDIUM_FAST, 50, false, false,
new PokemonForm("Male", "male", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 135, 50, 265, false, "", true), new PokemonForm("Male", "male", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 112, 65, 80, 75, 78, 45, 50, 265, false, "", true),
new PokemonForm("Female", "female", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 92, 65, 100, 75, 78, 135, 50, 265, false, null, true), new PokemonForm("Female", "female", PokemonType.WATER, PokemonType.GHOST, 3, 110, AbilityId.SWIFT_SWIM, AbilityId.ADAPTABILITY, AbilityId.MOLD_BREAKER, 530, 120, 92, 65, 100, 75, 78, 45, 50, 265, false, null, true),
), ),
new PokemonSpecies(SpeciesId.SNEASLER, 8, false, false, false, "Free Climb Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 1.3, 43, AbilityId.PRESSURE, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, 510, 80, 130, 60, 40, 80, 120, 135, 50, 102, GrowthRate.MEDIUM_SLOW, 50, false), new PokemonSpecies(SpeciesId.SNEASLER, 8, false, false, false, "Free Climb Pokémon", PokemonType.FIGHTING, PokemonType.POISON, 1.3, 43, AbilityId.PRESSURE, AbilityId.UNBURDEN, AbilityId.POISON_TOUCH, 510, 80, 130, 60, 40, 80, 120, 20, 50, 102, GrowthRate.MEDIUM_SLOW, 50, false),
new PokemonSpecies(SpeciesId.OVERQWIL, 8, false, false, false, "Pin Cluster Pokémon", PokemonType.DARK, PokemonType.POISON, 2.5, 60.5, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 510, 85, 115, 95, 65, 65, 85, 135, 50, 179, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(SpeciesId.OVERQWIL, 8, false, false, false, "Pin Cluster Pokémon", PokemonType.DARK, PokemonType.POISON, 2.5, 60.5, AbilityId.POISON_POINT, AbilityId.SWIFT_SWIM, AbilityId.INTIMIDATE, 510, 85, 115, 95, 65, 65, 85, 45, 50, 179, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(SpeciesId.ENAMORUS, 8, true, false, false, "Love-Hate Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, GrowthRate.SLOW, 0, false, true, new PokemonSpecies(SpeciesId.ENAMORUS, 8, true, false, false, "Love-Hate Pokémon", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, GrowthRate.SLOW, 0, false, true,
new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, false, null, true), new PokemonForm("Incarnate Forme", "incarnate", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.CUTE_CHARM, AbilityId.NONE, AbilityId.CONTRARY, 580, 74, 115, 70, 135, 80, 106, 3, 50, 116, false, null, true),
new PokemonForm("Therian Forme", "therian", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 580, 74, 115, 110, 135, 100, 46, 3, 50, 116), new PokemonForm("Therian Forme", "therian", PokemonType.FAIRY, PokemonType.FLYING, 1.6, 48, AbilityId.OVERCOAT, AbilityId.NONE, AbilityId.OVERCOAT, 580, 74, 115, 110, 135, 100, 46, 3, 50, 116),

View File

@ -1,8 +1,7 @@
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import type Move from "./moves/move"; import type Move from "./moves/move";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { ProtectAttr } from "./moves/move"; import type { BattlerIndex } from "#enums/battler-index";
import type { BattlerIndex } from "#app/battle";
import i18next from "i18next"; import i18next from "i18next";
export enum TerrainType { export enum TerrainType {
@ -55,7 +54,7 @@ export class Terrain {
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
switch (this.terrainType) { switch (this.terrainType) {
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) { if (!move.hasAttr("ProtectAttr")) {
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return ( return (
move.getPriority(user) > 0 && move.getPriority(user) > 0 &&

View File

@ -1,7 +1,7 @@
import { startingWave } from "#app/starting-wave"; import { startingWave } from "#app/starting-wave";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { GameModes } from "#app/game-mode"; import { GameModes } from "#enums/game-modes";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
export class TrainerPartyTemplate { export class TrainerPartyTemplate {

View File

@ -1,4 +1,4 @@
import type { TrainerTierPools } from "#app/data/trainers/typedefs"; import type { TrainerTierPools } from "#app/@types/trainer-funcs";
import { TrainerPoolTier } from "#enums/trainer-pool-tier"; import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";

View File

@ -1,12 +1,12 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "../moves/pokemon-move";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
import { doubleBattleDialogue } from "#app/data/dialogue"; import { doubleBattleDialogue } from "#app/data/dialogue";
import { TrainerVariant } from "#app/field/trainer"; import { TrainerVariant } from "#enums/trainer-variant";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
@ -48,7 +48,7 @@ import type {
TrainerTierPools, TrainerTierPools,
TrainerConfigs, TrainerConfigs,
PartyMemberFuncs, PartyMemberFuncs,
} from "./typedefs"; } from "../../@types/trainer-funcs";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */ /** Minimum BST for Pokemon generated onto the Elite Four's teams */
const ELITE_FOUR_MINIMUM_BST = 460; const ELITE_FOUR_MINIMUM_BST = 460;

View File

@ -4,7 +4,6 @@ import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type Move from "./moves/move"; import type Move from "./moves/move";
import { AttackMove } from "./moves/move";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { SuppressWeatherEffectAbAttr } from "./abilities/ability"; import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
import { TerrainType, getTerrainName } from "./terrain"; import { TerrainType, getTerrainName } from "./terrain";
@ -95,9 +94,9 @@ export class Weather {
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
return move instanceof AttackMove && moveType === PokemonType.WATER; return move.is("AttackMove") && moveType === PokemonType.WATER;
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return move instanceof AttackMove && moveType === PokemonType.FIRE; return move.is("AttackMove") && moveType === PokemonType.FIRE;
} }
return false; return false;

View File

@ -1,17 +0,0 @@
export function getData() {
const dataStr = localStorage.getItem("data");
if (!dataStr) {
return null;
}
return JSON.parse(atob(dataStr), (k, v) =>
k.endsWith("Attr") && !["natureAttr", "abilityAttr", "passiveAttr"].includes(k) ? BigInt(v) : v,
);
}
export function getSession() {
const sessionStr = localStorage.getItem("sessionData");
if (!sessionStr) {
return null;
}
return JSON.parse(atob(sessionStr));
}

11
src/enums/ability-attr.ts Normal file
View File

@ -0,0 +1,11 @@
/**
* Not to be confused with an Ability Attribute.
* This is an object literal storing the slot that an ability can occupy.
*/
export const AbilityAttr = Object.freeze({
ABILITY_1: 1,
ABILITY_2: 2,
ABILITY_HIDDEN: 4,
});
export type AbilityAttr = typeof AbilityAttr[keyof typeof AbilityAttr];

5
src/enums/ai-type.ts Normal file
View File

@ -0,0 +1,5 @@
export enum AiType {
RANDOM,
SMART_RANDOM,
SMART
}

View File

@ -0,0 +1,5 @@
export enum ArenaTagSide {
BOTH,
PLAYER,
ENEMY
}

View File

@ -0,0 +1,7 @@
export enum BattlerIndex {
ATTACKER = -1,
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2
}

View File

@ -0,0 +1,12 @@
export enum BattlerTagLapseType {
FAINT,
MOVE,
PRE_MOVE,
AFTER_MOVE,
MOVE_EFFECT,
TURN_END,
HIT,
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
AFTER_HIT,
CUSTOM
}

View File

@ -0,0 +1,69 @@
/**
* An enum for all the challenge types. The parameter entries on these describe the
* parameters to use when calling the applyChallenges function.
*/
export enum ChallengeType {
/**
* Challenges which modify what starters you can choose
* @see {@link Challenge.applyStarterChoice}
*/
STARTER_CHOICE,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPoints}
*/
STARTER_POINTS,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPointCost}
*/
STARTER_COST,
/**
* Challenges which modify your starters in some way
* @see {@link Challenge.applyStarterModify}
*/
STARTER_MODIFY,
/**
* Challenges which limit which pokemon you can have in battle.
* @see {@link Challenge.applyPokemonInBattle}
*/
POKEMON_IN_BATTLE,
/**
* Adds or modifies the fixed battles in a run
* @see {@link Challenge.applyFixedBattle}
*/
FIXED_BATTLES,
/**
* Modifies the effectiveness of Type matchups in battle
* @see {@linkcode Challenge.applyTypeEffectiveness}
*/
TYPE_EFFECTIVENESS,
/**
* Modifies what level the AI pokemon are. UNIMPLEMENTED.
*/
AI_LEVEL,
/**
* Modifies how many move slots the AI has. UNIMPLEMENTED.
*/
AI_MOVE_SLOTS,
/**
* Modifies if a pokemon has its passive. UNIMPLEMENTED.
*/
PASSIVE_ACCESS,
/**
* Modifies the game mode settings in some way. UNIMPLEMENTED.
*/
GAME_MODE_MODIFY,
/**
* Modifies what level AI pokemon can access a move. UNIMPLEMENTED.
*/
MOVE_ACCESS,
/**
* Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED.
*/
MOVE_WEIGHT,
/**
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT
}

7
src/enums/command.ts Normal file
View File

@ -0,0 +1,7 @@
export enum Command {
FIGHT = 0,
BALL,
POKEMON,
RUN,
TERA
}

11
src/enums/dex-attr.ts Normal file
View File

@ -0,0 +1,11 @@
export const DexAttr = Object.freeze({
NON_SHINY: 1n,
SHINY: 2n,
MALE: 4n,
FEMALE: 8n,
DEFAULT_VARIANT: 16n,
VARIANT_2: 32n,
VARIANT_3: 64n,
DEFAULT_FORM: 128n,
});
export type DexAttr = typeof DexAttr[keyof typeof DexAttr];

View File

@ -0,0 +1,5 @@
export enum FieldPosition {
CENTER,
LEFT,
RIGHT
}

View File

@ -0,0 +1,119 @@
export enum FormChangeItem {
NONE,
ABOMASITE,
ABSOLITE,
AERODACTYLITE,
AGGRONITE,
ALAKAZITE,
ALTARIANITE,
AMPHAROSITE,
AUDINITE,
BANETTITE,
BEEDRILLITE,
BLASTOISINITE,
BLAZIKENITE,
CAMERUPTITE,
CHARIZARDITE_X,
CHARIZARDITE_Y,
DIANCITE,
GALLADITE,
GARCHOMPITE,
GARDEVOIRITE,
GENGARITE,
GLALITITE,
GYARADOSITE,
HERACRONITE,
HOUNDOOMINITE,
KANGASKHANITE,
LATIASITE,
LATIOSITE,
LOPUNNITE,
LUCARIONITE,
MANECTITE,
MAWILITE,
MEDICHAMITE,
METAGROSSITE,
MEWTWONITE_X,
MEWTWONITE_Y,
PIDGEOTITE,
PINSIRITE,
RAYQUAZITE,
SABLENITE,
SALAMENCITE,
SCEPTILITE,
SCIZORITE,
SHARPEDONITE,
SLOWBRONITE,
STEELIXITE,
SWAMPERTITE,
TYRANITARITE,
VENUSAURITE,
BLUE_ORB = 50,
RED_ORB,
ADAMANT_CRYSTAL,
LUSTROUS_GLOBE,
GRISEOUS_CORE,
REVEAL_GLASS,
MAX_MUSHROOMS,
DARK_STONE,
LIGHT_STONE,
PRISON_BOTTLE,
RUSTED_SWORD,
RUSTED_SHIELD,
ICY_REINS_OF_UNITY,
SHADOW_REINS_OF_UNITY,
ULTRANECROZIUM_Z,
SHARP_METEORITE = 100,
HARD_METEORITE,
SMOOTH_METEORITE,
GRACIDEA,
SHOCK_DRIVE,
BURN_DRIVE,
CHILL_DRIVE,
DOUSE_DRIVE,
N_SOLARIZER,
N_LUNARIZER,
WELLSPRING_MASK,
HEARTHFLAME_MASK,
CORNERSTONE_MASK,
FIST_PLATE,
SKY_PLATE,
TOXIC_PLATE,
EARTH_PLATE,
STONE_PLATE,
INSECT_PLATE,
SPOOKY_PLATE,
IRON_PLATE,
FLAME_PLATE,
SPLASH_PLATE,
MEADOW_PLATE,
ZAP_PLATE,
MIND_PLATE,
ICICLE_PLATE,
DRACO_PLATE,
DREAD_PLATE,
PIXIE_PLATE,
BLANK_PLATE,// TODO: Find a potential use for this
LEGEND_PLATE,// TODO: Find a potential use for this
FIGHTING_MEMORY,
FLYING_MEMORY,
POISON_MEMORY,
GROUND_MEMORY,
ROCK_MEMORY,
BUG_MEMORY,
GHOST_MEMORY,
STEEL_MEMORY,
FIRE_MEMORY,
WATER_MEMORY,
GRASS_MEMORY,
ELECTRIC_MEMORY,
PSYCHIC_MEMORY,
ICE_MEMORY,
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
NORMAL_MEMORY
}

7
src/enums/game-modes.ts Normal file
View File

@ -0,0 +1,7 @@
export enum GameModes {
CLASSIC,
ENDLESS,
SPLICED_ENDLESS,
DAILY,
CHALLENGE
}

15
src/enums/hit-result.ts Normal file
View File

@ -0,0 +1,15 @@
export enum HitResult {
EFFECTIVE = 1,
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
ONE_HIT_KO,
NO_EFFECT,
STATUS,
HEAL,
FAIL,
MISS,
INDIRECT,
IMMUNE,
CONFUSION,
INDIRECT_KO
}

View File

@ -0,0 +1,8 @@
export enum LearnMoveSituation {
MISC,
LEVEL_UP,
RELEARN,
EVOLUTION,
EVOLUTION_FUSED,// If fusionSpecies has Evolved
EVOLUTION_FUSED_BASE
}

View File

@ -0,0 +1,8 @@
export enum LearnMoveType {
/** For learning a move via level-up, evolution, or other non-item-based event */
LEARN_MOVE,
/** For learning a move via Memory Mushroom */
MEMORY,
/** For learning a move via TM */
TM
}

View File

@ -0,0 +1,95 @@
export enum AnimFrameTarget {
USER,
TARGET,
GRAPHIC
}
export enum AnimFocus {
TARGET = 1,
USER,
USER_TARGET,
SCREEN
}
export enum AnimBlendType {
NORMAL,
ADD,
SUBTRACT
}
export enum ChargeAnim {
FLY_CHARGING = 1000,
BOUNCE_CHARGING,
DIG_CHARGING,
FUTURE_SIGHT_CHARGING,
DIVE_CHARGING,
SOLAR_BEAM_CHARGING,
SHADOW_FORCE_CHARGING,
SKULL_BASH_CHARGING,
FREEZE_SHOCK_CHARGING,
SKY_DROP_CHARGING,
SKY_ATTACK_CHARGING,
ICE_BURN_CHARGING,
DOOM_DESIRE_CHARGING,
RAZOR_WIND_CHARGING,
PHANTOM_FORCE_CHARGING,
GEOMANCY_CHARGING,
SHADOW_BLADE_CHARGING,
SOLAR_BLADE_CHARGING,
BEAK_BLAST_CHARGING,
METEOR_BEAM_CHARGING,
ELECTRO_SHOT_CHARGING
}
export enum CommonAnim {
USE_ITEM = 2000,
HEALTH_UP,
TERASTALLIZE,
POISON = 2010,
TOXIC,
PARALYSIS,
SLEEP,
FROZEN,
BURN,
CONFUSION,
ATTRACT,
BIND,
WRAP,
CURSE_NO_GHOST,
LEECH_SEED,
FIRE_SPIN,
PROTECT,
COVET,
WHIRLPOOL,
BIDE,
SAND_TOMB,
QUICK_GUARD,
WIDE_GUARD,
CURSE,
MAGMA_STORM,
CLAMP,
SNAP_TRAP,
THUNDER_CAGE,
INFESTATION,
ORDER_UP_CURLY,
ORDER_UP_DROOPY,
ORDER_UP_STRETCHY,
RAGING_BULL_FIRE,
RAGING_BULL_WATER,
SALT_CURE,
POWDER,
SUNNY = 2100,
RAIN,
SANDSTORM,
HAIL,
SNOW,
WIND,
HEAVY_RAIN,
HARSH_SUN,
STRONG_WINDS,
MISTY_TERRAIN = 2110,
ELECTRIC_TERRAIN,
GRASSY_TERRAIN,
PSYCHIC_TERRAIN,
LOCK_ON = 2120
}

7
src/enums/move-result.ts Normal file
View File

@ -0,0 +1,7 @@
export enum MoveResult {
PENDING,
SUCCESS,
FAIL,
MISS,
OTHER
}

View File

@ -0,0 +1,12 @@
/**
* Used for challenge types that modify movesets, these denote the various sources of moves for pokemon.
*/
export enum MoveSourceType {
LEVEL_UP,// Currently unimplemented for move access
RELEARNER,// Relearner moves currently unimplemented
COMMON_TM,
GREAT_TM,
ULTRA_TM,
COMMON_EGG,
RARE_EGG
}

View File

@ -0,0 +1,5 @@
export enum TrainerVariant {
DEFAULT,
FEMALE,
DOUBLE
}

View File

@ -1,4 +1,4 @@
import type { ArenaTagSide } from "#app/data/arena-tag"; import type { ArenaTagSide } from "#enums/arena-tag-side";
import type { ArenaTagType } from "#enums/arena-tag-type"; import type { ArenaTagType } from "#enums/arena-tag-type";
import type { TerrainType } from "#app/data/terrain"; import type { TerrainType } from "#app/data/terrain";
import type { WeatherType } from "#enums/weather-type"; import type { WeatherType } from "#enums/weather-type";

View File

@ -12,12 +12,13 @@ import {
getLegendaryWeatherContinuesMessage, getLegendaryWeatherContinuesMessage,
Weather, Weather,
} from "#app/data/weather"; } from "#app/data/weather";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type { ArenaTag } from "#app/data/arena-tag"; import type { ArenaTag } from "#app/data/arena-tag";
import { ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
import type { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#enums/arena-tag-side";
import type { BattlerIndex } from "#enums/battler-index";
import { Terrain, TerrainType } from "#app/data/terrain"; import { Terrain, TerrainType } from "#app/data/terrain";
import { import {
applyAbAttrs, applyAbAttrs,
@ -37,8 +38,10 @@ import { SpeciesId } from "#enums/species-id";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import {
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; SpeciesFormChangeRevertWeatherFormTrigger,
SpeciesFormChangeWeatherTrigger,
} from "#app/data/pokemon-forms/form-change-triggers";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { FieldEffectModifier } from "#app/modifier/modifier"; import { FieldEffectModifier } from "#app/modifier/modifier";
@ -297,8 +300,8 @@ export class Arena {
*/ */
trySetWeatherOverride(weather: WeatherType): boolean { trySetWeatherOverride(weather: WeatherType): boolean {
this.weather = new Weather(weather, 0); this.weather = new Weather(weather, 0);
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1))); globalScene.phaseManager.unshiftNew("CommonAnimPhase", undefined, undefined, CommonAnim.SUNNY + (weather - 1));
globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
return true; return true;
} }
@ -328,10 +331,14 @@ export class Arena {
this.weather?.isImmutable() && this.weather?.isImmutable() &&
![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather) ![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather)
) { ) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (oldWeatherType - 1), true), "CommonAnimPhase",
undefined,
undefined,
CommonAnim.SUNNY + (oldWeatherType - 1),
true,
); );
globalScene.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!);
return false; return false;
} }
@ -348,10 +355,16 @@ export class Arena {
); // TODO: is this bang correct? ); // TODO: is this bang correct?
if (this.weather) { if (this.weather) {
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true)); globalScene.phaseManager.unshiftNew(
globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? "CommonAnimPhase",
undefined,
undefined,
CommonAnim.SUNNY + (weather - 1),
true,
);
globalScene.phaseManager.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct?
} else { } else {
globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct?
} }
globalScene globalScene
@ -431,11 +444,16 @@ export class Arena {
if (this.terrain) { if (this.terrain) {
if (!ignoreAnim) { if (!ignoreAnim) {
globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); globalScene.phaseManager.unshiftNew(
"CommonAnimPhase",
undefined,
undefined,
CommonAnim.MISTY_TERRAIN + (terrain - 1),
);
} }
globalScene.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct?
} else { } else {
globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? globalScene.phaseManager.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct?
} }
globalScene globalScene

View File

@ -1,9 +1,9 @@
import { TextStyle, addTextObject } from "../ui/text"; import { TextStyle, addTextObject } from "../ui/text";
import type { DamageResult } from "./pokemon"; import type { DamageResult } from "./pokemon";
import type Pokemon from "./pokemon"; import type Pokemon from "./pokemon";
import { HitResult } from "./pokemon"; import { HitResult } from "#enums/hit-result";
import { formatStat, fixedInt } from "#app/utils/common"; import { formatStat, fixedInt } from "#app/utils/common";
import type { BattlerIndex } from "../battle"; import type { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
type TextAndShadowArr = [string | null, string | null]; type TextAndShadowArr = [string | null, string | null];

View File

@ -9,36 +9,8 @@ import BattleInfo from "#app/ui/battle-info/battle-info";
import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info"; import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info";
import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info"; import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { import { getMoveTargets } from "#app/data/moves/move-utils";
HighCritAttr, import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
HitsTagAttr,
applyMoveAttrs,
FixedDamageAttr,
VariableAtkAttr,
TypelessAttr,
CritOnlyAttr,
getMoveTargets,
OneHitKOAttr,
VariableMoveTypeAttr,
VariableDefAttr,
AttackMove,
ModifiedDamageAttr,
VariableMoveTypeMultiplierAttr,
IgnoreOpponentStatStagesAttr,
SacrificialAttr,
VariableMoveCategoryAttr,
CounterDamageAttr,
StatStageChangeAttr,
RechargeAttr,
IgnoreWeatherTypeDebuffAttr,
BypassBurnDamageReductionAttr,
SacrificialAttrOnHit,
OneHitKOAccuracyAttr,
RespectAttackTypeImmunityAttr,
CombinedPledgeStabBoostAttr,
VariableMoveTypeChartAttr,
HpSplitAttr,
} from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
@ -65,6 +37,7 @@ import {
rgbToHsv, rgbToHsv,
deltaRgb, deltaRgb,
isBetween, isBetween,
randSeedFloat,
type nil, type nil,
type Constructor, type Constructor,
randSeedIntRange, randSeedIntRange,
@ -115,7 +88,6 @@ import {
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { import {
BattlerTag, BattlerTag,
BattlerTagLapseType,
EncoreTag, EncoreTag,
GroundedTag, GroundedTag,
HighestStatBoostTag, HighestStatBoostTag,
@ -134,8 +106,10 @@ import {
loadBattlerTag, loadBattlerTag,
type GrudgeTag, type GrudgeTag,
} from "../data/battler-tags"; } from "../data/battler-tags";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability-class";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
@ -194,7 +168,7 @@ import {
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import type { PartyOption } from "#app/ui/party-ui-handler"; import type { PartyOption } from "#app/ui/party-ui-handler";
import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler";
@ -203,7 +177,7 @@ import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data";
import { DexAttr } from "#app/system/game-data"; import { DexAttr } from "#enums/dex-attr";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { getNatureStatMultiplier } from "#app/data/nature"; import { getNatureStatMultiplier } from "#app/data/nature";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
@ -212,14 +186,15 @@ import {
SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeLapseTeraTrigger,
SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveLearnedTrigger,
SpeciesFormChangePostMoveTrigger, SpeciesFormChangePostMoveTrigger,
} from "#app/data/pokemon-forms"; } from "#app/data/pokemon-forms/form-change-triggers";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import type { TrainerSlot } from "#enums/trainer-slot"; import type { TrainerSlot } from "#enums/trainer-slot";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import i18next from "i18next"; import i18next from "i18next";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { applyChallenges } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
@ -229,13 +204,6 @@ import { BiomeId } from "#enums/biome-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import { FaintPhase } from "#app/phases/faint-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
@ -255,22 +223,12 @@ import { doShinySparkleAnim } from "#app/field/anims";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
import { ResetStatusPhase } from "#app/phases/reset-status-phase"; import { FieldPosition } from "#enums/field-position";
import { LearnMoveSituation } from "#enums/learn-move-situation";
export enum LearnMoveSituation { import { HitResult } from "#enums/hit-result";
MISC, import { AiType } from "#enums/ai-type";
LEVEL_UP, import type { MoveResult } from "#enums/move-result";
RELEARN, import { PokemonMove } from "#app/data/moves/pokemon-move";
EVOLUTION,
EVOLUTION_FUSED, // If fusionSpecies has Evolved
EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved
}
export enum FieldPosition {
CENTER,
LEFT,
RIGHT,
}
/** Base typeclass for damage parameter methods, used for DRY */ /** Base typeclass for damage parameter methods, used for DRY */
type damageParams = { type damageParams = {
@ -503,7 +461,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (level > 1) { if (level > 1) {
const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly); const fused = new BooleanHolder(globalScene.gameMode.isSplicedOnly);
if (!fused.value && !this.isPlayer() && !this.hasTrainer()) { if (!fused.value && this.isEnemy() && !this.hasTrainer()) {
globalScene.applyModifier(EnemyFusionChanceModifier, false, fused); globalScene.applyModifier(EnemyFusionChanceModifier, false, fused);
} }
@ -788,7 +746,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return true; return true;
} }
abstract isPlayer(): boolean; abstract isPlayer(): this is PlayerPokemon;
abstract isEnemy(): this is EnemyPokemon;
abstract hasTrainer(): boolean; abstract hasTrainer(): boolean;
@ -1296,7 +1256,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
// During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus"
const currentPhase = globalScene.getCurrentPhase(); const currentPhase = globalScene.phaseManager.getCurrentPhase();
return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this); return !(currentPhase?.is("MoveEffectPhase") && currentPhase.getPokemon() === this);
} }
@ -1440,7 +1400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getCritStage(source: Pokemon, move: Move): number { getCritStage(source: Pokemon, move: Move): number {
const critStage = new NumberHolder(0); const critStage = new NumberHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critStage); applyMoveAttrs("HighCritAttr", source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
@ -1466,7 +1426,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getMoveCategory(target: Pokemon, move: Move): MoveCategory { getMoveCategory(target: Pokemon, move: Move): MoveCategory {
const moveCategory = new NumberHolder(move.category); const moveCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, this, target, move, moveCategory); applyMoveAttrs("VariableMoveCategoryAttr", this, target, move, moveCategory);
return moveCategory.value; return moveCategory.value;
} }
@ -2050,7 +2010,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) { if (Overrides.ABILITY_OVERRIDE && this.isPlayer()) {
return allAbilities[Overrides.ABILITY_OVERRIDE]; return allAbilities[Overrides.ABILITY_OVERRIDE];
} }
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_ABILITY_OVERRIDE && this.isEnemy()) {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
} }
if (this.isFusion()) { if (this.isFusion()) {
@ -2080,7 +2040,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) { if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) {
return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE]; return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE];
} }
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && this.isEnemy()) {
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
} }
if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) {
@ -2152,7 +2112,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// returns override if valid for current case // returns override if valid for current case
if ( if (
(Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) || (Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer()) ||
(Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && !this.isPlayer()) (Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isEnemy())
) { ) {
return false; return false;
} }
@ -2160,7 +2120,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && ((Overrides.PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) &&
this.isPlayer()) || this.isPlayer()) ||
((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== AbilityId.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) &&
!this.isPlayer()) this.isEnemy())
) { ) {
return true; return true;
} }
@ -2169,7 +2129,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const { currentBattle, gameMode } = globalScene; const { currentBattle, gameMode } = globalScene;
const waveIndex = currentBattle?.waveIndex; const waveIndex = currentBattle?.waveIndex;
if ( if (
this instanceof EnemyPokemon && this.isEnemy() &&
(currentBattle?.battleSpec === BattleSpec.FINAL_BOSS || (currentBattle?.battleSpec === BattleSpec.FINAL_BOSS ||
gameMode.isEndlessMinorBoss(waveIndex) || gameMode.isEndlessMinorBoss(waveIndex) ||
gameMode.isEndlessMajorBoss(waveIndex)) gameMode.isEndlessMajorBoss(waveIndex))
@ -2376,7 +2336,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public getMoveType(move: Move, simulated = true): PokemonType { public getMoveType(move: Move, simulated = true): PokemonType {
const moveTypeHolder = new NumberHolder(move.type); const moveTypeHolder = new NumberHolder(move.type);
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
@ -2420,18 +2380,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.turnData?.moveEffectiveness; return this.turnData?.moveEffectiveness;
} }
if (move.hasAttr(TypelessAttr)) { if (move.hasAttr("TypelessAttr")) {
return 1; return 1;
} }
const moveType = source.getMoveType(move); const moveType = source.getMoveType(move);
const typeMultiplier = new NumberHolder( const typeMultiplier = new NumberHolder(
move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr) move.category !== MoveCategory.STATUS || move.hasAttr("RespectAttackTypeImmunityAttr")
? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion) ? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion)
: 1, : 1,
); );
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs("VariableMoveTypeMultiplierAttr", source, this, move, typeMultiplier);
if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) { if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) {
typeMultiplier.value = 0; typeMultiplier.value = 0;
} }
@ -2458,7 +2418,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
for (const tag of immuneTags) { for (const tag of immuneTags) {
if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) { if (move && !move.getAttrs("HitsTagAttr").some(attr => attr.tagType === tag.tagType)) {
typeMultiplier.value = 0; typeMultiplier.value = 0;
break; break;
} }
@ -2514,7 +2474,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType)); const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier); applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (move) { if (move) {
applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType); applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType);
} }
if (source) { if (source) {
const ignoreImmunity = new BooleanHolder(false); const ignoreImmunity = new BooleanHolder(false);
@ -2550,7 +2510,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
) { ) {
multiplier /= 2; multiplier /= 2;
if (!simulated) { if (!simulated) {
globalScene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); globalScene.phaseManager.queueMessage(i18next.t("weather:strongWindsEffectMessage"));
} }
} }
return multiplier as TypeDamageMultiplier; return multiplier as TypeDamageMultiplier;
@ -2994,9 +2954,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let fusionOverride: PokemonSpecies | undefined = undefined; let fusionOverride: PokemonSpecies | undefined = undefined;
if (forStarter && this instanceof PlayerPokemon && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) { if (forStarter && this.isPlayer() && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) {
fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE); fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE);
} else if (this instanceof EnemyPokemon && Overrides.OPP_FUSION_SPECIES_OVERRIDE) { } else if (this.isEnemy() && Overrides.OPP_FUSION_SPECIES_OVERRIDE) {
fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE); fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE);
} }
@ -3150,24 +3110,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Bosses never get self ko moves or Pain Split // Bosses never get self ko moves or Pain Split
if (this.isBoss()) { if (this.isBoss()) {
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr) && !allMoves[m[0]].hasAttr(HpSplitAttr)); movePool = movePool.filter(
m => !allMoves[m[0]].hasAttr("SacrificialAttr") && !allMoves[m[0]].hasAttr("HpSplitAttr"),
);
} }
// No one gets Memento or Final Gambit // No one gets Memento or Final Gambit
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit)); movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("SacrificialAttrOnHit"));
if (this.hasTrainer()) { if (this.hasTrainer()) {
// Trainers never get OHKO moves // Trainers never get OHKO moves
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr)); movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("OneHitKOAttr"));
// Half the weight of self KO moves // Half the weight of self KO moves
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]); movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr("SacrificialAttr") ? 0.5 : 1)]);
// Trainers get a weight bump to stat buffing moves // Trainers get a weight bump to stat buffing moves
movePool = movePool.map(m => [ movePool = movePool.map(m => [
m[0], m[0],
m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1), m[1] * (allMoves[m[0]].getAttrs("StatStageChangeAttr").some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1),
]); ]);
// Trainers get a weight decrease to multiturn moves // Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [ movePool = movePool.map(m => [
m[0], m[0],
m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1), m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr("RechargeAttr") ? 0.7 : 1),
]); ]);
} }
@ -3234,7 +3196,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!this.moveset.some( !this.moveset.some(
mo => mo =>
m[0] === mo.moveId || m[0] === mo.moveId ||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed
), ),
) )
.map(m => { .map(m => {
@ -3263,7 +3225,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!this.moveset.some( !this.moveset.some(
mo => mo =>
m[0] === mo.moveId || m[0] === mo.moveId ||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed
), ),
); );
} }
@ -3307,7 +3269,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198));
this.battleInfo.setVisible(true); this.battleInfo.setVisible(true);
if (this.isPlayer()) { if (this.isPlayer()) {
this.battleInfo.expMaskRect.x += 150; // TODO: How do you get this to not require a private property access?
this["battleInfo"].expMaskRect.x += 150;
} }
globalScene.tweens.add({ globalScene.tweens.add({
targets: [this.battleInfo, this.battleInfo.expMaskRect], targets: [this.battleInfo, this.battleInfo.expMaskRect],
@ -3328,7 +3291,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
ease: "Cubic.easeIn", ease: "Cubic.easeIn",
onComplete: () => { onComplete: () => {
if (this.isPlayer()) { if (this.isPlayer()) {
this.battleInfo.expMaskRect.x -= 150; // TODO: How do you get this to not require a private property access?
this["battleInfo"].expMaskRect.x -= 150;
} }
this.battleInfo.setVisible(false); this.battleInfo.setVisible(false);
this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198));
@ -3423,7 +3387,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns An array of Pokémon on the allied field. * @returns An array of Pokémon on the allied field.
*/ */
getAlliedField(): Pokemon[] { getAlliedField(): Pokemon[] {
return this instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); return this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
} }
/** /**
@ -3469,7 +3433,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage);
} }
if (move) { if (move) {
applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
} }
} }
@ -3494,7 +3458,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns The calculated accuracy multiplier. * @returns The calculated accuracy multiplier.
*/ */
getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number { getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number {
const isOhko = sourceMove.hasAttr(OneHitKOAccuracyAttr); const isOhko = sourceMove.hasAttr("OneHitKOAccuracyAttr");
if (isOhko) { if (isOhko) {
return 1; return 1;
} }
@ -3507,7 +3471,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage);
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage);
applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
@ -3590,7 +3554,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated, simulated,
), ),
); );
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk); applyMoveAttrs("VariableAtkAttr", source, this, move, sourceAtk);
/** /**
* This Pokemon's defensive stat for the given move's category. * This Pokemon's defensive stat for the given move's category.
@ -3608,7 +3572,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated, simulated,
), ),
); );
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); applyMoveAttrs("VariableDefAttr", source, this, move, targetDef);
/** /**
* The attack's base damage, as determined by the source's level, move power * The attack's base damage, as determined by the source's level, move power
@ -3635,7 +3599,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number { calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number {
// If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle) // If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle)
if (move.hasAttr(TypelessAttr)) { if (move.hasAttr("TypelessAttr")) {
return 1; return 1;
} }
const sourceTypes = source.getTypes(); const sourceTypes = source.getTypes();
@ -3647,7 +3611,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
@ -3696,7 +3660,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const variableCategory = new NumberHolder(move.category); const variableCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); applyMoveAttrs("VariableMoveCategoryAttr", source, this, move, variableCategory);
const moveCategory = variableCategory.value as MoveCategory; const moveCategory = variableCategory.value as MoveCategory;
/** The move's type after type-changing effects are applied */ /** The move's type after type-changing effects are applied */
@ -3721,7 +3685,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const arenaAttackTypeMultiplier = new NumberHolder( const arenaAttackTypeMultiplier = new NumberHolder(
globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()), globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()),
); );
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); applyMoveAttrs("IgnoreWeatherTypeDebuffAttr", source, this, move, arenaAttackTypeMultiplier);
const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0; const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0;
@ -3735,7 +3699,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If the attack deals fixed damage, return a result with that much damage // If the attack deals fixed damage, return a result with that much damage
const fixedDamage = new NumberHolder(0); const fixedDamage = new NumberHolder(0);
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage);
if (fixedDamage.value) { if (fixedDamage.value) {
const multiLensMultiplier = new NumberHolder(1); const multiLensMultiplier = new NumberHolder(1);
globalScene.applyModifiers( globalScene.applyModifiers(
@ -3757,7 +3721,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP // If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP
const isOneHitKo = new BooleanHolder(false); const isOneHitKo = new BooleanHolder(false);
applyMoveAttrs(OneHitKOAttr, source, this, move, isOneHitKo); applyMoveAttrs("OneHitKOAttr", source, this, move, isOneHitKo);
if (isOneHitKo.value) { if (isOneHitKo.value) {
return { return {
cancelled: false, cancelled: false,
@ -3834,7 +3798,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isPhysical && isPhysical &&
source.status && source.status &&
source.status.effect === StatusEffect.BURN && source.status.effect === StatusEffect.BURN &&
!move.hasAttr(BypassBurnDamageReductionAttr) !move.hasAttr("BypassBurnDamageReductionAttr")
) { ) {
const burnDamageReductionCancelled = new BooleanHolder(false); const burnDamageReductionCancelled = new BooleanHolder(false);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
@ -3868,7 +3832,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
const hitsTagMultiplier = new NumberHolder(1); const hitsTagMultiplier = new NumberHolder(1);
move move
.getAttrs(HitsTagAttr) .getAttrs("HitsTagAttr")
.filter(hta => hta.doubleDamage) .filter(hta => hta.doubleDamage)
.forEach(hta => { .forEach(hta => {
if (this.getTag(hta.tagType)) { if (this.getTag(hta.tagType)) {
@ -3925,7 +3889,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
// This attribute may modify damage arbitrarily, so be careful about changing its order of application. // This attribute may modify damage arbitrarily, so be careful about changing its order of application.
applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage); applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
if (this.isFullHp() && !ignoreAbility) { if (this.isFullHp() && !ignoreAbility) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage);
@ -3961,7 +3925,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) { if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) {
return false; return false;
} }
const isCritical = new BooleanHolder(false); const isCritical = new BooleanHolder(false);
@ -3969,7 +3933,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
isCritical.value = true; isCritical.value = true;
} }
applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical); applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
if (!isCritical.value) { if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
@ -4022,8 +3986,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* *
* Once the MoveEffectPhase is over (and calls it's .end() function, shiftPhase() will reset the PhaseQueueSplice via clearPhaseQueueSplice() ) * Once the MoveEffectPhase is over (and calls it's .end() function, shiftPhase() will reset the PhaseQueueSplice via clearPhaseQueueSplice() )
*/ */
globalScene.setPhaseQueueSplice(); globalScene.phaseManager.setPhaseQueueSplice();
globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); globalScene.phaseManager.unshiftNew("FaintPhase", this.getBattlerIndex(), preventEndure);
this.destroySubstitute(); this.destroySubstitute();
this.lapseTag(BattlerTagType.COMMANDED); this.lapseTag(BattlerTagType.COMMANDED);
} }
@ -4059,8 +4023,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} = {}, } = {},
): number { ): number {
const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result); const isIndirectDamage = [HitResult.INDIRECT, HitResult.INDIRECT_KO].includes(result);
const damagePhase = new DamageAnimPhase(this.getBattlerIndex(), damage, result as DamageResult, isCritical); const damagePhase = globalScene.phaseManager.create(
globalScene.unshiftPhase(damagePhase); "DamageAnimPhase",
this.getBattlerIndex(),
damage,
result as DamageResult,
isCritical,
);
globalScene.phaseManager.unshiftPhase(damagePhase);
if (this.switchOutStatus && source) { if (this.switchOutStatus && source) {
damage = 0; damage = 0;
} }
@ -4275,7 +4245,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Copy all stat stages // Copy all stat stages
for (const s of BATTLE_STATS) { for (const s of BATTLE_STATS) {
const sourceStage = source.getStatStage(s); const sourceStage = source.getStatStage(s);
if (this instanceof PlayerPokemon && sourceStage === 6) { if (this.isPlayer() && sourceStage === 6) {
globalScene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE); globalScene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE);
} }
this.setStatStage(s, sourceStage); this.setStatStage(s, sourceStage);
@ -4626,7 +4596,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: i18next.t("abilityTriggers:moveImmunity", { : i18next.t("abilityTriggers:moveImmunity", {
pokemonNameWithAffix: getPokemonNameWithAffix(this), pokemonNameWithAffix: getPokemonNameWithAffix(this),
}); });
globalScene.queueMessage(message); globalScene.phaseManager.queueMessage(message);
} }
/** /**
@ -4746,7 +4716,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) { if (sourcePokemon && sourcePokemon !== this && this.isSafeguarded(sourcePokemon)) {
if (!quiet) { if (!quiet) {
globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) })); globalScene.phaseManager.queueMessage(
i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(this) }),
);
} }
return false; return false;
} }
@ -4775,7 +4747,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* cancel the attack's subsequent hits. * cancel the attack's subsequent hits.
*/ */
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) { if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
const currentPhase = globalScene.getCurrentPhase(); const currentPhase = globalScene.phaseManager.getCurrentPhase();
if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) { if (currentPhase?.is("MoveEffectPhase") && currentPhase.getUserPokemon() === this) {
this.turnData.hitCount = 1; this.turnData.hitCount = 1;
this.turnData.hitsLeft = 1; this.turnData.hitsLeft = 1;
@ -4786,8 +4758,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (overrideStatus) { if (overrideStatus) {
this.resetStatus(false); this.resetStatus(false);
} }
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon), "ObtainStatusEffectPhase",
this.getBattlerIndex(),
effect,
turnsRemaining,
sourceText,
sourcePokemon,
); );
return true; return true;
} }
@ -4836,7 +4813,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (asPhase) { if (asPhase) {
globalScene.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets)); globalScene.phaseManager.unshiftNew("ResetStatusPhase", this, confusion, reloadAssets);
} else { } else {
this.clearStatus(confusion, reloadAssets); this.clearStatus(confusion, reloadAssets);
} }
@ -5234,7 +5211,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let fusionPaletteColors: Map<number, number>; let fusionPaletteColors: Map<number, number>;
const originalRandom = Math.random; const originalRandom = Math.random;
Math.random = () => Phaser.Math.RND.realInRange(0, 1); Math.random = () => randSeedFloat();
globalScene.executeWithSeedOffset( globalScene.executeWithSeedOffset(
() => { () => {
@ -5483,7 +5460,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
heldItem.stackCount--; heldItem.stackCount--;
if (heldItem.stackCount <= 0) { if (heldItem.stackCount <= 0) {
globalScene.removeModifier(heldItem, !this.isPlayer()); globalScene.removeModifier(heldItem, this.isEnemy());
} }
if (forBattle) { if (forBattle) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false);
@ -5556,15 +5533,19 @@ export class PlayerPokemon extends Pokemon {
this.battleInfo.initInfo(this); this.battleInfo.initInfo(this);
} }
isPlayer(): boolean { override isPlayer(): this is PlayerPokemon {
return true; return true;
} }
hasTrainer(): boolean { override isEnemy(): this is EnemyPokemon {
return false;
}
override hasTrainer(): boolean {
return true; return true;
} }
isBoss(): boolean { override isBoss(): boolean {
return false; return false;
} }
@ -5639,9 +5620,13 @@ export class PlayerPokemon extends Pokemon {
this.getFieldIndex(), this.getFieldIndex(),
(slotIndex: number, _option: PartyOption) => { (slotIndex: number, _option: PartyOption) => {
if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) {
globalScene.prependToPhase( globalScene.phaseManager.prependNewToPhase(
new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), "MoveEndPhase",
MoveEndPhase, "SwitchSummonPhase",
switchType,
this.getFieldIndex(),
slotIndex,
false,
); );
} }
globalScene.ui.setMode(UiMode.MESSAGE).then(resolve); globalScene.ui.setMode(UiMode.MESSAGE).then(resolve);
@ -6004,7 +5989,9 @@ export class PlayerPokemon extends Pokemon {
const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this);
pokemon pokemon
.getMoveset(true) .getMoveset(true)
.map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); .map((m: PokemonMove) =>
globalScene.phaseManager.unshiftNew("LearnMovePhase", newPartyMemberIndex, m.getMove().id),
);
pokemon.destroy(); pokemon.destroy();
this.updateFusionPalette(); this.updateFusionPalette();
} }
@ -6267,7 +6254,7 @@ export class EnemyPokemon extends Pokemon {
.targets.map(ind => fieldPokemon[ind]) .targets.map(ind => fieldPokemon[ind])
.filter(p => this.isPlayer() !== p.isPlayer()); .filter(p => this.isPlayer() !== p.isPlayer());
// Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus
const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); const isCritical = move.hasAttr("CritOnlyAttr") || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
return ( return (
move.category !== MoveCategory.STATUS && move.category !== MoveCategory.STATUS &&
@ -6336,7 +6323,7 @@ export class EnemyPokemon extends Pokemon {
![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id) ![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id)
) { ) {
targetScore = -20; targetScore = -20;
} else if (move instanceof AttackMove) { } else if (move.is("AttackMove")) {
/** /**
* Attack moves are given extra multipliers to their base benefit score based on * Attack moves are given extra multipliers to their base benefit score based on
* the move's type effectiveness against the target and whether the move is a STAB move. * the move's type effectiveness against the target and whether the move is a STAB move.
@ -6457,7 +6444,7 @@ export class EnemyPokemon extends Pokemon {
if (!sortedBenefitScores.length) { if (!sortedBenefitScores.length) {
// Set target to BattlerIndex.ATTACKER when using a counter move // Set target to BattlerIndex.ATTACKER when using a counter move
// This is the same as when the player does so // This is the same as when the player does so
if (move.hasAttr(CounterDamageAttr)) { if (move.hasAttr("CounterDamageAttr")) {
return [BattlerIndex.ATTACKER]; return [BattlerIndex.ATTACKER];
} }
@ -6509,15 +6496,19 @@ export class EnemyPokemon extends Pokemon {
return [sortedBenefitScores[targetIndex][0]]; return [sortedBenefitScores[targetIndex][0]];
} }
isPlayer() { override isPlayer(): this is PlayerPokemon {
return false; return false;
} }
hasTrainer(): boolean { override isEnemy(): this is EnemyPokemon {
return true;
}
override hasTrainer(): boolean {
return !!this.trainerSlot; return !!this.trainerSlot;
} }
isBoss(): boolean { override isBoss(): boolean {
return !!this.bossSegments; return !!this.bossSegments;
} }
@ -6642,8 +6633,14 @@ export class EnemyPokemon extends Pokemon {
stages++; stages++;
} }
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new StatStageChangePhase(this.getBattlerIndex(), true, [boostedStat!], stages, true, true), "StatStageChangePhase",
this.getBattlerIndex(),
true,
[boostedStat!],
stages,
true,
true,
); );
this.bossSegmentIndex--; this.bossSegmentIndex--;
} }
@ -6930,36 +6927,6 @@ export class PokemonTurnData {
public berriesEaten: BerryType[] = []; public berriesEaten: BerryType[] = [];
} }
export enum AiType {
RANDOM,
SMART_RANDOM,
SMART,
}
export enum MoveResult {
PENDING,
SUCCESS,
FAIL,
MISS,
OTHER,
}
export enum HitResult {
EFFECTIVE = 1,
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
ONE_HIT_KO,
NO_EFFECT,
STATUS,
HEAL,
FAIL,
MISS,
INDIRECT,
IMMUNE,
CONFUSION,
INDIRECT_KO,
}
export type DamageResult = export type DamageResult =
| HitResult.EFFECTIVE | HitResult.EFFECTIVE
| HitResult.SUPER_EFFECTIVE | HitResult.SUPER_EFFECTIVE
@ -6978,91 +6945,3 @@ export interface DamageCalculationResult {
/** The damage dealt by the move */ /** The damage dealt by the move */
damage: number; damage: number;
} }
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks things like
* PP Ups recieved, PP used, etc.
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove {
public moveId: MoveId;
public ppUsed: number;
public ppUp: number;
public virtual: boolean;
/**
* If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform).
* This also nullifies all effects of `ppUp`.
*/
public maxPpOverride?: number;
constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) {
this.moveId = moveId;
this.ppUsed = ppUsed;
this.ppUp = ppUp;
this.virtual = virtual;
this.maxPpOverride = maxPpOverride;
}
/**
* Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
*
* @param pokemon - {@linkcode Pokemon} that would be using this move
* @param ignorePp - If `true`, skips the PP check
* @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
*/
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
return false;
}
if (this.getMove().name.endsWith(" (N)")) {
return false;
}
return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1;
}
getMove(): Move {
return allMoves[this.moveId];
}
/**
* Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp}
* @param count Amount of PP to use
*/
usePp(count = 1) {
this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp());
}
getMovePp(): number {
return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5);
}
getPpRatio(): number {
return 1 - this.ppUsed / this.getMovePp();
}
getName(): string {
return this.getMove().name;
}
/**
* Copies an existing move or creates a valid {@linkcode PokemonMove} object from json representing one
* @param source The data for the move to copy; can be a {@linkcode PokemonMove} or JSON object representing one
* @returns A valid {@linkcode PokemonMove} object
*/
static loadMove(source: PokemonMove | any): PokemonMove {
return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride);
}
}

View File

@ -13,19 +13,15 @@ import { TeraAIMode } from "#enums/tera-ai-mode";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common"; import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common";
import type { PersistentModifier } from "#app/modifier/modifier"; import type { PersistentModifier } from "#app/modifier/modifier";
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { signatureSpecies } from "#app/data/balance/signature-species"; import { signatureSpecies } from "#app/data/balance/signature-species";
import { TrainerVariant } from "#enums/trainer-variant";
export enum TrainerVariant {
DEFAULT,
FEMALE,
DOUBLE,
}
export default class Trainer extends Phaser.GameObjects.Container { export default class Trainer extends Phaser.GameObjects.Container {
public config: TrainerConfig; public config: TrainerConfig;

View File

@ -2,7 +2,8 @@ import i18next from "i18next";
import type { FixedBattleConfigs } from "./battle"; import type { FixedBattleConfigs } from "./battle";
import { classicFixedBattles, FixedBattleConfig } from "./battle"; import { classicFixedBattles, FixedBattleConfig } from "./battle";
import type { Challenge } from "./data/challenge"; import type { Challenge } from "./data/challenge";
import { allChallenges, applyChallenges, ChallengeType, copyChallenge } from "./data/challenge"; import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge";
import { ChallengeType } from "#enums/challenge-type";
import type PokemonSpecies from "./data/pokemon-species"; import type PokemonSpecies from "./data/pokemon-species";
import { allSpecies } from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species";
import type { Arena } from "./field/arena"; import type { Arena } from "./field/arena";
@ -14,14 +15,7 @@ import { Challenges } from "./enums/challenges";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getDailyStartingBiome } from "./data/daily-run"; import { getDailyStartingBiome } from "./data/daily-run";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants";
import { GameModes } from "#enums/game-modes";
export enum GameModes {
CLASSIC,
ENDLESS,
SPLICED_ENDLESS,
DAILY,
CHALLENGE,
}
interface GameModeConfig { interface GameModeConfig {
isClassic?: boolean; isClassic?: boolean;

View File

@ -1,4 +1,3 @@
export const starterColors: StarterColors = {}; export const starterColors: {
interface StarterColors {
[key: string]: [string, string]; [key: string]: [string, string];
} } = {};

View File

@ -16,7 +16,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusio
switch (globalScene.currentBattle.battleSpec) { switch (globalScene.currentBattle.battleSpec) {
case BattleSpec.DEFAULT: case BattleSpec.DEFAULT:
return !pokemon.isPlayer() return pokemon.isEnemy()
? pokemon.hasTrainer() ? pokemon.hasTrainer()
? i18next.t("battle:foePokemonWithAffix", { ? i18next.t("battle:foePokemonWithAffix", {
pokemonName: pokemon.getNameToRender(useIllusion), pokemonName: pokemon.getNameToRender(useIllusion),
@ -26,7 +26,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusio
}) })
: pokemon.getNameToRender(useIllusion); : pokemon.getNameToRender(useIllusion);
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
return !pokemon.isPlayer() return pokemon.isEnemy()
? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) }) ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) })
: pokemon.getNameToRender(useIllusion); : pokemon.getNameToRender(useIllusion);
default: default:

View File

@ -2,19 +2,16 @@ import { globalScene } from "#app/global-scene";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms";
import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry";
import { AttackMove } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature";
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import { import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms";
FormChangeItem, import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers";
pokemonFormChanges, import { FormChangeItem } from "#enums/form-change-item";
SpeciesFormChangeCondition,
SpeciesFormChangeItemTrigger,
} from "#app/data/pokemon-forms";
import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { getStatusEffectDescriptor } from "#app/data/status-effect";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { import {
@ -1339,7 +1336,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
p p
.getMoveset() .getMoveset()
.map(m => m.getMove()) .map(m => m.getMove())
.filter(m => m instanceof AttackMove) .filter(m => m.is("AttackMove"))
.map(m => m.type), .map(m => m.type),
); );
if (!attackMoveTypes.length) { if (!attackMoveTypes.length) {

View File

@ -3,19 +3,17 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import type { FormChangeItem } from "#enums/form-change-item";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { EvolutionPhase } from "#app/phases/evolution-phase"; import { LearnMoveType } from "#enums/learn-move-type";
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import type { VoucherType } from "#app/system/voucher"; import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#enums/command";
import { addTextObject, TextStyle } from "#app/ui/text"; import { addTextObject, TextStyle } from "#app/ui/text";
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils/common"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
@ -1548,7 +1546,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier {
if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) { if (!surviveDamage.value && pokemon.randBattleSeedInt(10) < this.getStackCount()) {
surviveDamage.value = true; surviveDamage.value = true;
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("modifier:surviveDamageApply", { i18next.t("modifier:surviveDamageApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
@ -1598,7 +1596,7 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW";
if (isCommandFight && hasQuickClaw) { if (isCommandFight && hasQuickClaw) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("modifier:bypassSpeedChanceApply", { i18next.t("modifier:bypassSpeedChanceApply", {
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name"),
@ -1684,16 +1682,15 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
*/ */
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
if (!pokemon.isFullHp()) { if (!pokemon.isFullHp()) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount,
i18next.t("modifier:turnHealApply", { i18next.t("modifier:turnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
}), }),
true, true,
),
); );
return true; return true;
} }
@ -1782,16 +1779,15 @@ export class HitHealModifier extends PokemonHeldItemModifier {
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) {
// TODO: this shouldn't be undefined AFAIK // TODO: this shouldn't be undefined AFAIK
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount,
i18next.t("modifier:hitHealApply", { i18next.t("modifier:hitHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
}), }),
true, true,
),
); );
} }
@ -1950,18 +1946,17 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
*/ */
override apply(pokemon: Pokemon): boolean { override apply(pokemon: Pokemon): boolean {
// Restore the Pokemon to half HP // Restore the Pokemon to half HP
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
pokemon.getBattlerIndex(), pokemon.getBattlerIndex(),
toDmgValue(pokemon.getMaxHp() / 2), toDmgValue(pokemon.getMaxHp() / 2),
i18next.t("modifier:pokemonInstantReviveApply", { i18next.t("modifier:pokemonInstantReviveApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
}), }),
false, false,
false, false,
true, true,
),
); );
// Remove the Pokemon's FAINT status // Remove the Pokemon's FAINT status
@ -2012,7 +2007,7 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier {
} }
if (statRestored) { if (statRestored) {
globalScene.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("modifier:resetNegativeStatStageApply", { i18next.t("modifier:resetNegativeStatStageApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
typeName: this.type.name, typeName: this.type.name,
@ -2323,12 +2318,11 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LevelUpPhase( "LevelUpPhase",
globalScene.getPlayerParty().indexOf(playerPokemon), globalScene.getPlayerParty().indexOf(playerPokemon),
playerPokemon.level - levelCount.value, playerPokemon.level - levelCount.value,
playerPokemon.level, playerPokemon.level,
),
); );
return true; return true;
@ -2344,8 +2338,11 @@ export class TmModifier extends ConsumablePokemonModifier {
* @returns always `true` * @returns always `true`
*/ */
override apply(playerPokemon: PlayerPokemon): boolean { override apply(playerPokemon: PlayerPokemon): boolean {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase(globalScene.getPlayerParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM), "LearnMovePhase",
globalScene.getPlayerParty().indexOf(playerPokemon),
this.type.moveId,
LearnMoveType.TM,
); );
return true; return true;
@ -2367,13 +2364,12 @@ export class RememberMoveModifier extends ConsumablePokemonModifier {
* @returns always `true` * @returns always `true`
*/ */
override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { override apply(playerPokemon: PlayerPokemon, cost?: number): boolean {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new LearnMovePhase( "LearnMovePhase",
globalScene.getPlayerParty().indexOf(playerPokemon), globalScene.getPlayerParty().indexOf(playerPokemon),
playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex],
LearnMoveType.MEMORY, LearnMoveType.MEMORY,
cost, cost,
),
); );
return true; return true;
@ -2410,7 +2406,7 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier {
} }
if (matchingEvolution) { if (matchingEvolution) {
globalScene.unshiftPhase(new EvolutionPhase(playerPokemon, matchingEvolution, playerPokemon.level - 1)); globalScene.phaseManager.unshiftNew("EvolutionPhase", playerPokemon, matchingEvolution, playerPokemon.level - 1);
return true; return true;
} }
@ -3008,7 +3004,7 @@ export class MoneyInterestModifier extends PersistentModifier {
moneyAmount: formattedMoneyAmount, moneyAmount: formattedMoneyAmount,
typeName: this.type.name, typeName: this.type.name,
}); });
globalScene.queueMessage(message, undefined, true); globalScene.phaseManager.queueMessage(message, undefined, true);
return true; return true;
} }
@ -3262,7 +3258,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
} }
for (const mt of transferredModifierTypes) { for (const mt of transferredModifierTypes) {
globalScene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); globalScene.phaseManager.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt));
} }
return !!transferredModifierTypes.length; return !!transferredModifierTypes.length;
@ -3349,7 +3345,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif
} }
getTransferredItemCount(): number { getTransferredItemCount(): number {
return Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount() ? 1 : 0; return randSeedFloat() <= this.chance * this.getStackCount() ? 1 : 0;
} }
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string { getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierType): string {
@ -3572,19 +3568,18 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier {
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (!enemyPokemon.isFullHp()) { if (!enemyPokemon.isFullHp()) {
globalScene.unshiftPhase( globalScene.phaseManager.unshiftNew(
new PokemonHealPhase( "PokemonHealPhase",
enemyPokemon.getBattlerIndex(), enemyPokemon.getBattlerIndex(),
Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1),
i18next.t("modifier:enemyTurnHealApply", { i18next.t("modifier:enemyTurnHealApply", {
pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon), pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon),
}), }),
true, true,
false, false,
false, false,
false, false,
true, true,
),
); );
return true; return true;
} }
@ -3627,7 +3622,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi
* @returns `true` if the {@linkcode Pokemon} was affected * @returns `true` if the {@linkcode Pokemon} was affected
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { if (randSeedFloat() <= this.chance * this.getStackCount()) {
return enemyPokemon.trySetStatus(this.effect, true); return enemyPokemon.trySetStatus(this.effect, true);
} }
@ -3662,21 +3657,21 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier
} }
/** /**
* Applies {@linkcode EnemyStatusEffectHealChanceModifier} * Applies {@linkcode EnemyStatusEffectHealChanceModifier} to randomly heal status.
* @param enemyPokemon The {@linkcode Pokemon} to heal * @param enemyPokemon - The {@linkcode Pokemon} to heal
* @returns `true` if the {@linkcode Pokemon} was healed * @returns `true` if the {@linkcode Pokemon} was healed
*/ */
override apply(enemyPokemon: Pokemon): boolean { override apply(enemyPokemon: Pokemon): boolean {
if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < this.chance * this.getStackCount()) { if (!enemyPokemon.status || randSeedFloat() > this.chance * this.getStackCount()) {
globalScene.queueMessage( return false;
getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)),
);
enemyPokemon.resetStatus();
enemyPokemon.updateInfo();
return true;
} }
return false; globalScene.phaseManager.queueMessage(
getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon)),
);
enemyPokemon.resetStatus();
enemyPokemon.updateInfo();
return true;
} }
getMaxStackCount(): number { getMaxStackCount(): number {
@ -3755,7 +3750,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier {
* @returns `true` if the {@linkcode EnemyPokemon} is a fusion * @returns `true` if the {@linkcode EnemyPokemon} is a fusion
*/ */
override apply(isFusion: BooleanHolder): boolean { override apply(isFusion: BooleanHolder): boolean {
if (Phaser.Math.RND.realInRange(0, 1) >= this.chance * this.getStackCount()) { if (randSeedFloat() > this.chance * this.getStackCount()) {
return false; return false;
} }

View File

@ -1,7 +1,7 @@
import { type PokeballCounts } from "#app/battle-scene"; import { type PokeballCounts } from "#app/battle-scene";
import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { EvolutionItem } from "#app/data/balance/pokemon-evolutions";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { FormChangeItem } from "#app/data/pokemon-forms"; import { FormChangeItem } from "#enums/form-change-item";
import { type ModifierOverride } from "#app/modifier/modifier-type"; import { type ModifierOverride } from "#app/modifier/modifier-type";
import { Variant } from "#app/sprites/variant"; import { Variant } from "#app/sprites/variant";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";

581
src/phase-manager.ts Normal file
View File

@ -0,0 +1,581 @@
import type { Phase } from "#app/phase";
import type { default as Pokemon } from "#app/field/pokemon";
import type { PhaseMap, PhaseString } from "./@types/phase-types";
import { globalScene } from "#app/global-scene";
import { AddEnemyBuffModifierPhase } from "#app/phases/add-enemy-buff-modifier-phase";
import { AttemptCapturePhase } from "#app/phases/attempt-capture-phase";
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { BerryPhase } from "#app/phases/berry-phase";
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { CommandPhase } from "#app/phases/command-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
import { EggLapsePhase } from "#app/phases/egg-lapse-phase";
import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
import { EncounterPhase } from "#app/phases/encounter-phase";
import { EndCardPhase } from "#app/phases/end-card-phase";
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { EvolutionPhase } from "#app/phases/evolution-phase";
import { ExpPhase } from "#app/phases/exp-phase";
import { FaintPhase } from "#app/phases/faint-phase";
import { FormChangePhase } from "#app/phases/form-change-phase";
import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import { HidePartyExpBarPhase } from "#app/phases/hide-party-exp-bar-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { LevelCapPhase } from "#app/phases/level-cap-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase";
import { LoadMoveAnimPhase } from "#app/phases/load-move-anim-phase";
import { LoginPhase } from "#app/phases/login-phase";
import { MessagePhase } from "#app/phases/message-phase";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { MoneyRewardPhase } from "#app/phases/money-reward-phase";
import { MoveAnimPhase } from "#app/phases/move-anim-phase";
import { MoveChargePhase } from "#app/phases/move-charge-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { MoveHeaderPhase } from "#app/phases/move-header-phase";
import { MovePhase } from "#app/phases/move-phase";
import {
MysteryEncounterPhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterBattlePhase,
MysteryEncounterRewardsPhase,
PostMysteryEncounterPhase,
MysteryEncounterBattleStartCleanupPhase,
} from "#app/phases/mystery-encounter-phases";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase";
import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import { PostGameOverPhase } from "#app/phases/post-game-over-phase";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { ResetStatusPhase } from "#app/phases/reset-status-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { RevivalBlessingPhase } from "#app/phases/revival-blessing-phase";
import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
import { SelectChallengePhase } from "#app/phases/select-challenge-phase";
import { SelectGenderPhase } from "#app/phases/select-gender-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { SelectStarterPhase } from "#app/phases/select-starter-phase";
import { SelectTargetPhase } from "#app/phases/select-target-phase";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SummonMissingPhase } from "#app/phases/summon-missing-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchBiomePhase } from "#app/phases/switch-biome-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { TeraPhase } from "#app/phases/tera-phase";
import { TitlePhase } from "#app/phases/title-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { TurnStartPhase } from "#app/phases/turn-start-phase";
import { UnavailablePhase } from "#app/phases/unavailable-phase";
import { UnlockPhase } from "#app/phases/unlock-phase";
import { VictoryPhase } from "#app/phases/victory-phase";
import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
/**
* Manager for phases used by battle scene.
*
* *This file must not be imported or used directly. The manager is exclusively used by the battle scene and is not intended for external use.*
*/
/**
* Object that holds all of the phase constructors.
* This is used to create new phases dynamically using the `newPhase` method in the `PhaseManager`.
*
* @remarks
* The keys of this object are the names of the phases, and the values are the constructors of the phases.
* This allows for easy creation of new phases without needing to import each phase individually.
*/
const PHASES = Object.freeze({
AddEnemyBuffModifierPhase,
AttemptCapturePhase,
AttemptRunPhase,
BattleEndPhase,
BerryPhase,
CheckStatusEffectPhase,
CheckSwitchPhase,
CommandPhase,
CommonAnimPhase,
DamageAnimPhase,
EggHatchPhase,
EggLapsePhase,
EggSummaryPhase,
EncounterPhase,
EndCardPhase,
EndEvolutionPhase,
EnemyCommandPhase,
EvolutionPhase,
ExpPhase,
FaintPhase,
FormChangePhase,
GameOverPhase,
GameOverModifierRewardPhase,
HideAbilityPhase,
HidePartyExpBarPhase,
LearnMovePhase,
LevelCapPhase,
LevelUpPhase,
LoadMoveAnimPhase,
LoginPhase,
MessagePhase,
ModifierRewardPhase,
MoneyRewardPhase,
MoveAnimPhase,
MoveChargePhase,
MoveEffectPhase,
MoveEndPhase,
MoveHeaderPhase,
MovePhase,
MysteryEncounterPhase,
MysteryEncounterOptionSelectedPhase,
MysteryEncounterBattlePhase,
MysteryEncounterBattleStartCleanupPhase,
MysteryEncounterRewardsPhase,
PostMysteryEncounterPhase,
NewBattlePhase,
NewBiomeEncounterPhase,
NextEncounterPhase,
ObtainStatusEffectPhase,
PartyExpPhase,
PartyHealPhase,
PokemonAnimPhase,
PokemonHealPhase,
PokemonTransformPhase,
PostGameOverPhase,
PostSummonPhase,
PostTurnStatusEffectPhase,
QuietFormChangePhase,
ReloadSessionPhase,
ResetStatusPhase,
ReturnPhase,
RevivalBlessingPhase,
RibbonModifierRewardPhase,
ScanIvsPhase,
SelectBiomePhase,
SelectChallengePhase,
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
SelectTargetPhase,
ShinySparklePhase,
ShowAbilityPhase,
ShowPartyExpBarPhase,
ShowTrainerPhase,
StatStageChangePhase,
SummonMissingPhase,
SummonPhase,
SwitchBiomePhase,
SwitchPhase,
SwitchSummonPhase,
TeraPhase,
TitlePhase,
ToggleDoublePositionPhase,
TrainerVictoryPhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
UnavailablePhase,
UnlockPhase,
VictoryPhase,
WeatherEffectPhase,
});
// This type export cannot be moved to `@types`, as `Phases` is intentionally private to this file
/** Maps Phase strings to their constructors */
export type PhaseConstructorMap = typeof PHASES;
/**
* PhaseManager is responsible for managing the phases in the battle scene
*/
export class PhaseManager {
/** PhaseQueue: dequeue/remove the first element to get the next phase */
public phaseQueue: Phase[] = [];
public conditionalQueue: Array<[() => boolean, Phase]> = [];
/** PhaseQueuePrepend: is a temp storage of what will be added to PhaseQueue */
private phaseQueuePrepend: Phase[] = [];
/** overrides default of inserting phases to end of phaseQueuePrepend array. Useful for inserting Phases "out of order" */
private phaseQueuePrependSpliceIndex = -1;
private nextCommandPhaseQueue: Phase[] = [];
private currentPhase: Phase | null = null;
private standbyPhase: Phase | null = null;
/* Phase Functions */
getCurrentPhase(): Phase | null {
return this.currentPhase;
}
getStandbyPhase(): Phase | null {
return this.standbyPhase;
}
/**
* Adds a phase to the conditional queue and ensures it is executed only when the specified condition is met.
*
* This method allows deferring the execution of a phase until certain conditions are met, which is useful for handling
* situations like abilities and entry hazards that depend on specific game states.
*
* @param phase - The phase to be added to the conditional queue.
* @param condition - A function that returns a boolean indicating whether the phase should be executed.
*
*/
pushConditionalPhase(phase: Phase, condition: () => boolean): void {
this.conditionalQueue.push([condition, phase]);
}
/**
* Adds a phase to nextCommandPhaseQueue, as long as boolean passed in is false
* @param phase {@linkcode Phase} the phase to add
* @param defer boolean on which queue to add to, defaults to false, and adds to phaseQueue
*/
pushPhase(phase: Phase, defer = false): void {
(!defer ? this.phaseQueue : this.nextCommandPhaseQueue).push(phase);
}
/**
* Adds Phase(s) to the end of phaseQueuePrepend, or at phaseQueuePrependSpliceIndex
* @param phases {@linkcode Phase} the phase(s) to add
*/
unshiftPhase(...phases: Phase[]): void {
if (this.phaseQueuePrependSpliceIndex === -1) {
this.phaseQueuePrepend.push(...phases);
} else {
this.phaseQueuePrepend.splice(this.phaseQueuePrependSpliceIndex, 0, ...phases);
}
}
/**
* Clears the phaseQueue
*/
clearPhaseQueue(): void {
this.phaseQueue.splice(0, this.phaseQueue.length);
}
/**
* Clears all phase-related stuff, including all phase queues, the current and standby phases, and a splice index
*/
clearAllPhases(): void {
for (const queue of [this.phaseQueue, this.phaseQueuePrepend, this.conditionalQueue, this.nextCommandPhaseQueue]) {
queue.splice(0, queue.length);
}
this.currentPhase = null;
this.standbyPhase = null;
this.clearPhaseQueueSplice();
}
/**
* Used by function unshiftPhase(), sets index to start inserting at current length instead of the end of the array, useful if phaseQueuePrepend gets longer with Phases
*/
setPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = this.phaseQueuePrepend.length;
}
/**
* Resets phaseQueuePrependSpliceIndex to -1, implies that calls to unshiftPhase will insert at end of phaseQueuePrepend
*/
clearPhaseQueueSplice(): void {
this.phaseQueuePrependSpliceIndex = -1;
}
/**
* Is called by each Phase implementations "end()" by default
* We dump everything from phaseQueuePrepend to the start of of phaseQueue
* then removes first Phase and starts it
*/
shiftPhase(): void {
if (this.standbyPhase) {
this.currentPhase = this.standbyPhase;
this.standbyPhase = null;
return;
}
if (this.phaseQueuePrependSpliceIndex > -1) {
this.clearPhaseQueueSplice();
}
if (this.phaseQueuePrepend.length) {
while (this.phaseQueuePrepend.length) {
const poppedPhase = this.phaseQueuePrepend.pop();
if (poppedPhase) {
this.phaseQueue.unshift(poppedPhase);
}
}
}
if (!this.phaseQueue.length) {
this.populatePhaseQueue();
// Clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift() ?? null;
// Check if there are any conditional phases queued
if (this.conditionalQueue?.length) {
// Retrieve the first conditional phase from the queue
const conditionalPhase = this.conditionalQueue.shift();
// Evaluate the condition associated with the phase
if (conditionalPhase?.[0]()) {
// If the condition is met, add the phase to the phase queue
this.pushPhase(conditionalPhase[1]);
} else if (conditionalPhase) {
// If the condition is not met, re-add the phase back to the front of the conditional queue
this.conditionalQueue.unshift(conditionalPhase);
} else {
console.warn("condition phase is undefined/null!", conditionalPhase);
}
}
if (this.currentPhase) {
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
this.currentPhase.start();
}
}
overridePhase(phase: Phase): boolean {
if (this.standbyPhase) {
return false;
}
this.standbyPhase = this.currentPhase;
this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;");
phase.start();
return true;
}
/**
* Find a specific {@linkcode Phase} in the phase queue.
*
* @param phaseFilter filter function to use to find the wanted phase
* @returns the found phase or undefined if none found
*/
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
return this.phaseQueue.find(phaseFilter) as P;
}
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue[phaseIndex] = phase;
return true;
}
return false;
}
tryRemovePhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueue.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueue.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found.
* @param phaseFilter filter function
*/
tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean {
const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter);
if (phaseIndex > -1) {
this.phaseQueuePrepend.splice(phaseIndex, 1);
return true;
}
return false;
}
/**
* Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase()
* @param phase - The phase to be added
* @param targetPhase - The phase to search for in phaseQueue
* @returns boolean if a targetPhase was found and added
*/
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
if (targetIndex !== -1) {
this.phaseQueue.splice(targetIndex, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Attempt to add the input phase(s) to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()}
* @param phase - The phase(s) to be added
* @param targetPhase - The phase to search for in phaseQueue
* @returns `true` if a `targetPhase` was found to append to
*/
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) {
this.phaseQueue.splice(targetIndex + 1, 0, ...phase);
return true;
}
this.unshiftPhase(...phase);
return false;
}
/**
* Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue
* @param message - string for MessagePhase
* @param callbackDelay - optional param for MessagePhase constructor
* @param prompt - optional param for MessagePhase constructor
* @param promptDelay - optional param for MessagePhase constructor
* @param defer - Whether to allow the phase to be deferred
*
* @see {@linkcode MessagePhase} for more details on the parameters
*/
queueMessage(
message: string,
callbackDelay?: number | null,
prompt?: boolean | null,
promptDelay?: number | null,
defer?: boolean | null,
) {
const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay);
if (!defer) {
// adds to the end of PhaseQueuePrepend
this.unshiftPhase(phase);
} else {
//remember that pushPhase adds it to nextCommandPhaseQueue
this.pushPhase(phase);
}
}
/**
* Queues an ability bar flyout phase
* @param pokemon The pokemon who has the ability
* @param passive Whether the ability is a passive
* @param show Whether to show or hide the bar
*/
public queueAbilityDisplay(pokemon: Pokemon, passive: boolean, show: boolean): void {
this.unshiftPhase(show ? new ShowAbilityPhase(pokemon.getBattlerIndex(), passive) : new HideAbilityPhase());
this.clearPhaseQueueSplice();
}
/**
* Hides the ability bar if it is currently visible
*/
public hideAbilityBar(): void {
if (globalScene.abilityBar.isVisible()) {
this.unshiftPhase(new HideAbilityPhase());
}
}
/**
* Moves everything from nextCommandPhaseQueue to phaseQueue (keeping order)
*/
private populatePhaseQueue(): void {
if (this.nextCommandPhaseQueue.length) {
this.phaseQueue.push(...this.nextCommandPhaseQueue);
this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length);
}
this.phaseQueue.push(new TurnInitPhase());
}
/**
* Dynamically create the named phase from the provided arguments
*
* @remarks
* Used to avoid importing each phase individually, allowing for dynamic creation of phases.
* @param phase - The name of the phase to create.
* @param args - The arguments to pass to the phase constructor.
* @returns The requested phase instance
*/
public create<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): PhaseMap[T] {
const PhaseClass = PHASES[phase];
if (!PhaseClass) {
throw new Error(`Phase ${phase} does not exist in PhaseMap.`);
}
// @ts-expect-error: Typescript does not support narrowing the type of operands in generic methods (see https://stackoverflow.com/a/72891234)
return new PhaseClass(...args);
}
/**
* Create a new phase and immediately push it to the phase queue. Equivalent to calling {@linkcode create} followed by {@linkcode pushPhase}.
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
*/
public pushNew<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): void {
this.pushPhase(this.create(phase, ...args));
}
/**
* Create a new phase and immediately unshift it to the phase queue. Equivalent to calling {@linkcode create} followed by {@linkcode unshiftPhase}.
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
*/
public unshiftNew<T extends PhaseString>(phase: T, ...args: ConstructorParameters<PhaseConstructorMap[T]>): void {
this.unshiftPhase(this.create(phase, ...args));
}
/**
* Create a new phase and immediately prepend it to an existing phase in the phase queue.
* Equivalent to calling {@linkcode create} followed by {@linkcode prependToPhase}.
* @param targetPhase - The phase to search for in phaseQueue
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
* @returns `true` if a `targetPhase` was found to prepend to
*/
public prependNewToPhase<T extends PhaseString>(
targetPhase: PhaseString,
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): boolean {
return this.prependToPhase(this.create(phase, ...args), targetPhase);
}
/**
* Create a new phase and immediately append it to an existing phase the phase queue.
* Equivalent to calling {@linkcode create} followed by {@linkcode appendToPhase}.
* @param targetPhase - The phase to search for in phaseQueue
* @param phase - The name of the phase to create
* @param args - The arguments to pass to the phase constructor
* @returns `true` if a `targetPhase` was found to append to
*/
public appendNewToPhase<T extends PhaseString>(
targetPhase: PhaseString,
phase: T,
...args: ConstructorParameters<PhaseConstructorMap[T]>
): boolean {
return this.appendToPhase(this.create(phase, ...args), targetPhase);
}
}

View File

@ -5,7 +5,7 @@ export abstract class Phase {
start() {} start() {}
end() { end() {
globalScene.shiftPhase(); globalScene.phaseManager.shiftPhase();
} }
/** /**

Some files were not shown because too many files have changed in this diff Show More