Merge branch 'beta' into rerollcleanup
@ -4,7 +4,7 @@ module.exports = {
|
||||
{
|
||||
name: "only-type-imports",
|
||||
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: {
|
||||
path: ["(^|/)src/@types", "(^|/)src/enums"],
|
||||
},
|
||||
@ -14,7 +14,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
name: "no-circular-at-runtime",
|
||||
severity: "warn",
|
||||
severity: "error",
|
||||
comment:
|
||||
"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) ",
|
||||
@ -34,7 +34,7 @@ module.exports = {
|
||||
"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 " +
|
||||
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||
severity: "warn",
|
||||
severity: "error",
|
||||
from: {
|
||||
orphan: true,
|
||||
pathNot: [
|
||||
@ -42,8 +42,7 @@ module.exports = {
|
||||
"[.]d[.]ts$", // TypeScript declaration files
|
||||
"(^|/)tsconfig[.]json$", // TypeScript config
|
||||
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
|
||||
// anything in src/@types
|
||||
"(^|/)src/@types/",
|
||||
"(^|/)test/.+[.]setup[.]ts", // Vitest setup files
|
||||
],
|
||||
},
|
||||
to: {},
|
||||
@ -53,7 +52,7 @@ module.exports = {
|
||||
comment:
|
||||
"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.",
|
||||
severity: "warn",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ["core"],
|
||||
@ -86,7 +85,7 @@ module.exports = {
|
||||
comment:
|
||||
"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.",
|
||||
severity: "warn",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ["deprecated"],
|
||||
@ -122,7 +121,7 @@ module.exports = {
|
||||
"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 " +
|
||||
"maintenance problems later on.",
|
||||
severity: "warn",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: {
|
||||
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",
|
||||
@ -188,7 +187,7 @@ module.exports = {
|
||||
"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 " +
|
||||
"add an exception to your dependency-cruiser configuration.",
|
||||
severity: "warn",
|
||||
severity: "error",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ["npm-peer"],
|
||||
@ -196,6 +195,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
options: {
|
||||
exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"],
|
||||
/* Which modules not to follow further when encountered */
|
||||
doNotFollow: {
|
||||
/* path: an array of regular expressions in strings to match against */
|
||||
@ -218,7 +218,7 @@ module.exports = {
|
||||
module systems it knows of. It's the default because it's the safe option
|
||||
It might come at a performance penalty, though.
|
||||
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
|
||||
|
||||
|
||||
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
|
||||
are widely used, you can limit the moduleSystems to those.
|
||||
*/
|
||||
@ -226,7 +226,7 @@ module.exports = {
|
||||
// moduleSystems: ['cjs', 'es6'],
|
||||
|
||||
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
|
||||
to open it on your online repo or `vscode://file/${process.cwd()}/` to
|
||||
to open it on your online repo or `vscode://file/${process.cwd()}/` to
|
||||
open it in visual studio code),
|
||||
*/
|
||||
// prefix: `vscode://file/${process.cwd()}/`,
|
||||
@ -235,7 +235,7 @@ module.exports = {
|
||||
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
|
||||
*/
|
||||
// tsPreCompilationDeps: false,
|
||||
tsPreCompilationDeps: true,
|
||||
|
||||
/* 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
|
||||
@ -271,7 +271,7 @@ module.exports = {
|
||||
to './webpack.conf.js'.
|
||||
|
||||
The (optional) `env` and `arguments` attributes contain the parameters
|
||||
to be passed if your webpack config is a function and takes them (see
|
||||
to be passed if your webpack config is a function and takes them (see
|
||||
webpack documentation for details)
|
||||
*/
|
||||
// webpackConfig: {
|
||||
@ -322,8 +322,8 @@ module.exports = {
|
||||
A list of alias fields in package.jsons
|
||||
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
|
||||
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
|
||||
documentation
|
||||
|
||||
documentation
|
||||
|
||||
Defaults to an empty array (= don't use alias fields).
|
||||
*/
|
||||
// aliasFields: ["browser"],
|
||||
|
2
.github/CODEOWNERS
vendored
@ -8,7 +8,7 @@
|
||||
|
||||
# Art Team
|
||||
/public/**/*.png @pagefaultgames/art-team
|
||||
/public/**/*.json @pagefaultgames/art-team
|
||||
/public/**/*.json @pagefaultgames/art-team
|
||||
/public/images @pagefaultgames/art-team
|
||||
/public/battle-anims @pagefaultgames/art-team
|
||||
|
||||
|
37
.github/pull_request_template.md
vendored
@ -2,25 +2,28 @@
|
||||
<!-- Feel free to look at other PRs for examples -->
|
||||
<!--
|
||||
Make sure the title includes categorization (choose the one that best fits):
|
||||
- [Bug]: If the PR is primarily a bug fix
|
||||
- [Move]: If a move has new or changed functionality
|
||||
- [Ability]: If an ability has new or changed functionality
|
||||
- [Item]: For new or modified items
|
||||
- [Mystery]: For new or modified Mystery Encounters
|
||||
- [Test]: If the PR is primarily adding or modifying tests
|
||||
- [UI/UX]: If the PR is changing UI/UX elements
|
||||
- [Audio]: If the PR is adding or changing music/sfx
|
||||
- [Sprite]: If the PR is adding or changing sprites
|
||||
- [Balance]: If the PR is related to game balance
|
||||
- [Challenge]: If the PR is adding or modifying challenges
|
||||
- [Bug]: If the PR is primarily a bug fix
|
||||
- [Move]: If a move has new or changed functionality
|
||||
- [Ability]: If an ability has new or changed functionality
|
||||
- [Item]: For new or modified items
|
||||
- [Mystery]: For new or modified Mystery Encounters
|
||||
- [Test]: If the PR is primarily adding or modifying tests
|
||||
- [UI/UX]: If the PR is changing UI/UX elements
|
||||
- [Audio]: If the PR is adding or changing music/sfx
|
||||
- [Sprite]: If the PR is adding or changing sprites
|
||||
- [Balance]: If the PR is related to game balance
|
||||
- [Challenge]: If the PR is adding or modifying challenges
|
||||
- [Refactor]: If the PR is primarily rewriting existing code
|
||||
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments)
|
||||
- [GitHub]: For changes to GitHub workflows/templates/etc
|
||||
- [Misc]: If no other category fits the PR
|
||||
- [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
|
||||
- [Misc]: If no other category fits the PR
|
||||
-->
|
||||
|
||||
<!--
|
||||
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?
|
||||
@ -66,11 +69,11 @@ 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 tested the changes manually?
|
||||
- [ ] 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 made sure that any UI change works for both UI themes (default and legacy)?
|
||||
|
||||
Are there any localization additions or changes? If so:
|
||||
- [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo?
|
||||
- [ ] If so, please leave a link to it here:
|
||||
- [ ] Has the translation team been contacted for proofreading/translation?
|
||||
- [ ] Has the translation team been contacted for proofreading/translation?
|
||||
|
2
.github/workflows/deploy.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
|
||||
- name: Deploy build on server
|
||||
if: github.event_name == 'push' && github.ref_name == 'main'
|
||||
run: |
|
||||
run: |
|
||||
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }}
|
||||
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
|
||||
- name: Purge Cloudflare Cache
|
||||
|
42
.github/workflows/linting.yml
vendored
Normal 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
|
41
.github/workflows/quality.yml
vendored
@ -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
|
29
biome.jsonc
@ -28,7 +28,6 @@
|
||||
".vscode/*",
|
||||
"*.css", // TODO?
|
||||
"*.html", // TODO?
|
||||
"src/overrides.ts",
|
||||
// TODO: these files are too big and complex, ignore them until their respective refactors
|
||||
"src/data/moves/move.ts",
|
||||
|
||||
@ -47,8 +46,8 @@
|
||||
"correctness": {
|
||||
"noUndeclaredVariables": "off",
|
||||
"noUnusedVariables": "error",
|
||||
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error
|
||||
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error
|
||||
"noSwitchDeclarations": "error",
|
||||
"noVoidTypeReturn": "error",
|
||||
"noUnusedImports": "error"
|
||||
},
|
||||
"style": {
|
||||
@ -85,7 +84,7 @@
|
||||
"useLiteralKeys": "off",
|
||||
"noForEach": "off", // Foreach vs for of is not that simple.
|
||||
"noUselessSwitchCase": "off", // Explicit > Implicit
|
||||
"noUselessConstructor": "warn", // TODO: Refactor and make this an error
|
||||
"noUselessConstructor": "error",
|
||||
"noBannedTypes": "warn" // TODO: Refactor and make this an error
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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();
|
2
global.d.ts
vendored
@ -7,7 +7,7 @@ declare global {
|
||||
* Only used in testing.
|
||||
* Can technically be undefined/null but for ease of use we are going to assume it is always defined.
|
||||
* Used to load i18n files exclusively.
|
||||
*
|
||||
*
|
||||
* To set up your own server in a test see `game_data.test.ts`
|
||||
*/
|
||||
var server: SetupServerApi;
|
||||
|
@ -13,15 +13,15 @@
|
||||
"test:cov": "vitest run --coverage --no-isolate",
|
||||
"test:watch": "vitest watch --coverage --no-isolate",
|
||||
"test:silent": "vitest run --silent --no-isolate",
|
||||
"test:create": "node scripts/create-test/create-test.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"eslint": "eslint --fix .",
|
||||
"eslint-ci": "eslint .",
|
||||
"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",
|
||||
"depcruise": "depcruise src",
|
||||
"depcruise": "depcruise src test",
|
||||
"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",
|
||||
"update-version:patch": "npm version patch --force --no-git-tag-version",
|
||||
"update-version:minor": "npm version minor --force --no-git-tag-version",
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 5.5 KiB |
147
scripts/create-test/create-test.js
Normal 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
|
43
scripts/create-test/test-boilerplate.ts
Normal 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);
|
||||
});
|
||||
});
|
@ -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 Pokemon from "#app/field/pokemon";
|
||||
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;
|
||||
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean;
|
||||
// Intentionally re-export all types from the ability attributes module
|
||||
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 PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
||||
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => 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]>;
|
||||
};
|
||||
|
32
src/@types/modifier-types.ts
Normal 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
@ -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;
|
@ -58,35 +58,29 @@ import {
|
||||
getEnemyModifierTypesForWave,
|
||||
getLuckString,
|
||||
getLuckTextTint,
|
||||
getModifierPoolForType,
|
||||
getModifierType,
|
||||
getPartyLuckValue,
|
||||
ModifierPoolType,
|
||||
modifierTypes,
|
||||
PokemonHeldItemModifierType,
|
||||
} 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 {
|
||||
applyAbAttrs,
|
||||
applyPostBattleInitAbAttrs,
|
||||
applyPostItemLostAbAttrs,
|
||||
BlockItemTheftAbAttr,
|
||||
DoubleBattleChanceAbAttr,
|
||||
PostBattleInitAbAttr,
|
||||
PostItemLostAbAttr,
|
||||
} from "#app/data/abilities/ability";
|
||||
import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs";
|
||||
import { allAbilities } from "./data/data-lists";
|
||||
import type { FixedBattleConfig } from "#app/battle";
|
||||
import Battle from "#app/battle";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
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 SpritePipeline from "#app/pipelines/sprite";
|
||||
import PartyExpBar from "#app/ui/party-exp-bar";
|
||||
import type { TrainerSlot } from "./enums/trainer-slot";
|
||||
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 SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||
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 type PokemonData from "#app/system/pokemon-data";
|
||||
import { Nature } from "#enums/nature";
|
||||
import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms";
|
||||
import {
|
||||
FormChangeItem,
|
||||
pokemonFormChanges,
|
||||
SpeciesFormChangeManualTrigger,
|
||||
SpeciesFormChangeTimeOfDayTrigger,
|
||||
} from "#app/data/pokemon-forms";
|
||||
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
|
||||
import type { SpeciesFormChangeTrigger } from "./data/pokemon-forms/form-change-triggers";
|
||||
import { pokemonFormChanges } from "#app/data/pokemon-forms";
|
||||
import { SpeciesFormChangeTimeOfDayTrigger } from "./data/pokemon-forms/form-change-triggers";
|
||||
import { SpeciesFormChangeManualTrigger } from "./data/pokemon-forms/form-change-triggers";
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
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 { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
||||
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { allMysteryEncounters, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import {
|
||||
allMysteryEncounters,
|
||||
ANTI_VARIANCE_WEIGHT_MODIFIER,
|
||||
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
|
||||
BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT,
|
||||
MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT,
|
||||
mysteryEncountersByBiome,
|
||||
} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
} from "./constants";
|
||||
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
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);
|
||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
||||
for (const p of playerField) {
|
||||
applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance);
|
||||
applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
|
||||
}
|
||||
return Math.max(doubleChance.value, 1);
|
||||
}
|
||||
@ -1468,7 +1460,7 @@ export default class BattleScene extends SceneBase {
|
||||
for (const pokemon of this.getPlayerParty()) {
|
||||
pokemon.resetBattleAndWaveData();
|
||||
pokemon.resetTera();
|
||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
||||
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
||||
if (
|
||||
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
||||
(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.PALDEA_TAUROS:
|
||||
return randSeedInt(species.forms.length);
|
||||
case SpeciesId.MAUSHOLD:
|
||||
case SpeciesId.DUDUNSPARCE:
|
||||
return !randSeedInt(4) ? 1 : 0;
|
||||
case SpeciesId.PIKACHU:
|
||||
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 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);
|
||||
|
||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled);
|
||||
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
||||
}
|
||||
|
||||
if (cancelled.value) {
|
||||
@ -2784,13 +2779,13 @@ export default class BattleScene extends SceneBase {
|
||||
if (target.isPlayer()) {
|
||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
||||
if (source && itemLost) {
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
||||
if (source && itemLost) {
|
||||
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false);
|
||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -2813,7 +2808,7 @@ export default class BattleScene extends SceneBase {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
|
||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled);
|
||||
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
||||
}
|
||||
|
||||
if (cancelled.value) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Command } from "./ui/command-ui-handler";
|
||||
import type { Command } from "#enums/command";
|
||||
import {
|
||||
randomString,
|
||||
getEnumValues,
|
||||
@ -10,9 +10,10 @@ import {
|
||||
randInt,
|
||||
randSeedFloat,
|
||||
} 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 { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
|
||||
import { MoneyMultiplierModifier, type PokemonHeldItemModifier } from "./modifier/modifier";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||
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 { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
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 { BattleType } from "#enums/battle-type";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
|
||||
export enum BattlerIndex {
|
||||
ATTACKER = -1,
|
||||
PLAYER,
|
||||
PLAYER_2,
|
||||
ENEMY,
|
||||
ENEMY_2,
|
||||
}
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
|
||||
export interface TurnCommand {
|
||||
command: Command;
|
||||
@ -179,7 +173,7 @@ export default class Battle {
|
||||
this.postBattleLoot.push(
|
||||
...globalScene
|
||||
.findModifiers(
|
||||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
||||
m => m.is("PokemonHeldItemModifier") && m.pokemonId === enemyPokemon.id && m.isTransferable,
|
||||
false,
|
||||
)
|
||||
.map(i => {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
||||
/** The maximum size of the player's party */
|
||||
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*/
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
832
src/data/abilities/apply-ab-attrs.ts
Normal 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,
|
||||
);
|
||||
}
|
@ -7,31 +7,20 @@ import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
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 type { BattlerIndex } from "#app/battle";
|
||||
import {
|
||||
BlockNonDirectDamageAbAttr,
|
||||
InfiltratorAbAttr,
|
||||
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
|
||||
ProtectStatAbAttr,
|
||||
applyAbAttrs,
|
||||
applyOnGainAbAttrs,
|
||||
applyOnLoseAbAttrs,
|
||||
} from "#app/data/abilities/ability";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "./abilities/apply-ab-attrs";
|
||||
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 { AbilityId } from "#enums/ability-id";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
|
||||
export enum ArenaTagSide {
|
||||
BOTH,
|
||||
PLAYER,
|
||||
ENEMY,
|
||||
}
|
||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
export abstract class ArenaTag {
|
||||
constructor(
|
||||
@ -148,7 +137,7 @@ export class MistTag extends ArenaTag {
|
||||
if (attacker) {
|
||||
const bypassed = new BooleanHolder(false);
|
||||
// TODO: Allow this to be simulated
|
||||
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
||||
if (bypassed.value) {
|
||||
return false;
|
||||
}
|
||||
@ -213,7 +202,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
||||
): boolean {
|
||||
if (this.weakenedCategories.includes(moveCategory)) {
|
||||
const bypassed = new BooleanHolder(false);
|
||||
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed);
|
||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
||||
if (bypassed.value) {
|
||||
return false;
|
||||
}
|
||||
@ -769,7 +758,7 @@ class SpikesTag extends ArenaTrapTag {
|
||||
}
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
if (simulated || cancelled.value) {
|
||||
return !cancelled.value;
|
||||
}
|
||||
@ -887,13 +876,13 @@ export class DelayedAttackTag extends ArenaTag {
|
||||
const ret = super.lapse(arena);
|
||||
|
||||
if (!ret) {
|
||||
// TODO: This should not add to move history (for Spite)
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"MoveEffectPhase",
|
||||
this.sourceId!,
|
||||
[this.targetIndex],
|
||||
allMoves[this.sourceMove!],
|
||||
false,
|
||||
true,
|
||||
MoveUseMode.FOLLOW_UP,
|
||||
); // TODO: are those bangs correct?
|
||||
}
|
||||
|
||||
@ -957,7 +946,7 @@ class StealthRockTag extends ArenaTrapTag {
|
||||
|
||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
if (cancelled.value) {
|
||||
return false;
|
||||
}
|
||||
@ -1014,7 +1003,7 @@ class StickyWebTag extends ArenaTrapTag {
|
||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||
if (pokemon.isGrounded()) {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
||||
|
||||
if (simulated) {
|
||||
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
|
||||
const setter = globalScene
|
||||
.getField()
|
||||
.filter(p => p?.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0];
|
||||
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr));
|
||||
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
|
||||
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1461,7 +1450,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
||||
|
||||
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
|
||||
if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) {
|
||||
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
||||
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
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 { speciesStarterCosts } from "./starters";
|
||||
import i18next from "i18next";
|
||||
@ -275,9 +274,9 @@ class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition {
|
||||
class TreasureEvolutionCondition extends SpeciesEvolutionCondition {
|
||||
constructor() {
|
||||
super(p => p.evoCounter
|
||||
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
|
||||
+ globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier
|
||||
|| m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9);
|
||||
+ p.getHeldItems().filter(m => m.is("DamageMoneyRewardModifier")).length
|
||||
+ globalScene.findModifiers(m => m.is("MoneyMultiplierModifier")
|
||||
|| m.is("ExtraModifierModifier") || m.is("TempExtraModifierModifier")).length > 9);
|
||||
this.description = i18next.t("pokemonEvolutions:treasure");
|
||||
}
|
||||
}
|
||||
@ -1794,8 +1793,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
||||
],
|
||||
[SpeciesId.CLAMPERL]: [
|
||||
// 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.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.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.is("SpeciesStatBoosterModifier") && (m.type as SpeciesStatBoosterModifierType).key === "DEEP_SEA_SCALE")), SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
],
|
||||
[SpeciesId.BOLDORE]: [
|
||||
new SpeciesEvolution(SpeciesId.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
||||
|
@ -1,111 +1,22 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move";
|
||||
import { allMoves } from "./data-lists";
|
||||
import { allMoves } from "#app/data/data-lists";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
|
||||
import type { BattlerIndex } from "../battle";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import {
|
||||
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 { SubstituteTag } from "./battler-tags";
|
||||
import { isNullOrUndefined } from "../utils/common";
|
||||
import Phaser from "phaser";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
|
||||
export enum AnimFrameTarget {
|
||||
USER,
|
||||
TARGET,
|
||||
GRAPHIC,
|
||||
}
|
||||
|
||||
enum AnimFocus {
|
||||
TARGET = 1,
|
||||
USER,
|
||||
USER_TARGET,
|
||||
SCREEN,
|
||||
}
|
||||
|
||||
enum AnimBlendType {
|
||||
NORMAL,
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
}
|
||||
|
||||
export enum ChargeAnim {
|
||||
FLY_CHARGING = 1000,
|
||||
BOUNCE_CHARGING,
|
||||
DIG_CHARGING,
|
||||
FUTURE_SIGHT_CHARGING,
|
||||
DIVE_CHARGING,
|
||||
SOLAR_BEAM_CHARGING,
|
||||
SHADOW_FORCE_CHARGING,
|
||||
SKULL_BASH_CHARGING,
|
||||
FREEZE_SHOCK_CHARGING,
|
||||
SKY_DROP_CHARGING,
|
||||
SKY_ATTACK_CHARGING,
|
||||
ICE_BURN_CHARGING,
|
||||
DOOM_DESIRE_CHARGING,
|
||||
RAZOR_WIND_CHARGING,
|
||||
PHANTOM_FORCE_CHARGING,
|
||||
GEOMANCY_CHARGING,
|
||||
SHADOW_BLADE_CHARGING,
|
||||
SOLAR_BLADE_CHARGING,
|
||||
BEAK_BLAST_CHARGING,
|
||||
METEOR_BEAM_CHARGING,
|
||||
ELECTRO_SHOT_CHARGING,
|
||||
}
|
||||
|
||||
export enum CommonAnim {
|
||||
USE_ITEM = 2000,
|
||||
HEALTH_UP,
|
||||
TERASTALLIZE,
|
||||
POISON = 2010,
|
||||
TOXIC,
|
||||
PARALYSIS,
|
||||
SLEEP,
|
||||
FROZEN,
|
||||
BURN,
|
||||
CONFUSION,
|
||||
ATTRACT,
|
||||
BIND,
|
||||
WRAP,
|
||||
CURSE_NO_GHOST,
|
||||
LEECH_SEED,
|
||||
FIRE_SPIN,
|
||||
PROTECT,
|
||||
COVET,
|
||||
WHIRLPOOL,
|
||||
BIDE,
|
||||
SAND_TOMB,
|
||||
QUICK_GUARD,
|
||||
WIDE_GUARD,
|
||||
CURSE,
|
||||
MAGMA_STORM,
|
||||
CLAMP,
|
||||
SNAP_TRAP,
|
||||
THUNDER_CAGE,
|
||||
INFESTATION,
|
||||
ORDER_UP_CURLY,
|
||||
ORDER_UP_DROOPY,
|
||||
ORDER_UP_STRETCHY,
|
||||
RAGING_BULL_FIRE,
|
||||
RAGING_BULL_WATER,
|
||||
SALT_CURE,
|
||||
POWDER,
|
||||
SUNNY = 2100,
|
||||
RAIN,
|
||||
SANDSTORM,
|
||||
HAIL,
|
||||
SNOW,
|
||||
WIND,
|
||||
HEAVY_RAIN,
|
||||
HARSH_SUN,
|
||||
STRONG_WINDS,
|
||||
MISTY_TERRAIN = 2110,
|
||||
ELECTRIC_TERRAIN,
|
||||
GRASSY_TERRAIN,
|
||||
PSYCHIC_TERRAIN,
|
||||
LOCK_ON = 2120,
|
||||
}
|
||||
import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
|
||||
export class AnimConfig {
|
||||
public id: number;
|
||||
@ -531,7 +442,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
|
||||
if (moveAnims.get(move) !== null) {
|
||||
const chargeAnimSource = allMoves[move].isChargingMove()
|
||||
? 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) {
|
||||
return;
|
||||
}
|
||||
@ -542,12 +453,11 @@ export function initMoveAnim(move: MoveId): Promise<void> {
|
||||
}
|
||||
} else {
|
||||
moveAnims.set(move, null);
|
||||
const defaultMoveAnim =
|
||||
allMoves[move] instanceof AttackMove
|
||||
? MoveId.TACKLE
|
||||
: allMoves[move] instanceof SelfStatusMove
|
||||
? MoveId.FOCUS_ENERGY
|
||||
: MoveId.TAIL_WHIP;
|
||||
const defaultMoveAnim = allMoves[move].is("AttackMove")
|
||||
? MoveId.TACKLE
|
||||
: allMoves[move].is("SelfStatusMove")
|
||||
? MoveId.FOCUS_ENERGY
|
||||
: MoveId.TAIL_WHIP;
|
||||
|
||||
const fetchAnimAndResolve = (move: MoveId) => {
|
||||
globalScene
|
||||
@ -570,7 +480,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
|
||||
}
|
||||
const chargeAnimSource = allMoves[move].isChargingMove()
|
||||
? allMoves[move]
|
||||
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]);
|
||||
: (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
|
||||
if (chargeAnimSource) {
|
||||
initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve());
|
||||
} else {
|
||||
@ -616,7 +526,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
|
||||
* @param encounterAnim one or more animations to fetch
|
||||
*/
|
||||
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 encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
|
||||
for (const anim of anims) {
|
||||
@ -703,7 +613,7 @@ export function loadMoveAnimAssets(moveIds: MoveId[], startLoad?: boolean): Prom
|
||||
for (const moveId of moveIds) {
|
||||
const chargeAnimSource = allMoves[moveId].isChargingMove()
|
||||
? allMoves[moveId]
|
||||
: (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]);
|
||||
: (allMoves[moveId].getAttrs("DelayedAttackAttr")[0] ?? allMoves[moveId].getAttrs("BeakBlastHeaderAttr")[0]);
|
||||
if (chargeAnimSource) {
|
||||
const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim);
|
||||
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 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 userInitialY = user!.y; // TODO: is this bang correct?
|
||||
@ -941,7 +851,7 @@ export abstract class BattleAnim {
|
||||
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 targetSprite = targetSubstitute?.sprite ?? target.getSprite();
|
||||
|
@ -1,37 +1,27 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import Overrides from "#app/overrides";
|
||||
import {
|
||||
applyAbAttrs,
|
||||
BlockNonDirectDamageAbAttr,
|
||||
FlinchEffectAbAttr,
|
||||
ProtectStatAbAttr,
|
||||
ConditionalUserFieldProtectStatAbAttr,
|
||||
ReverseDrainAbAttr,
|
||||
} from "#app/data/abilities/ability";
|
||||
import { applyAbAttrs } from "./abilities/apply-ab-attrs";
|
||||
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 {
|
||||
applyMoveAttrs,
|
||||
ConsecutiveUseDoublePowerAttr,
|
||||
HealOnAllyAttr,
|
||||
StatusCategoryOnAllyAttr,
|
||||
} from "#app/data/moves/move";
|
||||
import { applyMoveAttrs } from "./moves/apply-attrs";
|
||||
import { allMoves } from "./data-lists";
|
||||
import { MoveFlags } from "#enums/MoveFlags";
|
||||
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 { TerrainType } from "#app/data/terrain";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
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 type { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import type { MovePhase } from "#app/phases/move-phase";
|
||||
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
|
||||
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 { BattlerTagType } from "#enums/battler-tag-type";
|
||||
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 { WeatherType } from "#enums/weather-type";
|
||||
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,
|
||||
MOVE,
|
||||
PRE_MOVE,
|
||||
AFTER_MOVE,
|
||||
MOVE_EFFECT,
|
||||
TURN_END,
|
||||
HIT,
|
||||
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
|
||||
AFTER_HIT,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@linkcode BattlerTag} represents a semi-persistent effect that can be attached to a {@linkcode Pokemon}.
|
||||
* Tags can trigger various effects throughout a turn, and are cleared on switching out
|
||||
* or through their respective {@linkcode BattlerTag.lapse | lapse} methods.
|
||||
*/
|
||||
export class BattlerTag {
|
||||
public tagType: BattlerTagType;
|
||||
public lapseTypes: BattlerTagLapseType[];
|
||||
@ -72,7 +57,7 @@ export class BattlerTag {
|
||||
isBatonPassable = false,
|
||||
) {
|
||||
this.tagType = tagType;
|
||||
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType];
|
||||
this.lapseTypes = coerceArray(lapseType);
|
||||
this.turnCount = turnCount;
|
||||
this.sourceMove = sourceMove!; // TODO: is this bang correct?
|
||||
this.sourceId = sourceId;
|
||||
@ -91,7 +76,7 @@ export class BattlerTag {
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
// 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.
|
||||
*/
|
||||
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 lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||
@ -299,17 +274,18 @@ export class DisabledTag extends MoveRestrictionBattlerTag {
|
||||
/**
|
||||
* @override
|
||||
*
|
||||
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@linkcode moveId} and shows a message.
|
||||
* Otherwise the move ID will not get assigned and this tag will get removed next turn.
|
||||
* Attempt to disable the target's last move by setting this tag's {@linkcode moveId}
|
||||
* and showing a message.
|
||||
*/
|
||||
override onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
const move = pokemon.getLastXMoves(-1).find(m => !m.virtual);
|
||||
if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE || move.move === MoveId.NONE) {
|
||||
// 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.getLastNonVirtualMove();
|
||||
if (isNullOrUndefined(move) || move.move === MoveId.STRUGGLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.onAdd(pokemon);
|
||||
this.moveId = move.move;
|
||||
|
||||
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.
|
||||
* @extends MoveRestrictionBattlerTag
|
||||
*/
|
||||
export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
||||
private moveId = MoveId.NONE;
|
||||
@ -368,34 +343,30 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
||||
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
override isMoveRestricted(move: MoveId): boolean {
|
||||
return move !== this.moveId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Pokemon} pokemon the {@linkcode Pokemon} to check if the tag can be added
|
||||
* @returns `true` if the pokemon has a valid move and no existing {@linkcode GorillaTacticsTag}; `false` otherwise
|
||||
* Ensures that move history exists on {@linkcode Pokemon} and has a valid move to lock into.
|
||||
* @param pokemon - The {@linkcode Pokemon} to add the tag to
|
||||
* @returns `true` if the tag can be added
|
||||
*/
|
||||
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.
|
||||
* If so, sets the {@linkcode moveId} and increases the user's Attack by 50%.
|
||||
* @override
|
||||
* @param {Pokemon} pokemon the {@linkcode Pokemon} to add the tag to
|
||||
* Sets this tag's {@linkcode moveId} and increases the user's Attack by 50%.
|
||||
* @param pokemon - The {@linkcode Pokemon} to add the tag to
|
||||
*/
|
||||
override onAdd(pokemon: Pokemon): void {
|
||||
const lastValidMove = this.getLastValidMove(pokemon);
|
||||
super.onAdd(pokemon);
|
||||
|
||||
if (!lastValidMove) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.moveId = lastValidMove;
|
||||
// Bang is justified as tag is not added if prior move doesn't exist
|
||||
this.moveId = pokemon.getLastNonVirtualMove()!.move;
|
||||
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
|
||||
}
|
||||
|
||||
@ -410,29 +381,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
* @param {Pokemon} pokemon n/a
|
||||
* @param {MoveId} _move {@linkcode MoveId} ID of the move being denied
|
||||
* @returns {string} text to display when the move is denied
|
||||
* Return the text displayed when a move is restricted.
|
||||
* @param pokemon - The {@linkcode Pokemon} with this tag.
|
||||
* @returns A string containing the 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", {
|
||||
moveName: allMoves[this.moveId].name,
|
||||
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 {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
// Queue a placeholder move for the Pokemon to "use" next turn
|
||||
pokemon.getMoveQueue().push({ move: MoveId.NONE, targets: [] });
|
||||
// Queue a placeholder move for the Pokemon to "use" next turn.
|
||||
pokemon.pushMoveQueue({ move: MoveId.NONE, targets: [], useMode: MoveUseMode.NORMAL });
|
||||
}
|
||||
|
||||
/** 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),
|
||||
}),
|
||||
);
|
||||
applyAbAttrs(FlinchEffectAbAttr, pokemon, null);
|
||||
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -692,6 +650,7 @@ export class InterruptedTag extends BattlerTag {
|
||||
move: MoveId.NONE,
|
||||
result: MoveResult.OTHER,
|
||||
targets: [],
|
||||
useMode: MoveUseMode.NORMAL,
|
||||
});
|
||||
}
|
||||
|
||||
@ -957,7 +916,7 @@ export class SeedTag extends BattlerTag {
|
||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||
if (source) {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
|
||||
if (!cancelled.value) {
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
@ -968,7 +927,7 @@ export class SeedTag extends BattlerTag {
|
||||
);
|
||||
|
||||
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(
|
||||
"PokemonHealPhase",
|
||||
source.getBattlerIndex(),
|
||||
@ -1017,42 +976,45 @@ export class PowderTag extends BattlerTag {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies Powder's effects before the tag owner uses a Fire-type move.
|
||||
* Also causes the tag to expire at the end of turn.
|
||||
* @param pokemon {@linkcode Pokemon} the owner of this tag
|
||||
* @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out
|
||||
* @returns `true` if the tag should not expire after this lapse; `false` otherwise.
|
||||
* Applies Powder's effects before the tag owner uses a Fire-type move, damaging and canceling its action.
|
||||
* Lasts until the end of the turn.
|
||||
* @param pokemon - The {@linkcode Pokemon} with this tag.
|
||||
* @param lapseType - The {@linkcode BattlerTagLapseType} dictating how this tag is being activated
|
||||
* @returns `true` if the tag should remain active.
|
||||
*/
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||
const movePhase = globalScene.phaseManager.getCurrentPhase();
|
||||
if (movePhase?.is("MovePhase")) {
|
||||
const move = movePhase.move.getMove();
|
||||
const weather = globalScene.arena.weather;
|
||||
if (
|
||||
pokemon.getMoveType(move) === PokemonType.FIRE &&
|
||||
!(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed())
|
||||
) {
|
||||
movePhase.fail();
|
||||
movePhase.showMoveText();
|
||||
const movePhase = globalScene.phaseManager.getCurrentPhase();
|
||||
if (lapseType !== BattlerTagLapseType.PRE_MOVE || !movePhase?.is("MovePhase")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const idx = pokemon.getBattlerIndex();
|
||||
|
||||
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
||||
|
||||
const cancelDamage = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage);
|
||||
if (!cancelDamage.value) {
|
||||
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||
}
|
||||
|
||||
// "When the flame touched the powder\non the Pokémon, it exploded!"
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
|
||||
}
|
||||
}
|
||||
const move = movePhase.move.getMove();
|
||||
const weather = globalScene.arena.weather;
|
||||
if (
|
||||
pokemon.getMoveType(move) !== PokemonType.FIRE ||
|
||||
(weather?.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed()) // Heavy rain takes priority over powder
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return super.lapse(pokemon, lapseType);
|
||||
|
||||
// Disable the target's fire type move and damage it (subject to Magic Guard)
|
||||
movePhase.showMoveText();
|
||||
movePhase.fail();
|
||||
|
||||
const idx = pokemon.getBattlerIndex();
|
||||
|
||||
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
||||
|
||||
const cancelDamage = new BooleanHolder(false);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
|
||||
if (!cancelDamage.value) {
|
||||
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||
}
|
||||
|
||||
// "When the flame touched the powder\non the Pokémon, it exploded!"
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name }));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1094,7 +1056,7 @@ export class NightmareTag extends BattlerTag {
|
||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
|
||||
if (!cancelled.value) {
|
||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||
@ -1147,34 +1109,22 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
const lastMoves = pokemon.getLastXMoves(1);
|
||||
if (!lastMoves.length) {
|
||||
const lastMove = pokemon.getLastNonVirtualMove();
|
||||
if (!lastMove) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const repeatableMove = lastMoves[0];
|
||||
|
||||
if (!repeatableMove.move || repeatableMove.virtual) {
|
||||
if (invalidEncoreMoves.has(lastMove.move)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (repeatableMove.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;
|
||||
this.moveId = lastMove.move;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
// TODO: shouldn't this be `onAdd`?
|
||||
super.onRemove(pokemon);
|
||||
|
||||
globalScene.phaseManager.queueMessage(
|
||||
@ -1190,7 +1140,13 @@ export class EncoreTag extends MoveRestrictionBattlerTag {
|
||||
const lastMove = pokemon.getLastXMoves(1)[0];
|
||||
globalScene.phaseManager.tryReplacePhase(
|
||||
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);
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
|
||||
if (!cancelled.value) {
|
||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||
@ -1492,16 +1448,6 @@ export class WrapTag 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 {
|
||||
return i18next.t("battlerTags:vortexOnTrap", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
@ -1696,7 +1642,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
|
||||
*/
|
||||
override onContact(attacker: Pokemon, user: Pokemon): void {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
||||
if (!cancelled.value) {
|
||||
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
||||
result: HitResult.INDIRECT,
|
||||
@ -1926,24 +1872,29 @@ export class TruantTag extends AbilityBattlerTag {
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
if (!pokemon.hasAbility(AbilityId.TRUANT)) {
|
||||
// remove tag if mon lacks ability
|
||||
return super.lapse(pokemon, lapseType);
|
||||
}
|
||||
const passive = pokemon.getAbility().id !== AbilityId.TRUANT;
|
||||
|
||||
const lastMove = pokemon.getLastXMoves().find(() => true);
|
||||
const lastMove = pokemon.getLastXMoves()[0];
|
||||
|
||||
if (lastMove && lastMove.move !== MoveId.NONE) {
|
||||
(globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel();
|
||||
// TODO: Ability displays should be handled by the ability
|
||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("battlerTags:truantLapse", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
}),
|
||||
);
|
||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
||||
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;
|
||||
(globalScene.phaseManager.getCurrentPhase() as MovePhase).cancel();
|
||||
// TODO: Ability displays should be handled by the ability
|
||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("battlerTags:truantLapse", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
}),
|
||||
);
|
||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2292,7 +2243,7 @@ export class SaltCuredTag extends BattlerTag {
|
||||
);
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
|
||||
if (!cancelled.value) {
|
||||
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
||||
@ -2346,7 +2297,7 @@ export class CursedTag extends BattlerTag {
|
||||
);
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
||||
|
||||
if (!cancelled.value) {
|
||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||
@ -2681,7 +2632,7 @@ export class GulpMissileTag extends BattlerTag {
|
||||
}
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
||||
applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
|
||||
|
||||
if (!cancelled.value) {
|
||||
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) {
|
||||
const moveCategory = new NumberHolder(allMoves[move].category);
|
||||
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory);
|
||||
return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS;
|
||||
applyMoveAttrs("StatusCategoryOnAllyAttr", user, target, allMoves[move], moveCategory);
|
||||
return allMoves[move].hasAttr("HealOnAllyAttr") && moveCategory.value === MoveCategory.STATUS;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3071,8 +3022,8 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
||||
|
||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||
applyAbAttrs(ConditionalUserFieldProtectStatAbAttr, pokemon, cancelled, false, pokemon);
|
||||
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
||||
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
|
||||
if (!cancelled.value) {
|
||||
if (pokemon.mysteryEncounterBattleEffects) {
|
||||
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
|
||||
// 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 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;
|
||||
return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { getPokemonNameWithAffix } from "../messages";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import { HitResult } from "../field/pokemon";
|
||||
import { HitResult } from "#enums/hit-result";
|
||||
import { getStatusEffectHealText } from "./status-effect";
|
||||
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 { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
@ -38,25 +38,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
||||
const threshold = new NumberHolder(0.25);
|
||||
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
||||
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;
|
||||
};
|
||||
case BerryType.LANSAT:
|
||||
return (pokemon: Pokemon) => {
|
||||
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);
|
||||
};
|
||||
case BerryType.STARF:
|
||||
return (pokemon: Pokemon) => {
|
||||
const threshold = new NumberHolder(0.25);
|
||||
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
|
||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
||||
return pokemon.getHpRatio() < 0.25;
|
||||
};
|
||||
case BerryType.LEPPA:
|
||||
return (pokemon: Pokemon) => {
|
||||
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());
|
||||
};
|
||||
}
|
||||
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||
case BerryType.ENIGMA:
|
||||
{
|
||||
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed);
|
||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"PokemonHealPhase",
|
||||
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.
|
||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||
const statStages = new NumberHolder(1);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages);
|
||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
consumer.getBattlerIndex(),
|
||||
@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||
{
|
||||
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
||||
const stages = new NumberHolder(2);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages);
|
||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages);
|
||||
globalScene.phaseManager.unshiftNew(
|
||||
"StatStageChangePhase",
|
||||
consumer.getBattlerIndex(),
|
||||
|
@ -2,17 +2,18 @@ import { BooleanHolder, type NumberHolder, randSeedItem } from "#app/utils/commo
|
||||
import { deepCopy } from "#app/utils/data";
|
||||
import i18next from "i18next";
|
||||
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 { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
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 { getRandomTrainerFunc } from "#app/battle";
|
||||
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
|
||||
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 { Challenges } from "#enums/challenges";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
@ -20,97 +21,16 @@ import { TrainerType } from "#enums/trainer-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
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 { pokemonFormChanges } from "./pokemon-forms";
|
||||
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 */
|
||||
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.
|
||||
*/
|
||||
|
@ -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";
|
||||
|
||||
export const allAbilities: Ability[] = [];
|
||||
export const allMoves: Move[] = [];
|
||||
|
||||
// TODO: Figure out what this is used for and provide an appropriate tsdoc comment
|
||||
export const modifierTypes = {} as ModifierTypes;
|
||||
|
@ -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 = {
|
||||
[BattleSpec.FINAL_BOSS]: {
|
||||
encounter: "battleSpecDialogue:encounter",
|
||||
|
44
src/data/double-battle-dialogue.ts
Normal 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"],
|
||||
},
|
||||
};
|
58
src/data/moves/apply-attrs.ts
Normal 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);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
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([
|
||||
MoveId.AFTER_YOU,
|
||||
MoveId.ASSIST,
|
||||
@ -255,3 +255,28 @@ export const noAbilityTypeOverrideMoves: ReadonlySet<MoveId> = new Set([
|
||||
MoveId.TECHNO_BLAST,
|
||||
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,
|
||||
]);
|
||||
|
@ -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 { 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
|
||||
@ -18,3 +27,88 @@ export function isFieldTargeted(move: Move): boolean {
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
92
src/data/moves/pokemon-move.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -19,8 +19,8 @@ import i18next from "i18next";
|
||||
import type { IEggOptions } from "#app/data/egg";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { modifierTypes } from "#app/data/data-lists";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
|
@ -7,9 +7,10 @@ import {
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
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 { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/data/data-lists";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
@ -25,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { randInt } from "#app/utils/common";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import {
|
||||
applyModifierTypeToPlayerPokemon,
|
||||
catchPokemon,
|
||||
@ -37,6 +38,7 @@ import type HeldModifierConfig from "#app/@types/held-modifier-config";
|
||||
import type { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import i18next from "i18next";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/absoluteAvarice";
|
||||
@ -306,7 +308,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(MoveId.STUFF_CHEEKS),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
});
|
||||
|
||||
await transitionMysteryEncounterIntroVisuals(true, true, 500);
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
setEncounterExp,
|
||||
updatePlayerMoney,
|
||||
} 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 { SpeciesId } from "#enums/species-id";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
@ -12,7 +12,9 @@ import {
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
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 { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
@ -24,7 +24,7 @@ import { TrainerType } from "#enums/trainer-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import type { PlayerPokemon } 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 { MoveId } from "#enums/move-id";
|
||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
@ -38,7 +38,7 @@ import {
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { PokemonType } from "#enums/pokemon-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 {
|
||||
AttackTypeBoosterModifier,
|
||||
@ -50,7 +50,7 @@ import {
|
||||
import i18next from "i18next";
|
||||
import MoveInfoOverlay from "#app/ui/move-info-overlay";
|
||||
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 { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
|
@ -11,9 +11,9 @@ import {
|
||||
import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||
import { TrainerPartyCompoundTemplate } 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 { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
@ -37,11 +37,10 @@ import { UiMode } from "#enums/ui-mode";
|
||||
import i18next from "i18next";
|
||||
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { Ability } from "#app/data/abilities/ability-class";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
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 { EncounterAnim } from "#enums/encounter-anims";
|
||||
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 */
|
||||
const namespace = "mysteryEncounters/clowningAround";
|
||||
@ -139,7 +140,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
|
||||
// Generate random ability for Blacephalon from pool
|
||||
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 };
|
||||
|
||||
// Decide the random types for Blacephalon. They should not be the same.
|
||||
@ -209,19 +210,19 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY_2],
|
||||
move: new PokemonMove(MoveId.ROLE_PLAY),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.TAUNT),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER_2],
|
||||
move: new PokemonMove(MoveId.TAUNT),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
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 type { PlayerPokemon } 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 { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/data/data-lists";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
@ -39,6 +40,7 @@ import { PokeballType } from "#enums/pokeball";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import i18next from "i18next";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/dancingLessons";
|
||||
@ -213,7 +215,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.REVELATION_DANCE),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
});
|
||||
|
||||
await hideOricorioPokemon();
|
||||
|
@ -3,7 +3,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
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 type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
PreserveBerryModifier,
|
||||
} from "#app/modifier/modifier";
|
||||
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 type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { randSeedItem } from "#app/utils/common";
|
||||
|
@ -2,8 +2,8 @@ import {
|
||||
leaveEncounterWithoutBattle,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import type { ModifierTypeFunc } from "#app/@types/modifier-types";
|
||||
import { modifierTypes } from "#app/data/data-lists";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
@ -7,8 +7,9 @@ import {
|
||||
setEncounterExp,
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
generateModifierType,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
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 { Gender } from "#app/data/gender";
|
||||
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 { PokemonMove } from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
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 { BattlerTagType } from "#enums/battler-tag-type";
|
||||
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 { MoveUseMode } from "#enums/move-use-mode";
|
||||
import { allAbilities, modifierTypes } from "#app/data/data-lists";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/fieryFallout";
|
||||
@ -201,13 +201,13 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.FIRE_SPIN),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||
targets: [BattlerIndex.PLAYER_2],
|
||||
move: new PokemonMove(MoveId.FIRE_SPIN),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
);
|
||||
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
@ -246,7 +246,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
||||
// Burn applied
|
||||
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`);
|
||||
|
||||
// Also permanently change the burned Pokemon's ability to Heatproof
|
||||
|
@ -9,13 +9,10 @@ import {
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
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 {
|
||||
getPlayerModifierTypeOptions,
|
||||
ModifierPoolType,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
@ -13,7 +13,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import type { PlayerPokemon } 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 { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
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 { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
|
||||
import { addPokeballOpenParticles } from "#app/field/anims";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||
import { modifierTypes } from "#app/data/data-lists";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
@ -4,14 +4,11 @@ import {
|
||||
setEncounterRewards,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
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 type { ModifierTypeOption } from "#app/modifier/modifier-type";
|
||||
import {
|
||||
getPlayerModifierTypeOptions,
|
||||
ModifierPoolType,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
@ -33,7 +30,8 @@ import {
|
||||
} from "#app/utils/common";
|
||||
import type { PlayerPokemon } 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 {
|
||||
HiddenAbilityRateBoosterModifier,
|
||||
|
@ -11,7 +11,7 @@ import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encount
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
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_2_REQUIRED_MOVE = MoveId.FLY;
|
||||
|
@ -7,8 +7,8 @@ import { trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||
import { trainerPartyTemplates } from "#app/data/trainers/TrainerPartyTemplate";
|
||||
import { TrainerPartyCompoundTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { ModifierTier } from "#enums/modifier-tier";
|
||||
import { modifierTypes } from "#app/data/data-lists";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
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 { MoveId } from "#enums/move-id";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import type { PlayerPokemon } 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
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 { SpeciesId } from "#enums/species-id";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
@ -21,8 +21,9 @@ import {
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { AiType, PokemonMove } from "#app/field/pokemon";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { AiType } from "#enums/ai-type";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
@ -30,6 +31,7 @@ import { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/slumberingSnorlax";
|
||||
@ -136,7 +138,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.SNORE),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
});
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
},
|
||||
|
@ -23,7 +23,8 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
||||
import { BiomeId } from "#enums/biome-id";
|
||||
import { getBiomeKey } from "#app/field/arena";
|
||||
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 { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
|
@ -26,7 +26,7 @@ import { EggSourceType } from "#enums/egg-source-types";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
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 { getPokeballTintColor } from "#app/data/pokeball";
|
||||
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
generateModifierType,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
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 { globalScene } from "#app/global-scene";
|
||||
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 { Nature } from "#enums/nature";
|
||||
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 { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
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 { BerryType } from "#enums/berry-type";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/theStrongStuff";
|
||||
@ -214,13 +215,13 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.GASTRO_ACID),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.STEALTH_ROCK),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
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 { globalScene } from "#app/global-scene";
|
||||
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 { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
|
||||
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
|
||||
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||
import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
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 { 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
|
||||
pokemon.resetBattleAndWaveData();
|
||||
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
|
||||
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
||||
}
|
||||
|
||||
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
||||
|
@ -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 type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import {
|
||||
@ -12,7 +12,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
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 type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common";
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
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 { globalScene } from "#app/global-scene";
|
||||
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 { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
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 { MoveId } from "#enums/move-id";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounters/trashToTreasure";
|
||||
@ -207,13 +208,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(MoveId.TOXIC),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.ENEMY],
|
||||
move: new PokemonMove(MoveId.STOCKPILE),
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
},
|
||||
);
|
||||
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import type Pokemon 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 { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
@ -29,14 +29,14 @@ import {
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { SelfStatusMove } from "#app/data/moves/move";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
||||
import { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
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
|
||||
const pokemonMove = new PokemonMove(eggMove);
|
||||
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({
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [target],
|
||||
move: pokemonMove,
|
||||
ignorePp: true,
|
||||
useMode: MoveUseMode.IGNORE_PP,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import type { PlayerPokemon } 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 type PokemonSpecies 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 { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
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 {
|
||||
doPokemonTransformationSequence,
|
||||
@ -34,7 +34,7 @@ import {
|
||||
import { getLevelTotalExp } from "#app/data/exp";
|
||||
import { Stat } from "#enums/stat";
|
||||
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 { TrainerType } from "#enums/trainer-type";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
|
@ -2,14 +2,16 @@ import { globalScene } from "#app/global-scene";
|
||||
import { allAbilities } from "../data-lists";
|
||||
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
||||
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 { PokemonType } from "#enums/pokemon-type";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
|
||||
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 { MoveId } from "#enums/move-id";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
@ -270,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
|
||||
|
||||
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
|
||||
super();
|
||||
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
|
||||
this.requiredTimeOfDay = coerceArray(timeOfDay);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -292,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
||||
|
||||
constructor(weather: WeatherType | WeatherType[]) {
|
||||
super();
|
||||
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
|
||||
this.requiredWeather = coerceArray(weather);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -358,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
||||
constructor(heldItem: string | string[], minNumberOfItems = 1) {
|
||||
super();
|
||||
this.minNumberOfItems = minNumberOfItems;
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -424,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredSpecies = Array.isArray(species) ? species : [species];
|
||||
this.requiredSpecies = coerceArray(species);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -464,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredNature = Array.isArray(nature) ? nature : [nature];
|
||||
this.requiredNature = coerceArray(nature);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -502,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
|
||||
this.excludeFainted = excludeFainted;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredType = Array.isArray(type) ? type : [type];
|
||||
this.requiredType = coerceArray(type);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -556,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
|
||||
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
|
||||
this.requiredMoves = coerceArray(moves);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -607,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
|
||||
this.requiredMoves = coerceArray(learnableMove);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -663,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
|
||||
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
|
||||
this.requiredAbilities = coerceArray(abilities);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -708,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
|
||||
this.requiredStatusEffect = coerceArray(statusEffect);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -783,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
|
||||
this.requiredFormChangeItem = coerceArray(formChangeItem);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -841,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
|
||||
this.requiredEvolutionItem = coerceArray(evolutionItems);
|
||||
}
|
||||
|
||||
override meetsRequirement(): boolean {
|
||||
@ -906,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
@ -970,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
|
||||
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
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 { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
|
||||
import { capitalizeFirstLetter, coerceArray, isNullOrUndefined } from "#app/utils/common";
|
||||
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
|
||||
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
|
||||
@ -20,22 +21,22 @@ import {
|
||||
StatusEffectRequirement,
|
||||
WaveRangeRequirement,
|
||||
} 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 { MysteryEncounterMode } from "#enums/mystery-encounter-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 { Challenges } from "#enums/challenges";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { MoveUseMode } from "#enums/move-use-mode";
|
||||
|
||||
export interface EncounterStartOfBattleEffect {
|
||||
sourcePokemon?: Pokemon;
|
||||
sourceBattlerIndex?: BattlerIndex;
|
||||
targets: BattlerIndex[];
|
||||
move: PokemonMove;
|
||||
ignorePp: boolean;
|
||||
followUp?: boolean;
|
||||
useMode: MoveUseMode; // TODO: This should always be ignore PP...
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2;
|
||||
@ -253,7 +254,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
*/
|
||||
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[] = [];
|
||||
/**
|
||||
@ -716,7 +717,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
withAnimations(
|
||||
...encounterAnimations: EncounterAnim[]
|
||||
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
|
||||
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
|
||||
const animations = coerceArray(encounterAnimations);
|
||||
return Object.assign(this, { encounterAnimations: animations });
|
||||
}
|
||||
|
||||
@ -728,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
withDisallowedGameModes(
|
||||
...disallowedGameModes: GameModes[]
|
||||
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
||||
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
|
||||
const gameModes = coerceArray(disallowedGameModes);
|
||||
return Object.assign(this, { disallowedGameModes: gameModes });
|
||||
}
|
||||
|
||||
@ -740,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
withDisallowedChallenges(
|
||||
...disallowedChallenges: Challenges[]
|
||||
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
|
||||
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
|
||||
const challenges = coerceArray(disallowedChallenges);
|
||||
return Object.assign(this, { disallowedChallenges: challenges });
|
||||
}
|
||||
|
||||
|
@ -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 { 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 = [
|
||||
BiomeId.SEA,
|
||||
BiomeId.SEABED,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { isNullOrUndefined } from "#app/utils/common";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { coerceArray, isNullOrUndefined } from "#app/utils/common";
|
||||
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
|
||||
|
||||
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
|
||||
super();
|
||||
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
|
||||
this.requiredMoves = coerceArray(requiredMoves);
|
||||
|
||||
this.excludeLevelMoves = options.excludeLevelMoves ?? false;
|
||||
this.excludeTmMoves = options.excludeTmMoves ?? false;
|
||||
|
@ -1,36 +1,36 @@
|
||||
import type Battle from "#app/battle";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { BattleType } from "#enums/battle-type";
|
||||
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
|
||||
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
AVERAGE_ENCOUNTERS_PER_RUN_TARGET,
|
||||
WEIGHT_INCREMENT_ON_SPAWN_MISS,
|
||||
} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/constants";
|
||||
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 { 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 {
|
||||
getPartyLuckValue,
|
||||
ModifierPoolType,
|
||||
ModifierTypeGenerator,
|
||||
ModifierTypeOption,
|
||||
modifierTypes,
|
||||
regenerateModifierPoolThresholds,
|
||||
} 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 { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
|
||||
import { PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
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 { BiomeId } from "#enums/biome-id";
|
||||
import type { TrainerType } from "#enums/trainer-type";
|
||||
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 { Nature } from "#enums/nature";
|
||||
import type { MoveId } from "#enums/move-id";
|
||||
@ -448,7 +448,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
* @param moves
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
@ -791,7 +791,7 @@ export function setEncounterRewards(
|
||||
* @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) {
|
||||
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
|
||||
const participantIds = coerceArray(participantId);
|
||||
|
||||
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
|
||||
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));
|
||||
@ -973,33 +973,8 @@ export function handleMysteryEncounterBattleStartEffects() {
|
||||
) {
|
||||
const effects = encounter.startOfBattleEffects;
|
||||
effects.forEach(effect => {
|
||||
let source: EnemyPokemon | Pokemon;
|
||||
if (effect.sourcePokemon) {
|
||||
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,
|
||||
);
|
||||
const source = effect.sourcePokemon ?? globalScene.getField()[effect.sourceBattlerIndex ?? 0];
|
||||
globalScene.phaseManager.pushNew("MovePhase", source, effect.targets, effect.move, effect.useMode);
|
||||
});
|
||||
|
||||
// Pseudo turn end phase to reset flinch states, Endure, etc.
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
} from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
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 type { PermanentStat } from "#enums/stat";
|
||||
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
|
||||
|
97
src/data/phase-priority-queue.ts
Normal 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;
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
|
||||
import { NumberHolder } from "#app/utils/common";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import i18next from "i18next";
|
||||
@ -94,7 +93,7 @@ export function getCriticalCaptureChance(modifiedCatchRate: number): number {
|
||||
}
|
||||
const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr);
|
||||
const catchingCharmMultiplier = new NumberHolder(1);
|
||||
globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier);
|
||||
globalScene.findModifier(m => m.is("CriticalCatchChanceBoosterModifier"))?.apply(catchingCharmMultiplier);
|
||||
const dexMultiplier =
|
||||
globalScene.gameMode.isDaily || dexCount > 800
|
||||
? 2.5
|
||||
|
@ -1,139 +1,30 @@
|
||||
import { PokemonFormChangeItemModifier } from "../modifier/modifier";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { allMoves } from "./data-lists";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import type { Constructor, nil } from "#app/utils/common";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-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 { Challenges } from "#app/enums/challenges";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
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, // TODO: Find a potential use for this
|
||||
}
|
||||
import { FormChangeItem } from "#enums/form-change-item";
|
||||
import {
|
||||
MeloettaFormChangePostMoveTrigger,
|
||||
SpeciesDefaultFormMatchTrigger,
|
||||
SpeciesFormChangeAbilityTrigger,
|
||||
SpeciesFormChangeActiveTrigger,
|
||||
SpeciesFormChangeCompoundTrigger,
|
||||
SpeciesFormChangeItemTrigger,
|
||||
SpeciesFormChangeLapseTeraTrigger,
|
||||
SpeciesFormChangeManualTrigger,
|
||||
SpeciesFormChangeMoveLearnedTrigger,
|
||||
SpeciesFormChangePreMoveTrigger,
|
||||
SpeciesFormChangeRevertWeatherFormTrigger,
|
||||
SpeciesFormChangeTeraTrigger,
|
||||
type SpeciesFormChangeTrigger,
|
||||
SpeciesFormChangeWeatherTrigger,
|
||||
} from "./pokemon-forms/form-change-triggers";
|
||||
|
||||
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
|
||||
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.
|
||||
* Used for fusion forms such as Kyurem and Necrozma.
|
||||
|
345
src/data/pokemon-forms/form-change-triggers.ts
Normal 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 });
|
||||
}
|
@ -7,7 +7,8 @@ import i18next from "i18next";
|
||||
import type { AnySound } from "#app/battle-scene";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
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 {
|
||||
isNullOrUndefined,
|
||||
capitalizeString,
|
||||
@ -83,13 +84,14 @@ export const normalForm: SpeciesId[] = [
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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)) {
|
||||
// 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)];
|
||||
}
|
||||
if (species >= 2000) {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import type Move from "./moves/move";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { ProtectAttr } from "./moves/move";
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import type { BattlerIndex } from "#enums/battler-index";
|
||||
import i18next from "i18next";
|
||||
|
||||
export enum TerrainType {
|
||||
@ -55,7 +54,7 @@ export class Terrain {
|
||||
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
|
||||
switch (this.terrainType) {
|
||||
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
|
||||
return (
|
||||
move.getPriority(user) > 0 &&
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { startingWave } from "#app/starting-wave";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
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";
|
||||
|
||||
export class TrainerPartyTemplate {
|
||||
|
@ -1,12 +1,19 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common";
|
||||
import { modifierTypes } from "../data-lists";
|
||||
import { PokemonMove } from "../moves/pokemon-move";
|
||||
import {
|
||||
toReadableString,
|
||||
isNullOrUndefined,
|
||||
randSeedItem,
|
||||
randSeedInt,
|
||||
coerceArray,
|
||||
randSeedIntRange,
|
||||
} from "#app/utils/common";
|
||||
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { tmSpecies } from "#app/data/balance/tms";
|
||||
import { doubleBattleDialogue } from "#app/data/dialogue";
|
||||
import { TrainerVariant } from "#app/field/trainer";
|
||||
import { doubleBattleDialogue } from "../double-battle-dialogue";
|
||||
import { TrainerVariant } from "#enums/trainer-variant";
|
||||
import { getIsInitialized, initI18n } from "#app/plugins/i18n";
|
||||
import i18next from "i18next";
|
||||
import { Gender } from "#app/data/gender";
|
||||
@ -37,7 +44,7 @@ import { timedEventManager } from "#app/global-event-manager";
|
||||
// Type imports
|
||||
import type { PokemonSpeciesFilter } 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 { EvilTeam } from "./evil-admin-trainer-pools";
|
||||
import type {
|
||||
@ -554,10 +561,7 @@ export class TrainerConfig {
|
||||
this.speciesPools = evilAdminTrainerPools[poolName];
|
||||
|
||||
signatureSpecies.forEach((speciesPool, s) => {
|
||||
if (!Array.isArray(speciesPool)) {
|
||||
speciesPool = [speciesPool];
|
||||
}
|
||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||
});
|
||||
|
||||
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
|
||||
@ -620,10 +624,7 @@ export class TrainerConfig {
|
||||
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
|
||||
}
|
||||
signatureSpecies.forEach((speciesPool, s) => {
|
||||
if (!Array.isArray(speciesPool)) {
|
||||
speciesPool = [speciesPool];
|
||||
}
|
||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||
});
|
||||
if (!isNullOrUndefined(specialtyType)) {
|
||||
this.setSpeciesFilter(p => p.isOfType(specialtyType));
|
||||
@ -668,12 +669,8 @@ export class TrainerConfig {
|
||||
|
||||
// Set up party members with their corresponding species.
|
||||
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.
|
||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
|
||||
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
|
||||
});
|
||||
|
||||
// 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.
|
||||
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.
|
||||
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.
|
||||
|
@ -4,14 +4,13 @@ import { getPokemonNameWithAffix } from "../messages";
|
||||
import type Pokemon from "../field/pokemon";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import type Move from "./moves/move";
|
||||
import { AttackMove } from "./moves/move";
|
||||
import { randSeedInt } from "#app/utils/common";
|
||||
import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
|
||||
import { TerrainType, getTerrainName } from "./terrain";
|
||||
import i18next from "i18next";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type { Arena } from "#app/field/arena";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import type { SuppressWeatherEffectAbAttr } from "./abilities/ability";
|
||||
|
||||
export class Weather {
|
||||
public weatherType: WeatherType;
|
||||
@ -95,9 +94,9 @@ export class Weather {
|
||||
|
||||
switch (this.weatherType) {
|
||||
case WeatherType.HARSH_SUN:
|
||||
return move instanceof AttackMove && moveType === PokemonType.WATER;
|
||||
return move.is("AttackMove") && moveType === PokemonType.WATER;
|
||||
case WeatherType.HEAVY_RAIN:
|
||||
return move instanceof AttackMove && moveType === PokemonType.FIRE;
|
||||
return move.is("AttackMove") && moveType === PokemonType.FIRE;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -109,10 +108,10 @@ export class Weather {
|
||||
for (const pokemon of field) {
|
||||
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon
|
||||
.getAbility()
|
||||
.getAttrs(SuppressWeatherEffectAbAttr)[0];
|
||||
.getAttrs("SuppressWeatherEffectAbAttr")[0];
|
||||
if (!suppressWeatherEffectAbAttr) {
|
||||
suppressWeatherEffectAbAttr = pokemon.hasPassive()
|
||||
? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]
|
||||
? pokemon.getPassiveAbility().getAttrs("SuppressWeatherEffectAbAttr")[0]
|
||||
: null;
|
||||
}
|
||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {
|
||||
|
11
src/enums/ability-attr.ts
Normal 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
@ -0,0 +1,5 @@
|
||||
export enum AiType {
|
||||
RANDOM,
|
||||
SMART_RANDOM,
|
||||
SMART
|
||||
}
|
5
src/enums/arena-tag-side.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum ArenaTagSide {
|
||||
BOTH,
|
||||
PLAYER,
|
||||
ENEMY
|
||||
}
|
7
src/enums/battler-index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export enum BattlerIndex {
|
||||
ATTACKER = -1,
|
||||
PLAYER,
|
||||
PLAYER_2,
|
||||
ENEMY,
|
||||
ENEMY_2
|
||||
}
|
37
src/enums/battler-tag-lapse-type.ts
Normal 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,
|
||||
}
|
69
src/enums/challenge-type.ts
Normal 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
@ -0,0 +1,7 @@
|
||||
export enum Command {
|
||||
FIGHT = 0,
|
||||
BALL,
|
||||
POKEMON,
|
||||
RUN,
|
||||
TERA
|
||||
}
|
11
src/enums/dex-attr.ts
Normal 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];
|
6
src/enums/dynamic-phase-type.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Enum representation of the phase types held by implementations of {@linkcode PhasePriorityQueue}
|
||||
*/
|
||||
export enum DynamicPhaseType {
|
||||
POST_SUMMON
|
||||
}
|
5
src/enums/field-position.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum FieldPosition {
|
||||
CENTER,
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
119
src/enums/form-change-item.ts
Normal 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
@ -0,0 +1,7 @@
|
||||
export enum GameModes {
|
||||
CLASSIC,
|
||||
ENDLESS,
|
||||
SPLICED_ENDLESS,
|
||||
DAILY,
|
||||
CHALLENGE
|
||||
}
|