Merge branch 'beta' into rerollcleanup

This commit is contained in:
NightKev 2025-06-16 05:14:22 -07:00 committed by GitHub
commit 1511d4b3ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
519 changed files with 8723 additions and 6864 deletions

View File

@ -4,7 +4,7 @@ module.exports = {
{ {
name: "only-type-imports", name: "only-type-imports",
severity: "error", severity: "error",
comment: "Files in enums and @types may only use type imports.", comment: "Files in 'enums/' and '@types/' must only use type imports.",
from: { from: {
path: ["(^|/)src/@types", "(^|/)src/enums"], path: ["(^|/)src/@types", "(^|/)src/enums"],
}, },
@ -14,7 +14,7 @@ module.exports = {
}, },
{ {
name: "no-circular-at-runtime", name: "no-circular-at-runtime",
severity: "warn", severity: "error",
comment: comment:
"This dependency is part of a circular relationship. You might want to revise " + "This dependency is part of a circular relationship. You might want to revise " +
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
@ -34,7 +34,7 @@ module.exports = {
"add an exception for it in your dependency-cruiser configuration. By default " + "add an exception for it in your dependency-cruiser configuration. By default " +
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.", "files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: "warn", severity: "error",
from: { from: {
orphan: true, orphan: true,
pathNot: [ pathNot: [
@ -42,8 +42,7 @@ module.exports = {
"[.]d[.]ts$", // TypeScript declaration files "[.]d[.]ts$", // TypeScript declaration files
"(^|/)tsconfig[.]json$", // TypeScript config "(^|/)tsconfig[.]json$", // TypeScript config
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs "(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
// anything in src/@types "(^|/)test/.+[.]setup[.]ts", // Vitest setup files
"(^|/)src/@types/",
], ],
}, },
to: {}, to: {},
@ -53,7 +52,7 @@ module.exports = {
comment: comment:
"A module depends on a node core module that has been deprecated. Find an alternative - these are " + "A module depends on a node core module that has been deprecated. Find an alternative - these are " +
"bound to exist - node doesn't deprecate lightly.", "bound to exist - node doesn't deprecate lightly.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
dependencyTypes: ["core"], dependencyTypes: ["core"],
@ -86,7 +85,7 @@ module.exports = {
comment: comment:
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
"version of that module, or find an alternative. Deprecated modules are a security risk.", "version of that module, or find an alternative. Deprecated modules are a security risk.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
dependencyTypes: ["deprecated"], dependencyTypes: ["deprecated"],
@ -122,7 +121,7 @@ module.exports = {
"Likely this module depends on an external ('npm') package that occurs more than once " + "Likely this module depends on an external ('npm') package that occurs more than once " +
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
"maintenance problems later on.", "maintenance problems later on.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
moreThanOneDependencyType: true, moreThanOneDependencyType: true,
@ -133,7 +132,7 @@ module.exports = {
}, },
}, },
/* rules you might want to tweak for your specific situation: */ // rules you might want to tweak for your specific situation:
{ {
name: "not-to-spec", name: "not-to-spec",
@ -188,7 +187,7 @@ module.exports = {
"in your package.json. This makes sense if your package is e.g. a plugin, but in " + "in your package.json. This makes sense if your package is e.g. a plugin, but in " +
"other cases - maybe not so much. If the use of a peer dependency is intentional " + "other cases - maybe not so much. If the use of a peer dependency is intentional " +
"add an exception to your dependency-cruiser configuration.", "add an exception to your dependency-cruiser configuration.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
dependencyTypes: ["npm-peer"], dependencyTypes: ["npm-peer"],
@ -196,6 +195,7 @@ module.exports = {
}, },
], ],
options: { options: {
exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"],
/* Which modules not to follow further when encountered */ /* Which modules not to follow further when encountered */
doNotFollow: { doNotFollow: {
/* path: an array of regular expressions in strings to match against */ /* path: an array of regular expressions in strings to match against */
@ -235,7 +235,7 @@ module.exports = {
true: also detect dependencies that only exist before typescript-to-javascript compilation true: also detect dependencies that only exist before typescript-to-javascript compilation
"specify": for each dependency identify whether it only exists before compilation or also after "specify": for each dependency identify whether it only exists before compilation or also after
*/ */
// tsPreCompilationDeps: false, tsPreCompilationDeps: true,
/* list of extensions to scan that aren't javascript or compile-to-javascript. /* list of extensions to scan that aren't javascript or compile-to-javascript.
Empty by default. Only put extensions in here that you want to take into Empty by default. Only put extensions in here that you want to take into

View File

@ -14,13 +14,16 @@ Make sure the title includes categorization (choose the one that best fits):
- [Balance]: If the PR is related to game balance - [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges - [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code - [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments) - [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc - [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR - [Misc]: If no other category fits the PR
--> -->
<!-- <!--
Make sure that this PR is not overlapping with someone else's work Make sure that this PR is not overlapping with someone else's work
Please try to keep the PR self-contained (and small) Please try to keep the PR self-contained (and small!)
--> -->
## What are the changes the user will see? ## What are the changes the user will see?
@ -66,7 +69,7 @@ Do the reviewers need to do something special in order to test your changes?
- [ ] Have I provided a clear explanation of the changes? - [ ] Have I provided a clear explanation of the changes?
- [ ] Have I tested the changes manually? - [ ] Have I tested the changes manually?
- [ ] Are all unit tests still passing? (`npm run test:silent`) - [ ] Are all unit tests still passing? (`npm run test:silent`)
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes? - [ ] Have I created new automated tests (`npm run test:create`) or updated existing tests related to the PR's changes?
- [ ] Have I provided screenshots/videos of the changes (if applicable)? - [ ] Have I provided screenshots/videos of the changes (if applicable)?
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? - [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?

42
.github/workflows/linting.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Linting
on:
push:
branches:
- main
- beta
pull_request:
branches:
- main
- beta
merge_group:
types: [checks_requested]
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies
run: npm ci
- name: Run ESLint
run: npm run eslint-ci
- name: Lint with Biome
run: npm run biome-ci
- name: Check dependencies with depcruise
run: npm run depcruise

View File

@ -1,41 +0,0 @@
name: Biome Code Quality
on:
# Trigger the workflow on push or pull request,
# but only for the main branch
push:
branches:
- main # Trigger on push events to the main branch
- beta # Trigger on push events to the beta branch
pull_request:
branches:
- main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch
merge_group:
types: [checks_requested]
jobs:
run-linters: # Define a job named "run-linters"
name: Run linters # Human-readable name for the job
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
steps:
- name: Check out Git repository # Step to check out the repository
uses: actions/checkout@v4 # Use the checkout action version 4
with:
submodules: 'recursive'
- name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies
- name: eslint # Step to run linters
run: npm run eslint-ci
- name: Lint with Biome # Step to run linters
run: npm run biome-ci

View File

@ -28,7 +28,6 @@
".vscode/*", ".vscode/*",
"*.css", // TODO? "*.css", // TODO?
"*.html", // TODO? "*.html", // TODO?
"src/overrides.ts",
// TODO: these files are too big and complex, ignore them until their respective refactors // TODO: these files are too big and complex, ignore them until their respective refactors
"src/data/moves/move.ts", "src/data/moves/move.ts",
@ -47,8 +46,8 @@
"correctness": { "correctness": {
"noUndeclaredVariables": "off", "noUndeclaredVariables": "off",
"noUnusedVariables": "error", "noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error "noSwitchDeclarations": "error",
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error "noVoidTypeReturn": "error",
"noUnusedImports": "error" "noUnusedImports": "error"
}, },
"style": { "style": {
@ -85,7 +84,7 @@
"useLiteralKeys": "off", "useLiteralKeys": "off",
"noForEach": "off", // Foreach vs for of is not that simple. "noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit "noUselessSwitchCase": "off", // Explicit > Implicit
"noUselessConstructor": "warn", // TODO: Refactor and make this an error "noUselessConstructor": "error",
"noBannedTypes": "warn" // TODO: Refactor and make this an error "noBannedTypes": "warn" // TODO: Refactor and make this an error
}, },
"nursery": { "nursery": {
@ -120,6 +119,28 @@
} }
} }
} }
},
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes)
{
"include": ["src/overrides.ts", "src/enums/*"],
"linter": {
"rules": {
"correctness": {
"noUnusedImports": "off"
}
}
}
},
{
"include": ["src/overrides.ts"],
"linter": {
"rules": {
"style": {
"useImportType": "off"
}
}
}
} }
] ]
} }

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,15 +13,15 @@
"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 .",
"biome": "biome check --write --changed --no-errors-on-unmatched", "biome": "biome check --write --changed --no-errors-on-unmatched",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"docs": "typedoc", "docs": "typedoc",
"depcruise": "depcruise src", "depcruise": "depcruise src test",
"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,20 @@ 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 { import { MoveUseMode } from "#enums/move-use-mode";
BOTH,
PLAYER,
ENEMY,
}
export abstract class ArenaTag { export abstract class ArenaTag {
constructor( constructor(
@ -148,7 +137,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 +202,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 +758,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;
} }
@ -887,13 +876,13 @@ export class DelayedAttackTag extends ArenaTag {
const ret = super.lapse(arena); const ret = super.lapse(arena);
if (!ret) { if (!ret) {
// TODO: This should not add to move history (for Spite)
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"MoveEffectPhase", "MoveEffectPhase",
this.sourceId!, this.sourceId!,
[this.targetIndex], [this.targetIndex],
allMoves[this.sourceMove!], allMoves[this.sourceMove!],
false, MoveUseMode.FOLLOW_UP,
true,
); // TODO: are those bangs correct? ); // TODO: are those bangs correct?
} }
@ -957,7 +946,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 +1003,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 +1437,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 +1450,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";

View File

@ -1,111 +1,22 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move"; import { allMoves } from "#app/data/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 "#app/field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; import {
import type { BattlerIndex } from "../battle"; type nil,
getFrameMs,
getEnumKeys,
getEnumValues,
animationFileName,
coerceArray,
isNullOrUndefined,
} from "#app/utils/common";
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 { 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 +442,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 +453,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 +480,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 +526,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 +613,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 +777,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?
@ -941,7 +851,7 @@ export abstract class BattleAnim {
return; return;
} }
const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(SubstituteTag) : null; const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(BattlerTagType.SUBSTITUTE) : null;
const userSprite = user.getSprite(); const userSprite = user.getSprite();
const targetSprite = targetSubstitute?.sprite ?? target.getSprite(); const targetSprite = targetSubstitute?.sprite ?? target.getSprite();

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,20 +31,15 @@ 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 { MoveUseMode } from "#enums/move-use-mode";
import { invalidEncoreMoves } from "./moves/invalid-moves";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
export enum BattlerTagLapseType { /**
FAINT, * A {@linkcode BattlerTag} represents a semi-persistent effect that can be attached to a {@linkcode Pokemon}.
MOVE, * Tags can trigger various effects throughout a turn, and are cleared on switching out
PRE_MOVE, * or through their respective {@linkcode BattlerTag.lapse | lapse} methods.
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;
public lapseTypes: BattlerTagLapseType[]; public lapseTypes: BattlerTagLapseType[];
@ -72,7 +57,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;
@ -91,7 +76,7 @@ export class BattlerTag {
/** /**
* Tick down this {@linkcode BattlerTag}'s duration. * Tick down this {@linkcode BattlerTag}'s duration.
* @returns `true` if the tag should be kept (`turnCount` > 0`) * @returns `true` if the tag should be kept (`turnCount > 0`)
*/ */
lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
// TODO: Maybe flip this (return `true` if tag needs removal) // TODO: Maybe flip this (return `true` if tag needs removal)
@ -147,16 +132,6 @@ export interface TerrainBattlerTag {
* Players and enemies should not be allowed to select restricted moves. * Players and enemies should not be allowed to select restricted moves.
*/ */
export abstract class MoveRestrictionBattlerTag extends BattlerTag { export abstract class MoveRestrictionBattlerTag extends BattlerTag {
constructor(
tagType: BattlerTagType,
lapseType: BattlerTagLapseType | BattlerTagLapseType[],
turnCount: number,
sourceMove?: MoveId,
sourceId?: number,
) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
}
/** @override */ /** @override */
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) { if (lapseType === BattlerTagLapseType.PRE_MOVE) {
@ -299,17 +274,18 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
/** /**
* @override * @override
* *
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@linkcode moveId} and shows a message. * Attempt to disable the target's last move by setting this tag's {@linkcode moveId}
* Otherwise the move ID will not get assigned and this tag will get removed next turn. * and showing a message.
*/ */
override onAdd(pokemon: Pokemon): void { override onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); // Disable fails against struggle or an empty move history
// TODO: Confirm if this is redundant given Disable/Cursed Body's disable conditions
const move = pokemon.getLastXMoves(-1).find(m => !m.virtual); const move = pokemon.getLastNonVirtualMove();
if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE || move.move === MoveId.NONE) { if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE) {
return; return;
} }
super.onAdd(pokemon);
this.moveId = move.move; this.moveId = move.move;
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
@ -359,7 +335,6 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
/** /**
* Tag used by Gorilla Tactics to restrict the user to using only one move. * Tag used by Gorilla Tactics to restrict the user to using only one move.
* @extends MoveRestrictionBattlerTag
*/ */
export class GorillaTacticsTag extends MoveRestrictionBattlerTag { export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
private moveId = MoveId.NONE; private moveId = MoveId.NONE;
@ -368,34 +343,30 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0); super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
} }
/** @override */
override isMoveRestricted(move: MoveId): boolean { override isMoveRestricted(move: MoveId): boolean {
return move !== this.moveId; return move !== this.moveId;
} }
/** /**
* @override * Ensures that move history exists on {@linkcode Pokemon} and has a valid move to lock into.
* @param {Pokemon} pokemon the {@linkcode Pokemon} to check if the tag can be added * @param pokemon - The {@linkcode Pokemon} to add the tag to
* @returns `true` if the pokemon has a valid move and no existing {@linkcode GorillaTacticsTag}; `false` otherwise * @returns `true` if the tag can be added
*/ */
override canAdd(pokemon: Pokemon): boolean { override canAdd(pokemon: Pokemon): boolean {
return this.getLastValidMove(pokemon) !== undefined && !pokemon.getTag(GorillaTacticsTag); // Choice items ignore struggle, so Gorilla Tactics should too
const lastSelectedMove = pokemon.getLastNonVirtualMove();
return !isNullOrUndefined(lastSelectedMove) && lastSelectedMove.move !== MoveId.STRUGGLE;
} }
/** /**
* Ensures that move history exists on {@linkcode Pokemon} and has a valid move. * Sets this tag's {@linkcode moveId} and increases the user's Attack by 50%.
* If so, sets the {@linkcode moveId} and increases the user's Attack by 50%. * @param pokemon - The {@linkcode Pokemon} to add the tag to
* @override
* @param {Pokemon} pokemon the {@linkcode Pokemon} to add the tag to
*/ */
override onAdd(pokemon: Pokemon): void { override onAdd(pokemon: Pokemon): void {
const lastValidMove = this.getLastValidMove(pokemon); super.onAdd(pokemon);
if (!lastValidMove) { // Bang is justified as tag is not added if prior move doesn't exist
return; this.moveId = pokemon.getLastNonVirtualMove()!.move;
}
this.moveId = lastValidMove;
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
} }
@ -410,29 +381,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
} }
/** /**
* * Return the text displayed when a move is restricted.
* @override * @param pokemon - The {@linkcode Pokemon} with this tag.
* @param {Pokemon} pokemon n/a * @returns A string containing the text to display when the move is denied
* @param {MoveId} _move {@linkcode MoveId} ID of the move being denied
* @returns {string} text to display when the move is denied
*/ */
override selectionDeniedText(pokemon: Pokemon, _move: MoveId): string { override selectionDeniedText(pokemon: Pokemon): string {
return i18next.t("battle:canOnlyUseMove", { return i18next.t("battle:canOnlyUseMove", {
moveName: allMoves[this.moveId].name, moveName: allMoves[this.moveId].name,
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
}); });
} }
/**
* Gets the last valid move from the pokemon's move history.
* @param {Pokemon} pokemon {@linkcode Pokemon} to get the last valid move from
* @returns {MoveId | undefined} the last valid move from the pokemon's move history
*/
getLastValidMove(pokemon: Pokemon): MoveId | undefined {
const move = pokemon.getLastXMoves().find(m => m.move !== MoveId.NONE && m.move !== MoveId.STRUGGLE && !m.virtual);
return move?.move;
}
} }
/** /**
@ -446,8 +404,8 @@ export class RechargingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
// Queue a placeholder move for the Pokemon to "use" next turn // Queue a placeholder move for the Pokemon to "use" next turn.
pokemon.getMoveQueue().push({ move: MoveId.NONE, targets: [] }); pokemon.pushMoveQueue({ move: MoveId.NONE, targets: [], useMode: MoveUseMode.NORMAL });
} }
/** Cancels the source's move this turn and queues a "__ must recharge!" message */ /** Cancels the source's move this turn and queues a "__ must recharge!" message */
@ -663,7 +621,7 @@ export class FlinchedTag extends BattlerTag {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
applyAbAttrs(FlinchEffectAbAttr, pokemon, null); applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
return true; return true;
} }
@ -692,6 +650,7 @@ export class InterruptedTag extends BattlerTag {
move: MoveId.NONE, move: MoveId.NONE,
result: MoveResult.OTHER, result: MoveResult.OTHER,
targets: [], targets: [],
useMode: MoveUseMode.NORMAL,
}); });
} }
@ -957,7 +916,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 +927,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(),
@ -1017,43 +976,46 @@ export class PowderTag extends BattlerTag {
} }
/** /**
* Applies Powder's effects before the tag owner uses a Fire-type move. * Applies Powder's effects before the tag owner uses a Fire-type move, damaging and canceling its action.
* Also causes the tag to expire at the end of turn. * Lasts until the end of the turn.
* @param pokemon {@linkcode Pokemon} the owner of this tag * @param pokemon - The {@linkcode Pokemon} with this tag.
* @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out * @param lapseType - The {@linkcode BattlerTagLapseType} dictating how this tag is being activated
* @returns `true` if the tag should not expire after this lapse; `false` otherwise. * @returns `true` if the tag should remain active.
*/ */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
const movePhase = globalScene.phaseManager.getCurrentPhase(); const movePhase = globalScene.phaseManager.getCurrentPhase();
if (movePhase?.is("MovePhase")) { if (lapseType !== BattlerTagLapseType.PRE_MOVE || !movePhase?.is("MovePhase")) {
return false;
}
const move = movePhase.move.getMove(); const move = movePhase.move.getMove();
const weather = globalScene.arena.weather; const weather = globalScene.arena.weather;
if ( if (
pokemon.getMoveType(move) === PokemonType.FIRE && pokemon.getMoveType(move) !== PokemonType.FIRE ||
!(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) (weather?.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) // Heavy rain takes priority over powder
) { ) {
movePhase.fail(); return true;
}
// Disable the target's fire type move and damage it (subject to Magic Guard)
movePhase.showMoveText(); movePhase.showMoveText();
movePhase.fail();
const idx = pokemon.getBattlerIndex(); const idx = pokemon.getBattlerIndex();
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 });
} }
// "When the flame touched the powder\non the Pokémon, it exploded!" // "When the flame touched the powder\non the Pokémon, it exploded!"
globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
}
}
return true; return true;
} }
return super.lapse(pokemon, lapseType);
}
} }
export class NightmareTag extends BattlerTag { export class NightmareTag extends BattlerTag {
@ -1094,7 +1056,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 });
@ -1147,34 +1109,22 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
} }
canAdd(pokemon: Pokemon): boolean { canAdd(pokemon: Pokemon): boolean {
const lastMoves = pokemon.getLastXMoves(1); const lastMove = pokemon.getLastNonVirtualMove();
if (!lastMoves.length) { if (!lastMove) {
return false; return false;
} }
const repeatableMove = lastMoves[0]; if (invalidEncoreMoves.has(lastMove.move)) {
if (!repeatableMove.move || repeatableMove.virtual) {
return false; return false;
} }
switch (repeatableMove.move) { this.moveId = lastMove.move;
case MoveId.MIMIC:
case MoveId.MIRROR_MOVE:
case MoveId.TRANSFORM:
case MoveId.STRUGGLE:
case MoveId.SKETCH:
case MoveId.SLEEP_TALK:
case MoveId.ENCORE:
return false;
}
this.moveId = repeatableMove.move;
return true; return true;
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
// TODO: shouldn't this be `onAdd`?
super.onRemove(pokemon); super.onRemove(pokemon);
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
@ -1190,7 +1140,13 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
const lastMove = pokemon.getLastXMoves(1)[0]; const lastMove = pokemon.getLastXMoves(1)[0];
globalScene.phaseManager.tryReplacePhase( globalScene.phaseManager.tryReplacePhase(
m => m.is("MovePhase") && m.pokemon === pokemon, m => m.is("MovePhase") && m.pokemon === pokemon,
globalScene.phaseManager.create("MovePhase", pokemon, lastMove.targets ?? [], movesetMove), globalScene.phaseManager.create(
"MovePhase",
pokemon,
lastMove.targets ?? [],
movesetMove,
MoveUseMode.NORMAL,
),
); );
} }
} }
@ -1453,7 +1409,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 });
@ -1492,16 +1448,6 @@ export class WrapTag extends DamagingTrapTag {
} }
export abstract class VortexTrapTag extends DamagingTrapTag { export abstract class VortexTrapTag extends DamagingTrapTag {
constructor(
tagType: BattlerTagType,
commonAnim: CommonAnim,
turnCount: number,
sourceMove: MoveId,
sourceId: number,
) {
super(tagType, commonAnim, turnCount, sourceMove, sourceId);
}
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battlerTags:vortexOnTrap", { return i18next.t("battlerTags:vortexOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
@ -1696,7 +1642,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,
@ -1926,13 +1872,19 @@ export class TruantTag extends AbilityBattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (!pokemon.hasAbility(AbilityId.TRUANT)) { if (!pokemon.hasAbility(AbilityId.TRUANT)) {
// remove tag if mon lacks ability
return super.lapse(pokemon, lapseType); return super.lapse(pokemon, lapseType);
} }
const lastMove = pokemon.getLastXMoves()[0];
if (!lastMove) {
// Don't interrupt move if last move was `Moves.NONE` OR no prior move was found
return true;
}
// Interrupt move usage in favor of slacking off
const passive = pokemon.getAbility().id !== AbilityId.TRUANT; const passive = pokemon.getAbility().id !== AbilityId.TRUANT;
const lastMove = pokemon.getLastXMoves().find(() => true);
if (lastMove && lastMove.move !== MoveId.NONE) {
(globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel(); (globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel();
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
@ -1942,7 +1894,6 @@ export class TruantTag extends AbilityBattlerTag {
}), }),
); );
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
}
return true; return true;
} }
@ -2292,7 +2243,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 +2297,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 +2632,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 +2755,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 +3022,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 +3092,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,6 +1,6 @@
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
/** Set of moves that cannot be called by {@linkcode MoveId.METRONOME Metronome} */ /** Set of moves that cannot be called by {@linkcode MoveId.METRONOME | Metronome}. */
export const invalidMetronomeMoves: ReadonlySet<MoveId> = new Set([ export const invalidMetronomeMoves: ReadonlySet<MoveId> = new Set([
MoveId.AFTER_YOU, MoveId.AFTER_YOU,
MoveId.ASSIST, MoveId.ASSIST,
@ -255,3 +255,28 @@ export const noAbilityTypeOverrideMoves: ReadonlySet<MoveId> = new Set([
MoveId.TECHNO_BLAST, MoveId.TECHNO_BLAST,
MoveId.HIDDEN_POWER, MoveId.HIDDEN_POWER,
]); ]);
/** Set of all moves that cannot be copied by {@linkcode Moves.SKETCH}. */
export const invalidSketchMoves: ReadonlySet<MoveId> = new Set([
MoveId.NONE,
MoveId.CHATTER,
MoveId.MIRROR_MOVE,
MoveId.SLEEP_TALK,
MoveId.STRUGGLE,
MoveId.SKETCH,
MoveId.REVIVAL_BLESSING,
MoveId.TERA_STARSTORM,
MoveId.BREAKNECK_BLITZ__PHYSICAL,
MoveId.BREAKNECK_BLITZ__SPECIAL,
]);
/** Set of all moves that cannot be locked into by {@linkcode Moves.ENCORE}. */
export const invalidEncoreMoves: ReadonlySet<MoveId> = new Set([
MoveId.MIMIC,
MoveId.MIRROR_MOVE,
MoveId.TRANSFORM,
MoveId.STRUGGLE,
MoveId.SKETCH,
MoveId.SLEEP_TALK,
MoveId.ENCORE,
]);

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,92 @@
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;
/**
* 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, maxPpOverride?: number) {
this.moveId = moveId;
this.ppUsed = ppUsed;
this.ppUp = ppUp;
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 {
// TODO: Add Sky Drop's 1 turn stall
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.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,
@ -37,6 +38,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import i18next from "i18next"; import i18next from "i18next";
import { MoveUseMode } from "#enums/move-use-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/absoluteAvarice"; const namespace = "mysteryEncounters/absoluteAvarice";
@ -306,7 +308,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY], targets: [BattlerIndex.ENEMY],
move: new PokemonMove(MoveId.STUFF_CHEEKS), move: new PokemonMove(MoveId.STUFF_CHEEKS),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}); });
await transitionMysteryEncounterIntroVisuals(true, true, 500); await transitionMysteryEncounterIntroVisuals(true, true, 500);

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,9 @@ 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 { 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 +37,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 +48,8 @@ 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 { MoveUseMode } from "#enums/move-use-mode";
import { allAbilities, modifierTypes } 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.
@ -209,19 +210,19 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY_2], targets: [BattlerIndex.ENEMY_2],
move: new PokemonMove(MoveId.ROLE_PLAY), move: new PokemonMove(MoveId.ROLE_PLAY),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY_2, sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.TAUNT), move: new PokemonMove(MoveId.TAUNT),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY_2, sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2], targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(MoveId.TAUNT), move: new PokemonMove(MoveId.TAUNT),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
); );

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";
@ -39,6 +40,7 @@ import { PokeballType } from "#enums/pokeball";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import i18next from "i18next"; import i18next from "i18next";
import { MoveUseMode } from "#enums/move-use-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/dancingLessons"; const namespace = "mysteryEncounters/dancingLessons";
@ -213,7 +215,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.REVELATION_DANCE), move: new PokemonMove(MoveId.REVELATION_DANCE),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}); });
await hideOricorioPokemon(); await hideOricorioPokemon();

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,6 @@ 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 { 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 +23,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 +44,9 @@ 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 { MoveUseMode } from "#enums/move-use-mode";
import { allAbilities, modifierTypes } 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";
@ -201,13 +201,13 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.FIRE_SPIN), move: new PokemonMove(MoveId.FIRE_SPIN),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY_2, sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2], targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(MoveId.FIRE_SPIN), move: new PokemonMove(MoveId.FIRE_SPIN),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
); );
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
@ -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,10 @@ 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, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
getPlayerModifierTypeOptions, import { ModifierPoolType } from "#enums/modifier-pool-type";
ModifierPoolType,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-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,11 @@ 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, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
getPlayerModifierTypeOptions, import { ModifierPoolType } from "#enums/modifier-pool-type";
ModifierPoolType,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-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 +30,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";
@ -30,6 +31,7 @@ import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { MoveUseMode } from "#enums/move-use-mode";
/** i18n namespace for the encounter */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounters/slumberingSnorlax"; const namespace = "mysteryEncounters/slumberingSnorlax";
@ -136,7 +138,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.SNORE), move: new PokemonMove(MoveId.SNORE),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}); });
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
}, },

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,17 +17,18 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { MoveUseMode } from "#enums/move-use-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theStrongStuff"; const namespace = "mysteryEncounters/theStrongStuff";
@ -214,13 +215,13 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.GASTRO_ACID), move: new PokemonMove(MoveId.GASTRO_ACID),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.STEALTH_ROCK), move: new PokemonMove(MoveId.STEALTH_ROCK),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
); );

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,13 +21,14 @@ 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";
import { MoveUseMode } from "#enums/move-use-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/trashToTreasure"; const namespace = "mysteryEncounters/trashToTreasure";
@ -207,13 +208,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER], targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.TOXIC), move: new PokemonMove(MoveId.TOXIC),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
{ {
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY], targets: [BattlerIndex.ENEMY],
move: new PokemonMove(MoveId.STOCKPILE), move: new PokemonMove(MoveId.STOCKPILE),
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}, },
); );
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);

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,14 +29,14 @@ import {
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { SelfStatusMove } from "#app/data/moves/move";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { MoveUseMode } from "#enums/move-use-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/uncommonBreed"; const namespace = "mysteryEncounters/uncommonBreed";
@ -175,13 +175,13 @@ 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,
targets: [target], targets: [target],
move: pokemonMove, move: pokemonMove,
ignorePp: true, useMode: MoveUseMode.IGNORE_PP,
}); });
} }

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,22 +21,22 @@ 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";
import type { MoveUseMode } from "#enums/move-use-mode";
export interface EncounterStartOfBattleEffect { export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon; sourcePokemon?: Pokemon;
sourceBattlerIndex?: BattlerIndex; sourceBattlerIndex?: BattlerIndex;
targets: BattlerIndex[]; targets: BattlerIndex[];
move: PokemonMove; move: PokemonMove;
ignorePp: boolean; useMode: MoveUseMode; // TODO: This should always be ignore PP...
followUp?: boolean;
} }
const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2; const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2;
@ -253,7 +254,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
*/ */
selectedOption?: MysteryEncounterOption; selectedOption?: MysteryEncounterOption;
/** /**
* Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases * Array containing data pertaining to free moves used at the start of a battle mystery envounter.
*/ */
startOfBattleEffects: EncounterStartOfBattleEffect[] = []; startOfBattleEffects: EncounterStartOfBattleEffect[] = [];
/** /**
@ -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,36 @@
import type Battle from "#app/battle"; import type Battle from "#app/battle";
import { BattlerIndex } from "#app/battle";
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 +448,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 +791,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));
@ -973,33 +973,8 @@ export function handleMysteryEncounterBattleStartEffects() {
) { ) {
const effects = encounter.startOfBattleEffects; const effects = encounter.startOfBattleEffects;
effects.forEach(effect => { effects.forEach(effect => {
let source: EnemyPokemon | Pokemon; const source = effect.sourcePokemon ?? globalScene.getField()[effect.sourceBattlerIndex ?? 0];
if (effect.sourcePokemon) { globalScene.phaseManager.pushNew("MovePhase", source, effect.targets, effect.move, effect.useMode);
source = effect.sourcePokemon;
} else if (!isNullOrUndefined(effect.sourceBattlerIndex)) {
if (effect.sourceBattlerIndex === BattlerIndex.ATTACKER) {
source = globalScene.getEnemyField()[0];
} else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY) {
source = globalScene.getEnemyField()[0];
} else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY_2) {
source = globalScene.getEnemyField()[1];
} else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER) {
source = globalScene.getPlayerField()[0];
} else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER_2) {
source = globalScene.getPlayerField()[1];
}
} else {
source = globalScene.getEnemyField()[0];
}
globalScene.phaseManager.pushNew(
"MovePhase",
// @ts-expect-error: source is guaranteed to be defined
source,
effect.targets,
effect.move,
effect.followUp,
effect.ignorePp,
);
}); });
// Pseudo turn end phase to reset flinch states, Endure, etc. // Pseudo turn end phase to reset flinch states, Endure, etc.

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,
@ -83,13 +84,14 @@ export const normalForm: SpeciesId[] = [
/** /**
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given * Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
* @param species The species to fetch * @param species - The {@linkcode SpeciesId} to fetch.
* If an array of `SpeciesId`s is passed (such as for named trainer spawn pools),
* one will be selected at random.
* @returns The associated {@linkcode PokemonSpecies} object * @returns The associated {@linkcode PokemonSpecies} object
*/ */
export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies { export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies {
// If a special pool (named trainers) is used here it CAN happen that they have a array as species (which means choose one of those two). So we catch that with this code block
if (Array.isArray(species)) { if (Array.isArray(species)) {
// Pick a random species from the list // TODO: this RNG roll should not be handled by this function
species = species[Math.floor(Math.random() * species.length)]; species = species[Math.floor(Math.random() * species.length)];
} }
if (species >= 2000) { if (species >= 2000) {

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,37 @@
/**
* Enum representing the possible ways a given BattlerTag can activate and/or tick down.
* Each tag can have multiple different behaviors attached to different lapse types.
*/
export enum BattlerTagLapseType {
// TODO: This is unused...
FAINT,
/**
* Tag activate before the holder uses a non-virtual move, possibly interrupting its action.
* @see MoveUseMode for more information
*/
MOVE,
/** Tag activates before the holder uses **any** move, triggering effects or interrupting its action. */
PRE_MOVE,
/** Tag activates immediately after the holder's move finishes triggering (successful or not). */
AFTER_MOVE,
/**
* Tag activates before move effects are applied.
* TODO: Stop using this as a catch-all "semi-invulnerability" tag
*/
MOVE_EFFECT,
/** Tag activates at the end of the turn. */
TURN_END,
/**
* Tag activates after the holder is hit by an attack, but before damage is applied.
* Occurs even if the user's {@linkcode SubstituteTag | Substitute} is hit.
*/
HIT,
/**
* Tag activates after the holder is directly hit by an attack.
* Does **not** occur on hits to the holder's {@linkcode SubstituteTag | Substitute},
* but still triggers on being KO'd.
*/
AFTER_HIT,
/** The tag has some other custom activation or removal condition. */
CUSTOM,
}

View File

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

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

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

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

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

View File

@ -0,0 +1,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
}

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