Merge branch 'beta' into pokedex-category-flavortext

This commit is contained in:
damocleas 2025-06-13 21:18:48 -04:00 committed by GitHub
commit 72ea7f6819
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
399 changed files with 6200 additions and 4784 deletions

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

@ -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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

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,11 +1,27 @@
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ability";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { BattleStat } from "#enums/stat"; import type { BattleStat } from "#enums/stat";
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void; // Intentionally re-export all types from the ability attributes module
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean; export type * from "#app/data/abilities/ability";
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => void;
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => boolean;
export type AbAttrCondition = (pokemon: Pokemon) => boolean; export type AbAttrCondition = (pokemon: Pokemon) => boolean;
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean; export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
/**
* Union type of all ability attribute class names as strings
*/
export type AbAttrString = keyof AbAttrConstructorMap;
/**
* Map of ability attribute class names to an instance of the class.
*/
export type AbAttrMap = {
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
};

View File

@ -0,0 +1,32 @@
/**
* Re-exports of all the types defined in the modifier module.
*/
import type Pokemon from "#app/field/pokemon";
import type { ModifierConstructorMap } from "#app/modifier/modifier";
import type { ModifierType, WeightedModifierType } from "#app/modifier/modifier-type";
export type ModifierTypeFunc = () => ModifierType;
export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
export type { ModifierConstructorMap } from "#app/modifier/modifier";
/**
* Map of modifier names to their respective instance types
*/
export type ModifierInstanceMap = {
[K in keyof ModifierConstructorMap]: InstanceType<ModifierConstructorMap[K]>;
};
/**
* Union type of all modifier constructors.
*/
export type ModifierClass = ModifierConstructorMap[keyof ModifierConstructorMap];
/**
* Union type of all modifier names as strings.
*/
export type ModifierString = keyof ModifierConstructorMap;
export type ModifierPool = {
[tier: string]: WeightedModifierType[];
}

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

@ -58,35 +58,29 @@ import {
getEnemyModifierTypesForWave, getEnemyModifierTypesForWave,
getLuckString, getLuckString,
getLuckTextTint, getLuckTextTint,
getModifierPoolForType,
getModifierType,
getPartyLuckValue, getPartyLuckValue,
ModifierPoolType,
modifierTypes,
PokemonHeldItemModifierType, PokemonHeldItemModifierType,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import { getModifierType } from "./utils/modifier-utils";
import { modifierTypes } from "./data/data-lists";
import { getModifierPoolForType } from "./utils/modifier-utils";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import AbilityBar from "#app/ui/ability-bar"; import AbilityBar from "#app/ui/ability-bar";
import { import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostBattleInitAbAttrs,
applyPostItemLostAbAttrs,
BlockItemTheftAbAttr,
DoubleBattleChanceAbAttr,
PostBattleInitAbAttr,
PostItemLostAbAttr,
} from "#app/data/abilities/ability";
import { allAbilities } from "./data/data-lists"; import { allAbilities } from "./data/data-lists";
import type { FixedBattleConfig } from "#app/battle"; 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,13 +95,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 { 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";
@ -144,14 +137,13 @@ import { LoadingScene } from "#app/loading-scene";
import type { MovePhase } from "#app/phases/move-phase"; import type { MovePhase } from "#app/phases/move-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 { allMysteryEncounters, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters";
import { import {
allMysteryEncounters,
ANTI_VARIANCE_WEIGHT_MODIFIER, ANTI_VARIANCE_WEIGHT_MODIFIER,
AVERAGE_ENCOUNTERS_PER_RUN_TARGET, AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT,
MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT,
mysteryEncountersByBiome, } from "./constants";
} from "#app/data/mystery-encounters/mystery-encounters";
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";
@ -1263,7 +1255,7 @@ export default class BattleScene extends SceneBase {
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
for (const p of playerField) { for (const p of playerField) {
applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance); applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
} }
return Math.max(doubleChance.value, 1); return Math.max(doubleChance.value, 1);
} }
@ -1468,7 +1460,7 @@ export default class BattleScene extends SceneBase {
for (const pokemon of this.getPlayerParty()) { for (const pokemon of this.getPlayerParty()) {
pokemon.resetBattleAndWaveData(); pokemon.resetBattleAndWaveData();
pokemon.resetTera(); pokemon.resetTera();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
if ( if (
pokemon.hasSpecies(SpeciesId.TERAPAGOS) || pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190) (this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
@ -1643,6 +1635,9 @@ export default class BattleScene extends SceneBase {
case SpeciesId.TATSUGIRI: case SpeciesId.TATSUGIRI:
case SpeciesId.PALDEA_TAUROS: case SpeciesId.PALDEA_TAUROS:
return randSeedInt(species.forms.length); return randSeedInt(species.forms.length);
case SpeciesId.MAUSHOLD:
case SpeciesId.DUDUNSPARCE:
return !randSeedInt(4) ? 1 : 0;
case SpeciesId.PIKACHU: case SpeciesId.PIKACHU:
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) {
return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30
@ -2744,7 +2739,7 @@ export default class BattleScene extends SceneBase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (source && source.isPlayer() !== target.isPlayer()) { if (source && source.isPlayer() !== target.isPlayer()) {
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
} }
if (cancelled.value) { if (cancelled.value) {
@ -2784,13 +2779,13 @@ export default class BattleScene extends SceneBase {
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) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
} }
return true; return true;
} }
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
if (source && itemLost) { if (source && itemLost) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
} }
return true; return true;
} }
@ -2813,7 +2808,7 @@ export default class BattleScene extends SceneBase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (source && source.isPlayer() !== target.isPlayer()) { if (source && source.isPlayer() !== target.isPlayer()) {
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
} }
if (cancelled.value) { if (cancelled.value) {

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,
@ -10,9 +10,10 @@ import {
randInt, randInt,
randSeedFloat, 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, type PokemonHeldItemModifier } from "./modifier/modifier";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
@ -29,18 +30,11 @@ import i18next from "#app/plugins/i18n";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/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;
@ -179,7 +173,7 @@ export default class Battle {
this.postBattleLoot.push( this.postBattleLoot.push(
...globalScene ...globalScene
.findModifiers( .findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable,
false, false,
) )
.map(i => { .map(i => {

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,78 @@ 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
/**
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
*/
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3;
/**
* The divisor for determining ME spawns, defines the "maximum" weight required for a spawn
* If spawn_weight === MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, 100% chance to spawn a ME
*/
export const MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT = 256;
/**
* When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks.
* These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
*/
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3;
/**
* Specifies the target average for total ME spawns in a single Classic run.
* Used by anti-variance mechanic to check whether a run is above or below the target on a given wave.
*/
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12;
/**
* Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET
* Example:
* AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors)
* ANTI_VARIANCE_WEIGHT_MODIFIER = 15
*
* On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs.
* So anti-variance adds 0/256 to the spawn weight check for ME spawn.
*
* On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME.
* So anti-variance adds 15/256 to the spawn weight check for ME spawn.
*
* On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME.
* So anti-variance adds -15/256 to the spawn weight check for ME spawn.
*/
export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;

View File

@ -1,58 +0,0 @@
import type { AbAttrCondition } from "#app/@types/ability-types";
import type Pokemon from "#app/field/pokemon";
import type { BooleanHolder } from "#app/utils/common";
export abstract class AbAttr {
public showAbility: boolean;
private extraCondition: AbAttrCondition;
/**
* @param showAbility - Whether to show this ability as a flyout during battle; default `true`.
* Should be kept in parity with mainline where possible.
*/
constructor(showAbility = true) {
this.showAbility = showAbility;
}
/**
* Applies ability effects without checking conditions
* @param _pokemon - The pokemon to apply this ability to
* @param _passive - Whether or not the ability is a passive
* @param _simulated - Whether the call is simulated
* @param _args - Extra args passed to the function. Handled by child classes.
* @see {@linkcode canApply}
*/
apply(
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_cancelled: BooleanHolder | null,
_args: any[],
): void {}
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null;
}
getCondition(): AbAttrCondition | null {
return this.extraCondition || null;
}
addCondition(condition: AbAttrCondition): AbAttr {
this.extraCondition = condition;
return this;
}
/**
* Returns a boolean describing whether the ability can be applied under current conditions
* @param _pokemon - The pokemon to apply this ability to
* @param _passive - Whether or not the ability is a passive
* @param _simulated - Whether the call is simulated
* @param _args - Extra args passed to the function. Handled by child classes.
* @returns `true` if the ability can be applied, `false` otherwise
* @see {@linkcode apply}
*/
canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
return true;
}
}

View File

@ -1,137 +0,0 @@
import { AbilityId } from "#enums/ability-id";
import type { AbAttrCondition } from "#app/@types/ability-types";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import i18next from "i18next";
import type { Localizable } from "#app/@types/locales";
import type { Constructor } from "#app/utils/common";
export class Ability implements Localizable {
public id: AbilityId;
private nameAppend: string;
public name: string;
public description: string;
public generation: number;
public isBypassFaint: boolean;
public isIgnorable: boolean;
public isSuppressable = true;
public isCopiable = true;
public isReplaceable = true;
public attrs: AbAttr[];
public conditions: AbAttrCondition[];
constructor(id: AbilityId, generation: number) {
this.id = id;
this.nameAppend = "";
this.generation = generation;
this.attrs = [];
this.conditions = [];
this.isSuppressable = true;
this.isCopiable = true;
this.isReplaceable = true;
this.localize();
}
public get isSwappable(): boolean {
return this.isCopiable && this.isReplaceable;
}
localize(): void {
const i18nKey = AbilityId[this.id]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as string;
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
}
/**
* Get all ability attributes that match `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends AbAttr>(attrType: Constructor<T>): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
/**
* Check if an ability has an attribute that matches `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns true if the ability has attribute `attrType`
*/
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some(attr => attr instanceof attrType);
}
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
this.attrs.push(attr);
return this;
}
conditionalAttr<T extends Constructor<AbAttr>>(
condition: AbAttrCondition,
AttrType: T,
...args: ConstructorParameters<T>
): Ability {
const attr = new AttrType(...args);
attr.addCondition(condition);
this.attrs.push(attr);
return this;
}
bypassFaint(): Ability {
this.isBypassFaint = true;
return this;
}
ignorable(): Ability {
this.isIgnorable = true;
return this;
}
unsuppressable(): Ability {
this.isSuppressable = false;
return this;
}
uncopiable(): Ability {
this.isCopiable = false;
return this;
}
unreplaceable(): Ability {
this.isReplaceable = false;
return this;
}
condition(condition: AbAttrCondition): Ability {
this.conditions.push(condition);
return this;
}
partial(): this {
this.nameAppend += " (P)";
return this;
}
unimplemented(): this {
this.nameAppend += " (N)";
return this;
}
/**
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
* @returns the ability
*/
edgeCase(): this {
return this;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,832 @@
import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types";
import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import type { BooleanHolder, NumberHolder } from "#app/utils/common";
import type { BattlerIndex } from "#enums/battler-index";
import type { HitResult } from "#enums/hit-result";
import type { BattleStat, Stat } from "#enums/stat";
import type { StatusEffect } from "#enums/status-effect";
import type { WeatherType } from "#enums/weather-type";
import type { BattlerTag } from "../battler-tags";
import type Move from "../moves/move";
import type { PokemonMove } from "../moves/pokemon-move";
import type { TerrainType } from "../terrain";
import type { Weather } from "../weather";
import type {
PostBattleInitAbAttr,
PreDefendAbAttr,
PostDefendAbAttr,
PostMoveUsedAbAttr,
StatMultiplierAbAttr,
AllyStatMultiplierAbAttr,
PostSetStatusAbAttr,
PostDamageAbAttr,
FieldMultiplyStatAbAttr,
PreAttackAbAttr,
ExecutedMoveAbAttr,
PostAttackAbAttr,
PostKnockOutAbAttr,
PostVictoryAbAttr,
PostSummonAbAttr,
PreSummonAbAttr,
PreSwitchOutAbAttr,
PreLeaveFieldAbAttr,
PreStatStageChangeAbAttr,
PostStatStageChangeAbAttr,
PreSetStatusAbAttr,
PreApplyBattlerTagAbAttr,
PreWeatherEffectAbAttr,
PreWeatherDamageAbAttr,
PostTurnAbAttr,
PostWeatherChangeAbAttr,
PostWeatherLapseAbAttr,
PostTerrainChangeAbAttr,
CheckTrappedAbAttr,
PostBattleAbAttr,
PostFaintAbAttr,
PostItemLostAbAttr,
} from "./ability";
function applySingleAbAttrs<T extends AbAttrString>(
pokemon: Pokemon,
passive: boolean,
attrType: T,
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
args: any[],
gainedMidTurn = false,
simulated = false,
messages: string[] = [],
) {
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
return;
}
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
if (
gainedMidTurn &&
ability.getAttrs(attrType).some(attr => {
attr.is("PostSummonAbAttr") && !attr.shouldActivateOnGain();
})
) {
return;
}
for (const attr of ability.getAttrs(attrType)) {
const condition = attr.getCondition();
let abShown = false;
if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) {
continue;
}
globalScene.phaseManager.setPhaseQueueSplice();
if (attr.showAbility && !simulated) {
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
abShown = true;
}
const message = attr.getTriggerMessage(pokemon, ability.name, args);
if (message) {
if (!simulated) {
globalScene.phaseManager.queueMessage(message);
}
messages.push(message);
}
applyFunc(attr, passive);
if (abShown) {
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
}
if (!simulated) {
pokemon.waveData.abilitiesApplied.add(ability.id);
}
globalScene.phaseManager.clearPhaseQueueSplice();
}
}
function applyAbAttrsInternal<T extends AbAttrString>(
attrType: T,
pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
args: any[],
simulated = false,
messages: string[] = [],
gainedMidTurn = false,
) {
for (const passive of [false, true]) {
if (pokemon) {
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages);
globalScene.phaseManager.clearPhaseQueueSplice();
}
}
}
export function applyAbAttrs<T extends AbAttrString>(
attrType: T,
pokemon: Pokemon,
cancelled: BooleanHolder | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal<T>(
attrType,
pokemon,
// @ts-expect-error: TODO: fix the error on `cancelled`
(attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
simulated,
);
}
// TODO: Improve the type signatures of the following methods / refactor the apply methods
export function applyPostBattleInitAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreDefendAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never,
pokemon: Pokemon,
attacker: Pokemon,
move: Move | null,
cancelled: BooleanHolder | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
(attr, passive) =>
(attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
args,
simulated,
);
}
export function applyPostDefendAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never,
pokemon: Pokemon,
attacker: Pokemon,
move: Move,
hitResult: HitResult | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
(attr, passive) =>
(attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
args,
simulated,
);
}
export function applyPostMoveUsedAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never,
pokemon: Pokemon,
move: PokemonMove,
source: Pokemon,
targets: BattlerIndex[],
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args),
(attr, _passive) =>
(attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args),
args,
simulated,
);
}
export function applyStatMultiplierAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never,
pokemon: Pokemon,
stat: BattleStat,
statValue: NumberHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args),
(attr, passive) =>
(attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args),
args,
);
}
/**
* Applies an ally's Stat multiplier attribute
* @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
* @param pokemon - The {@linkcode Pokemon} with the ability
* @param stat - The type of the checked {@linkcode Stat}
* @param statValue - {@linkcode NumberHolder} containing the value of the checked stat
* @param checkedPokemon - The {@linkcode Pokemon} with the checked stat
* @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move.
* @param args - unused
*/
export function applyAllyStatMultiplierAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never,
pokemon: Pokemon,
stat: BattleStat,
statValue: NumberHolder,
simulated = false,
checkedPokemon: Pokemon,
ignoreAbility: boolean,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as AllyStatMultiplierAbAttr).applyAllyStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
ignoreAbility,
args,
),
(attr, passive) =>
(attr as AllyStatMultiplierAbAttr).canApplyAllyStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
ignoreAbility,
args,
),
args,
simulated,
);
}
export function applyPostSetStatusAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never,
pokemon: Pokemon,
effect: StatusEffect,
sourcePokemon?: Pokemon | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
(attr, passive) =>
(attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
args,
simulated,
);
}
export function applyPostDamageAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never,
pokemon: Pokemon,
damage: number,
_passive: boolean,
simulated = false,
args: any[],
source?: Pokemon,
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source),
(attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source),
args,
);
}
/**
* Applies a field Stat multiplier attribute
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
* @param pokemon {@linkcode Pokemon} the Pokemon applying this ability
* @param stat {@linkcode Stat} the type of the checked stat
* @param statValue {@linkcode NumberHolder} the value of the checked stat
* @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat
* @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat
* @param args unused
*/
export function applyFieldStatMultiplierAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never,
pokemon: Pokemon,
stat: Stat,
statValue: NumberHolder,
checkedPokemon: Pokemon,
hasApplied: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as FieldMultiplyStatAbAttr).applyFieldStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
hasApplied,
args,
),
(attr, passive) =>
(attr as FieldMultiplyStatAbAttr).canApplyFieldStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
hasApplied,
args,
),
args,
);
}
export function applyPreAttackAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never,
pokemon: Pokemon,
defender: Pokemon | null,
move: Move,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args),
(attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args),
args,
simulated,
);
}
export function applyExecutedMoveAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated),
attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated),
args,
simulated,
);
}
export function applyPostAttackAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never,
pokemon: Pokemon,
defender: Pokemon,
move: Move,
hitResult: HitResult | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
(attr, passive) =>
(attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
args,
simulated,
);
}
export function applyPostKnockOutAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never,
pokemon: Pokemon,
knockedOut: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
(attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
args,
simulated,
);
}
export function applyPostVictoryAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPostSummonAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never,
pokemon: Pokemon,
passive = false,
simulated = false,
...args: any[]
): void {
applySingleAbAttrs(
pokemon,
passive,
attrType,
(attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args),
args,
false,
simulated,
);
}
export function applyPreSummonAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never,
pokemon: Pokemon,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args),
(attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args),
args,
);
}
export function applyPreSwitchOutAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args),
(attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreLeaveFieldAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args),
(attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreStatStageChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never,
pokemon: Pokemon | null,
stat: BattleStat,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
(attr, passive) =>
(attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
args,
simulated,
);
}
export function applyPostStatStageChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never,
pokemon: Pokemon,
stats: BattleStat[],
stages: number,
selfTarget: boolean,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, _passive) =>
(attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args),
(attr, _passive) =>
(attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange(
pokemon,
simulated,
stats,
stages,
selfTarget,
args,
),
args,
simulated,
);
}
export function applyPreSetStatusAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never,
pokemon: Pokemon,
effect: StatusEffect | undefined,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
(attr, passive) =>
(attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
args,
simulated,
);
}
export function applyPreApplyBattlerTagAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never,
pokemon: Pokemon,
tag: BattlerTag,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
(attr, passive) =>
(attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
args,
simulated,
);
}
export function applyPreWeatherEffectAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never,
pokemon: Pokemon,
weather: Weather | null,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
(attr, passive) =>
(attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
args,
simulated,
);
}
export function applyPostTurnAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPostWeatherChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never,
pokemon: Pokemon,
weather: WeatherType,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args),
(attr, passive) =>
(attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args),
args,
simulated,
);
}
export function applyPostWeatherLapseAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never,
pokemon: Pokemon,
weather: Weather | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args),
(attr, passive) =>
(attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args),
args,
simulated,
);
}
export function applyPostTerrainChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never,
pokemon: Pokemon,
terrain: TerrainType,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args),
(attr, passive) =>
(attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args),
args,
simulated,
);
}
export function applyCheckTrappedAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never,
pokemon: Pokemon,
trapped: BooleanHolder,
otherPokemon: Pokemon,
messages: string[],
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
(attr, passive) =>
(attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
args,
simulated,
messages,
);
}
export function applyPostBattleAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPostFaintAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never,
pokemon: Pokemon,
attacker?: Pokemon,
move?: Move,
hitResult?: HitResult,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
(attr, passive) =>
(attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
args,
simulated,
);
}
export function applyPostItemLostAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args),
(attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args),
args,
);
}
/**
* Applies abilities when they become active mid-turn (ability switch)
*
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
*/
export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs(
pokemon,
passive,
"PostSummonAbAttr",
(attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args),
(attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args),
args,
true,
simulated,
);
}
/**
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
*/
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs(
pokemon,
passive,
"PreLeaveFieldAbAttr",
(attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]),
(attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]),
args,
true,
simulated,
);
applySingleAbAttrs(
pokemon,
passive,
"IllusionBreakAbAttr",
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
true,
simulated,
);
}

View File

@ -7,31 +7,19 @@ 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 { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "./abilities/apply-ab-attrs";
BlockNonDirectDamageAbAttr,
InfiltratorAbAttr,
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
ProtectStatAbAttr,
applyAbAttrs,
applyOnGainAbAttrs,
applyOnLoseAbAttrs,
} 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 { ArenaTagSide } from "#enums/arena-tag-side";
export enum ArenaTagSide {
BOTH,
PLAYER,
ENEMY,
}
export abstract class ArenaTag { export abstract class ArenaTag {
constructor( constructor(
@ -148,7 +136,7 @@ export class MistTag extends ArenaTag {
if (attacker) { if (attacker) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated // TODO: Allow this to be simulated
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
if (bypassed.value) { if (bypassed.value) {
return false; return false;
} }
@ -213,7 +201,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
): boolean { ): boolean {
if (this.weakenedCategories.includes(moveCategory)) { if (this.weakenedCategories.includes(moveCategory)) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
if (bypassed.value) { if (bypassed.value) {
return false; return false;
} }
@ -769,7 +757,7 @@ class SpikesTag extends ArenaTrapTag {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (simulated || cancelled.value) { if (simulated || cancelled.value) {
return !cancelled.value; return !cancelled.value;
} }
@ -957,7 +945,7 @@ class StealthRockTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -1014,7 +1002,7 @@ class StickyWebTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
if (simulated) { if (simulated) {
return !cancelled.value; return !cancelled.value;
@ -1448,8 +1436,8 @@ export class SuppressAbilitiesTag extends ArenaTag {
// Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all
const setter = globalScene const setter = globalScene
.getField() .getField()
.filter(p => p?.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0]; .filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)); applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
} }
} }
@ -1461,7 +1449,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
// There is only one pokemon with this attr on the field on removal, so its abilities are already active // There is only one pokemon with this attr on the field on removal, so its abilities are already active
if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) { if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive)); [true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
} }
} }

View File

@ -11,7 +11,6 @@ import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, SpeciesStatBoosterModifier, TempExtraModifierModifier } from "#app/modifier/modifier";
import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type"; import type { SpeciesStatBoosterModifierType } from "#app/modifier/modifier-type";
import { speciesStarterCosts } from "./starters"; import { speciesStarterCosts } from "./starters";
import i18next from "i18next"; import i18next from "i18next";
@ -275,9 +274,9 @@ class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition {
class TreasureEvolutionCondition extends SpeciesEvolutionCondition { class TreasureEvolutionCondition extends SpeciesEvolutionCondition {
constructor() { constructor() {
super(p => p.evoCounter super(p => p.evoCounter
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length
+ globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier + globalScene.findModifiers(m => m.is("MoneyMultiplierModifier")
|| m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9); || m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier")).length > 9);
this.description = i18next.t("pokemonEvolutions:treasure"); this.description = i18next.t("pokemonEvolutions:treasure");
} }
} }
@ -1794,8 +1793,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
], ],
[SpeciesId.CLAMPERL]: [ [SpeciesId.CLAMPERL]: [
// TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework // TODO: Change the SpeciesEvolutionConditions here to use a bespoke HeldItemEvolutionCondition after the modifier rework
new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesEvolution(SpeciesId.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_TOOTH")), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m instanceof SpeciesStatBoosterModifier && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(SpeciesId.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.getHeldItems().some(m => m.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG)
], ],
[SpeciesId.BOLDORE]: [ [SpeciesId.BOLDORE]: [
new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)

View File

@ -1,4 +1,4 @@
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
@ -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,16 @@
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, coerceArray } 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 { import { BattlerTagType } from "#enums/battler-tag-type";
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 +436,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,10 +447,9 @@ 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] instanceof SelfStatusMove : allMoves[move].is("SelfStatusMove")
? MoveId.FOCUS_ENERGY ? MoveId.FOCUS_ENERGY
: MoveId.TAIL_WHIP; : MoveId.TAIL_WHIP;
@ -570,7 +474,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 {
@ -616,7 +520,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
* @param encounterAnim one or more animations to fetch * @param encounterAnim one or more animations to fetch
*/ */
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> { export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim]; const anims = coerceArray(encounterAnim);
const encounterAnimNames = getEnumKeys(EncounterAnim); const encounterAnimNames = getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = []; const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) { for (const anim of anims) {
@ -703,7 +607,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?
@ -867,7 +771,7 @@ export abstract class BattleAnim {
const user = !isOppAnim ? this.user : this.target; const user = !isOppAnim ? this.user : this.target;
const target = !isOppAnim ? this.target : this.user; const target = !isOppAnim ? this.target : this.user;
const targetSubstitute = onSubstitute && user !== target ? target!.getTag(SubstituteTag) : null; const targetSubstitute = onSubstitute && user !== target ? target!.getTag(BattlerTagType.SUBSTITUTE) : null;
const userInitialX = user!.x; // TODO: is this bang correct? const userInitialX = user!.x; // TODO: is this bang correct?
const userInitialY = user!.y; // TODO: is this bang correct? const userInitialY = user!.y; // TODO: is this bang correct?

View File

@ -1,37 +1,27 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { import { applyAbAttrs } from "./abilities/apply-ab-attrs";
applyAbAttrs,
BlockNonDirectDamageAbAttr,
FlinchEffectAbAttr,
ProtectStatAbAttr,
ConditionalUserFieldProtectStatAbAttr,
ReverseDrainAbAttr,
} from "#app/data/abilities/ability";
import { allAbilities } from "./data-lists"; import { allAbilities } from "./data-lists";
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { import { applyMoveAttrs } from "./moves/apply-attrs";
applyMoveAttrs,
ConsecutiveUseDoublePowerAttr,
HealOnAllyAttr,
StatusCategoryOnAllyAttr,
} from "#app/data/moves/move";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeAbilityTrigger } from "./pokemon-forms/form-change-triggers";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult, MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result";
import { HitResult } from "#enums/hit-result";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
import type { MovePhase } from "#app/phases/move-phase"; import type { MovePhase } from "#app/phases/move-phase";
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common"; import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
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 { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -41,19 +31,7 @@ import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
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,
}
export class BattlerTag { export class BattlerTag {
public tagType: BattlerTagType; public tagType: BattlerTagType;
@ -72,7 +50,7 @@ export class BattlerTag {
isBatonPassable = false, isBatonPassable = false,
) { ) {
this.tagType = tagType; this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType]; this.lapseTypes = coerceArray(lapseType);
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct? this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId; this.sourceId = sourceId;
@ -663,7 +641,7 @@ export class FlinchedTag extends BattlerTag {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
applyAbAttrs(FlinchEffectAbAttr, pokemon, null); applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
return true; return true;
} }
@ -957,7 +935,7 @@ export class SeedTag extends BattlerTag {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) { if (source) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
@ -968,7 +946,7 @@ export class SeedTag extends BattlerTag {
); );
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"PokemonHealPhase", "PokemonHealPhase",
source.getBattlerIndex(), source.getBattlerIndex(),
@ -1041,7 +1019,7 @@ export class PowderTag extends BattlerTag {
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
const cancelDamage = new BooleanHolder(false); const cancelDamage = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
if (!cancelDamage.value) { if (!cancelDamage.value) {
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
} }
@ -1094,7 +1072,7 @@ export class NightmareTag extends BattlerTag {
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -1453,7 +1431,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim); phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
@ -1696,7 +1674,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
*/ */
override onContact(attacker: Pokemon, user: Pokemon): void { override onContact(attacker: Pokemon, user: Pokemon): void {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT, result: HitResult.INDIRECT,
@ -2292,7 +2270,7 @@ export class SaltCuredTag extends BattlerTag {
); );
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER); const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
@ -2346,7 +2324,7 @@ export class CursedTag extends BattlerTag {
); );
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -2681,7 +2659,7 @@ export class GulpMissileTag extends BattlerTag {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT }); attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
@ -2804,8 +2782,8 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
*/ */
override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) { override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) {
const moveCategory = new NumberHolder(allMoves[move].category); const moveCategory = new NumberHolder(allMoves[move].category);
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); applyMoveAttrs("StatusCategoryOnAllyAttr", user, target, allMoves[move], moveCategory);
return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS; return allMoves[move].hasAttr("HealOnAllyAttr") && moveCategory.value === MoveCategory.STATUS;
} }
/** /**
@ -3071,8 +3049,8 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
if (lapseType === BattlerTagLapseType.CUSTOM) { if (lapseType === BattlerTagLapseType.CUSTOM) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
applyAbAttrs(ConditionalUserFieldProtectStatAbAttr, pokemon, cancelled, false, pokemon); applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
if (!cancelled.value) { if (!cancelled.value) {
if (pokemon.mysteryEncounterBattleEffects) { if (pokemon.mysteryEncounterBattleEffects) {
pokemon.mysteryEncounterBattleEffects(pokemon); pokemon.mysteryEncounterBattleEffects(pokemon);
@ -3141,7 +3119,7 @@ export class TormentTag extends MoveRestrictionBattlerTag {
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
const moveObj = allMoves[lastMove.move]; const moveObj = allMoves[lastMove.move];
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY); const isUnaffected = moveObj.hasAttr("ConsecutiveUseDoublePowerAttr") || user.getTag(BattlerTagType.FRENZY);
const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS; const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS;
return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected; return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected;
} }

View File

@ -1,9 +1,9 @@
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 { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability"; import { applyAbAttrs } from "./abilities/apply-ab-attrs";
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";
@ -38,25 +38,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: case BerryType.STARF:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25; return pokemon.getHpRatio() < 0.25;
}; };
case BerryType.LEPPA: case BerryType.LEPPA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return !!pokemon.getMoveset().find(m => !m.getPpRatio()); return !!pokemon.getMoveset().find(m => !m.getPpRatio());
}; };
} }
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.ENIGMA: case BerryType.ENIGMA:
{ {
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.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"PokemonHealPhase", "PokemonHealPhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),
@ -105,7 +105,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc. // Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
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.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"StatStageChangePhase", "StatStageChangePhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),
@ -126,7 +126,7 @@ 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.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"StatStageChangePhase", "StatStageChangePhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),

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";
@ -20,97 +21,16 @@ import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import { TypeColor, TypeShadow } from "#enums/color"; import { TypeColor, TypeShadow } from "#enums/color";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/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,5 +1,9 @@
import type { Ability } from "./abilities/ability-class"; import type { ModifierTypes } from "#app/modifier/modifier-type";
import type { Ability } from "./abilities/ability";
import type Move from "./moves/move"; import type Move from "./moves/move";
export const allAbilities: Ability[] = []; export const allAbilities: Ability[] = [];
export const allMoves: Move[] = []; export const allMoves: Move[] = [];
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
export const modifierTypes = {} as ModifierTypes;

View File

@ -1723,49 +1723,6 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
], ],
}; };
export const doubleBattleDialogue = {
blue_red_double: {
encounter: ["doubleBattleDialogue:blue_red_double.encounter.1"],
victory: ["doubleBattleDialogue:blue_red_double.victory.1"],
},
red_blue_double: {
encounter: ["doubleBattleDialogue:red_blue_double.encounter.1"],
victory: ["doubleBattleDialogue:red_blue_double.victory.1"],
},
tate_liza_double: {
encounter: ["doubleBattleDialogue:tate_liza_double.encounter.1"],
victory: ["doubleBattleDialogue:tate_liza_double.victory.1"],
},
liza_tate_double: {
encounter: ["doubleBattleDialogue:liza_tate_double.encounter.1"],
victory: ["doubleBattleDialogue:liza_tate_double.victory.1"],
},
wallace_steven_double: {
encounter: ["doubleBattleDialogue:wallace_steven_double.encounter.1"],
victory: ["doubleBattleDialogue:wallace_steven_double.victory.1"],
},
steven_wallace_double: {
encounter: ["doubleBattleDialogue:steven_wallace_double.encounter.1"],
victory: ["doubleBattleDialogue:steven_wallace_double.victory.1"],
},
alder_iris_double: {
encounter: ["doubleBattleDialogue:alder_iris_double.encounter.1"],
victory: ["doubleBattleDialogue:alder_iris_double.victory.1"],
},
iris_alder_double: {
encounter: ["doubleBattleDialogue:iris_alder_double.encounter.1"],
victory: ["doubleBattleDialogue:iris_alder_double.victory.1"],
},
marnie_piers_double: {
encounter: ["doubleBattleDialogue:marnie_piers_double.encounter.1"],
victory: ["doubleBattleDialogue:marnie_piers_double.victory.1"],
},
piers_marnie_double: {
encounter: ["doubleBattleDialogue:piers_marnie_double.encounter.1"],
victory: ["doubleBattleDialogue:piers_marnie_double.victory.1"],
},
};
export const battleSpecDialogue = { export const battleSpecDialogue = {
[BattleSpec.FINAL_BOSS]: { [BattleSpec.FINAL_BOSS]: {
encounter: "battleSpecDialogue:encounter", encounter: "battleSpecDialogue:encounter",

View File

@ -0,0 +1,44 @@
// TODO: Move this back into `dialogue.ts` after finding a suitable way to remove the circular dependencies
// that caused this to be moved out in the first place
export const doubleBattleDialogue = {
blue_red_double: {
encounter: ["doubleBattleDialogue:blue_red_double.encounter.1"],
victory: ["doubleBattleDialogue:blue_red_double.victory.1"],
},
red_blue_double: {
encounter: ["doubleBattleDialogue:red_blue_double.encounter.1"],
victory: ["doubleBattleDialogue:red_blue_double.victory.1"],
},
tate_liza_double: {
encounter: ["doubleBattleDialogue:tate_liza_double.encounter.1"],
victory: ["doubleBattleDialogue:tate_liza_double.victory.1"],
},
liza_tate_double: {
encounter: ["doubleBattleDialogue:liza_tate_double.encounter.1"],
victory: ["doubleBattleDialogue:liza_tate_double.victory.1"],
},
wallace_steven_double: {
encounter: ["doubleBattleDialogue:wallace_steven_double.encounter.1"],
victory: ["doubleBattleDialogue:wallace_steven_double.victory.1"],
},
steven_wallace_double: {
encounter: ["doubleBattleDialogue:steven_wallace_double.encounter.1"],
victory: ["doubleBattleDialogue:steven_wallace_double.victory.1"],
},
alder_iris_double: {
encounter: ["doubleBattleDialogue:alder_iris_double.encounter.1"],
victory: ["doubleBattleDialogue:alder_iris_double.victory.1"],
},
iris_alder_double: {
encounter: ["doubleBattleDialogue:iris_alder_double.encounter.1"],
victory: ["doubleBattleDialogue:iris_alder_double.victory.1"],
},
marnie_piers_double: {
encounter: ["doubleBattleDialogue:marnie_piers_double.encounter.1"],
victory: ["doubleBattleDialogue:marnie_piers_double.victory.1"],
},
piers_marnie_double: {
encounter: ["doubleBattleDialogue:piers_marnie_double.encounter.1"],
victory: ["doubleBattleDialogue:piers_marnie_double.victory.1"],
},
};

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,8 +19,8 @@ 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 { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
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 */

View File

@ -7,9 +7,10 @@ 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/data/data-lists";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -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,

View File

@ -4,7 +4,7 @@ import {
setEncounterExp, setEncounterExp,
updatePlayerMoney, updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -12,7 +12,9 @@ import {
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 { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -24,7 +24,7 @@ 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 { 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";
@ -38,7 +38,7 @@ import {
} from "#app/data/mystery-encounters/mystery-encounter-requirements"; } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { import {
AttackTypeBoosterModifier, AttackTypeBoosterModifier,
@ -50,7 +50,7 @@ import {
import i18next from "i18next"; import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay"; import MoveInfoOverlay from "#app/ui/move-info-overlay";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";

View File

@ -11,9 +11,10 @@ import {
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -37,11 +38,10 @@ 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 { 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";
@ -49,6 +49,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { allAbilities } from "#app/data/data-lists";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/clowningAround"; const namespace = "mysteryEncounters/clowningAround";
@ -139,7 +140,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
// Generate random ability for Blacephalon from pool // Generate random ability for Blacephalon from pool
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)]; const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
encounter.setDialogueToken("ability", new Ability(ability, 3).name); encounter.setDialogueToken("ability", allAbilities[ability].name);
encounter.misc = { ability }; encounter.misc = { ability };
// Decide the random types for Blacephalon. They should not be the same. // Decide the random types for Blacephalon. They should not be the same.

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,9 +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/data/data-lists";
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";

View File

@ -3,7 +3,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";

View File

@ -28,7 +28,7 @@ import {
PreserveBerryModifier, PreserveBerryModifier,
} 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/data/data-lists";
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";

View File

@ -2,8 +2,8 @@ import {
leaveEncounterWithoutBattle, leaveEncounterWithoutBattle,
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";

View File

@ -7,8 +7,9 @@ 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 { modifierTypes } from "#app/modifier/modifier-type"; import type { PokemonMove } from "#app/data/moves/pokemon-move";
import { modifierTypes } from "#app/data/data-lists";
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";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -10,7 +10,7 @@ import {
generateModifierType, generateModifierType,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
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";
@ -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";
@ -45,8 +45,8 @@ 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 { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
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";
import { allAbilities } from "#app/data/data-lists";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fieryFallout"; const namespace = "mysteryEncounters/fieryFallout";
@ -246,7 +246,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied // Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
encounter.setDialogueToken("abilityName", new Ability(AbilityId.HEATPROOF, 3).name); encounter.setDialogueToken("abilityName", allAbilities[AbilityId.HEATPROOF].name);
queueEncounterMessage(`${namespace}:option.2.target_burned`); queueEncounterMessage(`${namespace}:option.2.target_burned`);
// Also permanently change the burned Pokemon's ability to Heatproof // Also permanently change the burned Pokemon's ability to Heatproof

View File

@ -9,13 +9,13 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { import {
getPlayerModifierTypeOptions, getPlayerModifierTypeOptions,
ModifierPoolType,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
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";

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,8 +25,8 @@ 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 { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
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";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";

View File

@ -4,14 +4,14 @@ import {
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings"; import { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { import {
getPlayerModifierTypeOptions, getPlayerModifierTypeOptions,
ModifierPoolType,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
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";
@ -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

@ -7,8 +7,8 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate"; import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate";
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate"; import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -16,7 +16,7 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
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 "#enums/modifier-tier";
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";

View File

@ -7,7 +7,7 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
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 { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";

View File

@ -1,6 +1,6 @@
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
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/data/data-lists";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -21,8 +21,9 @@ 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";

View File

@ -23,7 +23,8 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import { getBiomeKey } from "#app/field/arena"; import { getBiomeKey } from "#app/field/arena";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type"; import { getPartyLuckValue } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
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";

View File

@ -26,7 +26,7 @@ import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/data/data-lists";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";

View File

@ -8,7 +8,7 @@ import {
generateModifierType, generateModifierType,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
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/data/data-lists";
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";
@ -17,11 +17,11 @@ 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";

View File

@ -8,7 +8,7 @@ import {
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
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/data/data-lists";
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";
@ -23,12 +23,12 @@ 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 } from "#app/data/abilities/apply-ab-attrs";
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 i18next from "i18next"; import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -221,7 +221,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects // Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
pokemon.resetBattleAndWaveData(); pokemon.resetBattleAndWaveData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
} }
globalScene.phaseManager.unshiftNew("ShowTrainerPhase"); globalScene.phaseManager.unshiftNew("ShowTrainerPhase");

View File

@ -1,4 +1,4 @@
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
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 { import {
@ -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";

View File

@ -8,7 +8,7 @@ import {
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
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/data/data-lists";
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";
@ -21,11 +21,11 @@ import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/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,8 +29,7 @@ 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";
@ -175,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";
@ -25,7 +25,7 @@ import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
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/data/data-lists";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { import {
doPokemonTransformationSequence, doPokemonTransformationSequence,
@ -34,7 +34,7 @@ import {
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { PlayerGender } from "#enums/player-gender"; 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";

View File

@ -2,14 +2,16 @@ 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";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { isNullOrUndefined } from "#app/utils/common"; import { coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -270,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) { constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super(); super();
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay]; this.requiredTimeOfDay = coerceArray(timeOfDay);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -292,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) { constructor(weather: WeatherType | WeatherType[]) {
super(); super();
this.requiredWeather = Array.isArray(weather) ? weather : [weather]; this.requiredWeather = coerceArray(weather);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -358,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
constructor(heldItem: string | string[], minNumberOfItems = 1) { constructor(heldItem: string | string[], minNumberOfItems = 1) {
super(); super();
this.minNumberOfItems = minNumberOfItems; this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -424,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredSpecies = Array.isArray(species) ? species : [species]; this.requiredSpecies = coerceArray(species);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -464,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredNature = Array.isArray(nature) ? nature : [nature]; this.requiredNature = coerceArray(nature);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -502,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted; this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredType = Array.isArray(type) ? type : [type]; this.requiredType = coerceArray(type);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -556,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [moves]; this.requiredMoves = coerceArray(moves);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -607,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove]; this.requiredMoves = coerceArray(learnableMove);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -663,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities]; this.requiredAbilities = coerceArray(abilities);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -708,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect]; this.requiredStatusEffect = coerceArray(statusEffect);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -783,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem]; this.requiredFormChangeItem = coerceArray(formChangeItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -841,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems]; this.requiredEvolutionItem = coerceArray(evolutionItems);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -906,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
this.requireTransferable = requireTransferable; this.requireTransferable = requireTransferable;
} }
@ -970,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes]; this.requiredHeldItemTypes = coerceArray(heldItemTypes);
this.requireTransferable = requireTransferable; this.requireTransferable = requireTransferable;
} }

View File

@ -1,5 +1,5 @@
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters"; import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/constants";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";

View File

@ -1,7 +1,8 @@
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, coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
@ -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";
@ -716,7 +717,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withAnimations( withAnimations(
...encounterAnimations: EncounterAnim[] ...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> { ): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; const animations = coerceArray(encounterAnimations);
return Object.assign(this, { encounterAnimations: animations }); return Object.assign(this, { encounterAnimations: animations });
} }
@ -728,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedGameModes( withDisallowedGameModes(
...disallowedGameModes: GameModes[] ...disallowedGameModes: GameModes[]
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> { ): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes]; const gameModes = coerceArray(disallowedGameModes);
return Object.assign(this, { disallowedGameModes: gameModes }); return Object.assign(this, { disallowedGameModes: gameModes });
} }
@ -740,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedChallenges( withDisallowedChallenges(
...disallowedChallenges: Challenges[] ...disallowedChallenges: Challenges[]
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> { ): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges]; const challenges = coerceArray(disallowedChallenges);
return Object.assign(this, { disallowedChallenges: challenges }); return Object.assign(this, { disallowedChallenges: challenges });
} }

View File

@ -34,42 +34,6 @@ import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encount
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
import { getBiomeName } from "#app/data/balance/biomes"; import { getBiomeName } from "#app/data/balance/biomes";
/**
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
*/
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 3;
/**
* The divisor for determining ME spawns, defines the "maximum" weight required for a spawn
* If spawn_weight === MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, 100% chance to spawn a ME
*/
export const MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT = 256;
/**
* When an ME spawn roll fails, WEIGHT_INCREMENT_ON_SPAWN_MISS is added to future rolls for ME spawn checks.
* These values are cleared whenever the next ME spawns, and spawn weight returns to BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
*/
export const WEIGHT_INCREMENT_ON_SPAWN_MISS = 3;
/**
* Specifies the target average for total ME spawns in a single Classic run.
* Used by anti-variance mechanic to check whether a run is above or below the target on a given wave.
*/
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 12;
/**
* Will increase/decrease the chance of spawning a ME based on the current run's total MEs encountered vs AVERAGE_ENCOUNTERS_PER_RUN_TARGET
* Example:
* AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 17 (expects avg 1 ME every 10 floors)
* ANTI_VARIANCE_WEIGHT_MODIFIER = 15
*
* On wave 20, if 1 ME has been encountered, the difference from expected average is 0 MEs.
* So anti-variance adds 0/256 to the spawn weight check for ME spawn.
*
* On wave 20, if 0 MEs have been encountered, the difference from expected average is 1 ME.
* So anti-variance adds 15/256 to the spawn weight check for ME spawn.
*
* On wave 20, if 2 MEs have been encountered, the difference from expected average is -1 ME.
* So anti-variance adds -15/256 to the spawn weight check for ME spawn.
*/
export const ANTI_VARIANCE_WEIGHT_MODIFIER = 15;
export const EXTREME_ENCOUNTER_BIOMES = [ export const EXTREME_ENCOUNTER_BIOMES = [
BiomeId.SEA, BiomeId.SEA,
BiomeId.SEABED, BiomeId.SEABED,

View File

@ -1,7 +1,7 @@
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 { coerceArray, 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";
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) { constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
super(); super();
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves]; this.requiredMoves = coerceArray(requiredMoves);
this.excludeLevelMoves = options.excludeLevelMoves ?? false; this.excludeLevelMoves = options.excludeLevelMoves ?? false;
this.excludeTmMoves = options.excludeTmMoves ?? false; this.excludeTmMoves = options.excludeTmMoves ?? false;

View File

@ -1,36 +1,37 @@
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";
import { import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants";
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
WEIGHT_INCREMENT_ON_SPAWN_MISS,
} 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,
ModifierPoolType,
ModifierTypeGenerator, ModifierTypeGenerator,
ModifierTypeOption, ModifierTypeOption,
modifierTypes,
regenerateModifierPoolThresholds, regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
import { ModifierPoolType } from "#enums/modifier-pool-type";
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";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt, randomString, randSeedItem, coerceArray } from "#app/utils/common";
import type { BattlerTagType } from "#enums/battler-tag-type"; 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";
@ -448,7 +449,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
* @param moves * @param moves
*/ */
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) { export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
moves = Array.isArray(moves) ? moves : [moves]; moves = coerceArray(moves);
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves)); return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
} }
@ -791,7 +792,7 @@ export function setEncounterRewards(
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue * @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
*/ */
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) { export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
const participantIds = Array.isArray(participantId) ? participantId : [participantId]; const participantIds = coerceArray(participantId);
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds)); globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));

View File

@ -29,7 +29,7 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
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/data/data-lists";
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 { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler";

View File

@ -0,0 +1,97 @@
import { globalScene } from "#app/global-scene";
import type { Phase } from "#app/phase";
import { ActivatePriorityQueuePhase } from "#app/phases/activate-priority-queue-phase";
import type { PostSummonPhase } from "#app/phases/post-summon-phase";
import { PostSummonActivateAbilityPhase } from "#app/phases/post-summon-activate-ability-phase";
import { Stat } from "#enums/stat";
import { BooleanHolder } from "#app/utils/common";
import { TrickRoomTag } from "#app/data/arena-tag";
import { DynamicPhaseType } from "#enums/dynamic-phase-type";
/**
* Stores a list of {@linkcode Phase}s
*
* Dynamically updates ordering to always pop the highest "priority", based on implementation of {@linkcode reorder}
*/
export abstract class PhasePriorityQueue {
protected abstract queue: Phase[];
/**
* Sorts the elements in the queue
*/
public abstract reorder(): void;
/**
* Calls {@linkcode reorder} and shifts the queue
* @returns The front element of the queue after sorting
*/
public pop(): Phase | undefined {
this.reorder();
return this.queue.shift();
}
/**
* Adds a phase to the queue
* @param phase The phase to add
*/
public push(phase: Phase): void {
this.queue.push(phase);
}
/**
* Removes all phases from the queue
*/
public clear(): void {
this.queue.splice(0, this.queue.length);
}
}
/**
* Priority Queue for {@linkcode PostSummonPhase} and {@linkcode PostSummonActivateAbilityPhase}
*
* Orders phases first by ability priority, then by the {@linkcode Pokemon}'s effective speed
*/
export class PostSummonPhasePriorityQueue extends PhasePriorityQueue {
protected override queue: PostSummonPhase[] = [];
public override reorder(): void {
this.queue.sort((phaseA: PostSummonPhase, phaseB: PostSummonPhase) => {
if (phaseA.getPriority() === phaseB.getPriority()) {
return (
(phaseB.getPokemon().getEffectiveStat(Stat.SPD) - phaseA.getPokemon().getEffectiveStat(Stat.SPD)) *
(isTrickRoom() ? -1 : 1)
);
}
return phaseB.getPriority() - phaseA.getPriority();
});
}
public override push(phase: PostSummonPhase): void {
super.push(phase);
this.queueAbilityPhase(phase);
}
/**
* Queues all necessary {@linkcode PostSummonActivateAbilityPhase}s for each pushed {@linkcode PostSummonPhase}
* @param phase The {@linkcode PostSummonPhase} that was pushed onto the queue
*/
private queueAbilityPhase(phase: PostSummonPhase): void {
const phasePokemon = phase.getPokemon();
phasePokemon.getAbilityPriorities().forEach((priority, idx) => {
this.queue.push(new PostSummonActivateAbilityPhase(phasePokemon.getBattlerIndex(), priority, !!idx));
globalScene.phaseManager.appendToPhase(
new ActivatePriorityQueuePhase(DynamicPhaseType.POST_SUMMON),
"ActivatePriorityQueuePhase",
(p: ActivatePriorityQueuePhase) => p.getType() === DynamicPhaseType.POST_SUMMON,
);
});
}
}
function isTrickRoom(): boolean {
const speedReversed = new BooleanHolder(false);
globalScene.arena.applyTags(TrickRoomTag, false, speedReversed);
return speedReversed.value;
}

View File

@ -1,5 +1,4 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
import { NumberHolder } from "#app/utils/common"; import { NumberHolder } from "#app/utils/common";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import i18next from "i18next"; import i18next from "i18next";
@ -94,7 +93,7 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
} }
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
const catchingCharmMultiplier = new NumberHolder(1); const catchingCharmMultiplier = new NumberHolder(1);
globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier);
const dexMultiplier = const dexMultiplier =
globalScene.gameMode.isDaily || dexCount > 800 globalScene.gameMode.isDaily || dexCount > 800
? 2.5 ? 2.5

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,345 @@
import i18next from "i18next";
import { coerceArray, 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();
this.statusEffects = coerceArray(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

@ -7,7 +7,8 @@ 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 { DexAttr } from "#enums/dex-attr";
import { import {
isNullOrUndefined, isNullOrUndefined,
capitalizeString, capitalizeString,

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,12 +1,19 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "../data-lists";
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,
coerceArray,
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 "../double-battle-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";
@ -37,7 +44,7 @@ import { timedEventManager } from "#app/global-event-manager";
// Type imports // Type imports
import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; import type { PokemonSpeciesFilter } from "#app/data/pokemon-species";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import type { ModifierTypeFunc } from "#app/@types/modifier-types";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import type { EvilTeam } from "./evil-admin-trainer-pools"; import type { EvilTeam } from "./evil-admin-trainer-pools";
import type { import type {
@ -554,10 +561,7 @@ export class TrainerConfig {
this.speciesPools = evilAdminTrainerPools[poolName]; this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -620,10 +624,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
} }
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
if (!isNullOrUndefined(specialtyType)) { if (!isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType)); this.setSpeciesFilter(p => p.isOfType(specialtyType));
@ -668,12 +669,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool. // Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
}); });
// If specialty type is provided, set species filter and specialty type. // If specialty type is provided, set species filter and specialty type.
@ -729,12 +726,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool. // Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
}); });
// Set species filter and specialty type if provided, otherwise filter by base total. // Set species filter and specialty type if provided, otherwise filter by base total.

View File

@ -4,14 +4,13 @@ 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 { TerrainType, getTerrainName } from "./terrain"; import { TerrainType, getTerrainName } from "./terrain";
import i18next from "i18next"; import i18next from "i18next";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Arena } from "#app/field/arena"; import type { Arena } from "#app/field/arena";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";
import type { SuppressWeatherEffectAbAttr } from "./abilities/ability";
export class Weather { export class Weather {
public weatherType: WeatherType; public weatherType: WeatherType;
@ -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;
@ -109,10 +108,10 @@ export class Weather {
for (const pokemon of field) { for (const pokemon of field) {
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon
.getAbility() .getAbility()
.getAttrs(SuppressWeatherEffectAbAttr)[0]; .getAttrs("SuppressWeatherEffectAbAttr")[0];
if (!suppressWeatherEffectAbAttr) { if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() suppressWeatherEffectAbAttr = pokemon.hasPassive()
? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] ? pokemon.getPassiveAbility().getAttrs("SuppressWeatherEffectAbAttr")[0]
: null; : null;
} }
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) { if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {

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
}

View File

@ -0,0 +1,13 @@
// biome-ignore lint/correctness/noUnusedImports: Used in tsdoc
import type ConfirmUiHandler from "#app/ui/confirm-ui-handler";
/**
* Used by {@linkcode ConfirmUiHandler} to determine whether the cursor should start on Yes or No
*/
export const ConfirmUiMode = Object.freeze({
/** Start cursor on Yes */
DEFAULT_YES: 1,
/** Start cursor on No */
DEFAULT_NO: 2
});
export type ConfirmUiMode = typeof ConfirmUiMode[keyof typeof ConfirmUiMode];

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,6 @@
/**
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}
*/
export enum DynamicPhaseType {
POST_SUMMON
}

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,7 @@
export enum ModifierPoolType {
PLAYER,
WILD,
TRAINER,
ENEMY_BUFF,
DAILY_STARTER
}

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
}

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