Merge branch 'beta' into evil-team-monogen

This commit is contained in:
Madmadness65 2024-10-04 00:42:35 -05:00 committed by GitHub
commit 62042bf826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1430 changed files with 10016 additions and 130920 deletions

View File

@ -1,6 +1,6 @@
VITE_BYPASS_LOGIN=0 VITE_BYPASS_LOGIN=0
VITE_BYPASS_TUTORIAL=0 VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=https://api.beta.pokerogue.net VITE_SERVER_URL=https://apibeta.pokerogue.net
VITE_DISCORD_CLIENT_ID=1248062921129459756 VITE_DISCORD_CLIENT_ID=1248062921129459756
VITE_GOOGLE_CLIENT_ID=955345393540-2k6lfftf0fdnb0krqmpthjnqavfvvf73.apps.googleusercontent.com VITE_GOOGLE_CLIENT_ID=955345393540-2k6lfftf0fdnb0krqmpthjnqavfvvf73.apps.googleusercontent.com
VITE_I18N_DEBUG=1 VITE_I18N_DEBUG=0

View File

@ -3,5 +3,5 @@ VITE_BYPASS_TUTORIAL=0
VITE_SERVER_URL=http://localhost:8001 VITE_SERVER_URL=http://localhost:8001
VITE_DISCORD_CLIENT_ID=1234567890 VITE_DISCORD_CLIENT_ID=1234567890
VITE_GOOGLE_CLIENT_ID=1234567890 VITE_GOOGLE_CLIENT_ID=1234567890
VITE_I18N_DEBUG=1 VITE_I18N_DEBUG=0
VITE_PORT=8000 VITE_PORT=8000

38
.github/CODEOWNERS vendored
View File

@ -5,41 +5,3 @@
# github actions/templates etc. - Dev Leads # github actions/templates etc. - Dev Leads
/.github @pagefaultgames/dev-leads /.github @pagefaultgames/dev-leads
# --- Translations ---
# all translations - Translation Leads
/src/locales @pagefaultgames/translation-leads
# Catalan (Spain/Spanish)
/src/locales/ca_ES @pagefaultgames/catalan-translation-team
# German
/src/locales/de @pagefaultgames/german-translation-team
# English
/src/locales/en @pagefaultgames/english-translation-team
# Spanish
/src/locales/es @pagefaultgames/spanish-translation-team
# French
/src/locales/fr @pagefaultgames/french-translation-team
# Italian
/src/locales/it @pagefaultgames/italian-translation-team
# Japenese
/src/locales/ja @pagefaultgames/japanese-translation-team
# Korean
/src/locales/ko @pagefaultgames/korean-translation-team
# Brasilian (Brasil/Portuguese)
/src/locales/pt_BR @pagefaultgames/portuguese_br-translation-team
# Chinese (simplified)
/src/locales/zh_CN @pagefaultgames/chinese_simplified-translation-team
# Chinese (traditional)
/src/locales/zh_TW @pagefaultgames/chinese_traditional-translation-team

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
github: patapancakes github: pagefaultgames

View File

@ -11,6 +11,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
submodules: 'recursive'
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "20"

View File

@ -10,6 +10,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
submodules: 'recursive'
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: "20" node-version: "20"

View File

@ -22,6 +22,8 @@ jobs:
steps: steps:
- name: Check out Git repository # Step to check out the repository - name: Check out Git repository # Step to check out the repository
uses: actions/checkout@v4 # Use the checkout action version 4 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 - name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4 uses: actions/setup-node@v4 # Use the setup-node action version 4

View File

@ -26,6 +26,7 @@ jobs:
- name: Checkout repository for Typedoc - name: Checkout repository for Typedoc
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
submodules: 'recursive'
path: pokerogue_docs path: pokerogue_docs
- name: Install OS package - name: Install OS package

View File

@ -20,6 +20,8 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:

View File

@ -22,6 +22,7 @@ jobs:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: 'recursive'
path: tests-action path: tests-action
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "public/locales"]
path = public/locales
url = https://github.com/pagefaultgames/pokerogue-locales

View File

@ -24,6 +24,9 @@ We're using ESLint as our common linter and formatter. It will run automatically
**How do I test a new _______?** **How do I test a new _______?**
- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing - In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing
**How do I retrieve the translations?**
- The translations were moved to the [dedicated translation repository](https://github.com/pagefaultgames/pokerogue-locales) and are now applied as a submodule in this project.
- The command to retrieve the translations is `git submodule update --init --recursive`. If you still struggle to get it working, please reach out to #dev-corner channel in Discord.
## 🪧 To Do ## 🪧 To Do
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us! Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!

View File

@ -1,9 +1,7 @@
/** /**
* This script creates a test boilerplate file for a move or ability. * This script creates a test boilerplate file in the appropriate
* @param {string} type - The type of test to create. Either "move", "ability", * directory based on the type selected.
* or "item". * @example npm run create-test
* @param {string} fileName - The name of the file to create.
* @example npm run create-test move tackle
*/ */
import fs from "fs"; import fs from "fs";
@ -34,7 +32,7 @@ async function promptTestType() {
console.log("Exiting..."); console.log("Exiting...");
return process.exit(); return process.exit();
} else if (!typeChoices.includes(typeAnswer.selectedOption)) { } else if (!typeChoices.includes(typeAnswer.selectedOption)) {
console.error('Please provide a valid type ("move", "ability", or "item")!'); console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
return await promptTestType(); return await promptTestType();
} }
@ -102,7 +100,7 @@ async function runInteractive() {
description = `Mystery Encounter - ${formattedName}`; description = `Mystery Encounter - ${formattedName}`;
break; break;
default: default:
console.error('Invalid type. Please use "move", "ability", or "item".'); console.error(`Invalid type. Please use one of the following: ${typeChoices.join(", ")}.`);
process.exit(1); process.exit(1);
} }

View File

@ -41,6 +41,11 @@ export default [
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords "keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
"comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json "import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
"array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets
"object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers
"computed-property-spacing": ["error", "never" ], // Enforces consistent spacing inside computed property brackets
"space-infix-ops": ["error", { "int32Hint": false }], // Enforces spacing around infix operators
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines
} }
} }
] ]

View File

@ -14,3 +14,8 @@ pre-push:
eslint: eslint:
glob: "*.{js,ts,jsx,tsx}" glob: "*.{js,ts,jsx,tsx}"
run: npx eslint --fix {push_files} run: npx eslint --fix {push_files}
post-merge:
commands:
update-submodules:
run: git submodule update --init --recursive

51
package-lock.json generated
View File

@ -12,6 +12,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"i18next": "^23.11.1", "i18next": "^23.11.1",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",
"i18next-http-backend": "^2.6.1",
"i18next-korean-postposition-processor": "^1.0.0", "i18next-korean-postposition-processor": "^1.0.0",
"json-stable-stringify": "^1.1.0", "json-stable-stringify": "^1.1.0",
"phaser": "^3.70.0", "phaser": "^3.70.0",
@ -37,7 +38,7 @@
"typedoc": "^0.26.4", "typedoc": "^0.26.4",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"typescript-eslint": "^8.0.0-alpha.54", "typescript-eslint": "^8.0.0-alpha.54",
"vite": "^5.3.5", "vite": "^5.4.8",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.0.4", "vitest": "^2.0.4",
"vitest-canvas-mock": "^0.3.3" "vitest-canvas-mock": "^0.3.3"
@ -3650,7 +3651,6 @@
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -4004,9 +4004,10 @@
} }
}, },
"node_modules/i18next-http-backend": { "node_modules/i18next-http-backend": {
"version": "2.5.2", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz", "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.1.tgz",
"integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==", "integrity": "sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==",
"license": "MIT",
"dependencies": { "dependencies": {
"cross-fetch": "4.0.0" "cross-fetch": "4.0.0"
} }
@ -5042,7 +5043,6 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@ -5441,9 +5441,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.39", "version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -5459,16 +5459,21 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.0.1", "picocolors": "^1.1.0",
"source-map-js": "^1.2.0" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss/node_modules/picocolors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
"dev": true
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -5865,11 +5870,10 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -6469,15 +6473,14 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.3.5", "version": "5.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.39", "postcss": "^8.4.43",
"rollup": "^4.13.0" "rollup": "^4.20.0"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@ -6496,6 +6499,7 @@
"less": "*", "less": "*",
"lightningcss": "^1.21.0", "lightningcss": "^1.21.0",
"sass": "*", "sass": "*",
"sass-embedded": "*",
"stylus": "*", "stylus": "*",
"sugarss": "*", "sugarss": "*",
"terser": "^5.4.0" "terser": "^5.4.0"
@ -6513,6 +6517,9 @@
"sass": { "sass": {
"optional": true "optional": true
}, },
"sass-embedded": {
"optional": true
},
"stylus": { "stylus": {
"optional": true "optional": true
}, },

View File

@ -19,7 +19,8 @@
"docs": "typedoc", "docs": "typedoc",
"depcruise": "depcruise src", "depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js" "create-test": "node ./create-test-boilerplate.js",
"postinstall": "npx lefthook install && npx lefthook run post-merge"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
@ -41,7 +42,7 @@
"typedoc": "^0.26.4", "typedoc": "^0.26.4",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"typescript-eslint": "^8.0.0-alpha.54", "typescript-eslint": "^8.0.0-alpha.54",
"vite": "^5.3.5", "vite": "^5.4.8",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.0.4", "vitest": "^2.0.4",
"vitest-canvas-mock": "^0.3.3" "vitest-canvas-mock": "^0.3.3"
@ -51,6 +52,7 @@
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"i18next": "^23.11.1", "i18next": "^23.11.1",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",
"i18next-http-backend": "^2.6.1",
"i18next-korean-postposition-processor": "^1.0.0", "i18next-korean-postposition-processor": "^1.0.0",
"json-stable-stringify": "^1.1.0", "json-stable-stringify": "^1.1.0",
"phaser": "^3.70.0", "phaser": "^3.70.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

1
public/locales Submodule

@ -0,0 +1 @@
Subproject commit 3ccef8472dd7cc7c362538489954cb8fdad27e5f

View File

@ -1,18 +1,8 @@
import { type enConfig } from "#app/locales/en/config";
import { TOptions } from "i18next"; import { TOptions } from "i18next";
//TODO: this needs to be type properly in the future
// Module declared to make referencing keys in the localization files type-safe. // Module declared to make referencing keys in the localization files type-safe.
declare module "i18next" { declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "menu", // needed here as well for typedoc
resources: typeof enConfig
}
interface TFunction { interface TFunction {
( (key: string | string[], options?: TOptions & Record<string, unknown>): string;
key: string | string[],
options?: TOptions & Record<string, unknown>
): string;
} }
} }

View File

@ -25,7 +25,7 @@ import { trainerConfigs, TrainerSlot } from "./data/trainer-config";
import Trainer, { TrainerVariant } from "./field/trainer"; import Trainer, { TrainerVariant } from "./field/trainer";
import TrainerData from "./system/trainer-data"; import TrainerData from "./system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { pokemonPrevolutions } from "./data/pokemon-evolutions"; import { pokemonPrevolutions } from "./data/balance/pokemon-evolutions";
import PokeballTray from "./ui/pokeball-tray"; import PokeballTray from "./ui/pokeball-tray";
import InvertPostFX from "./pipelines/invert"; import InvertPostFX from "./pipelines/invert";
import { Achv, achvs, ModifierAchv, MoneyAchv } from "./system/achv"; import { Achv, achvs, ModifierAchv, MoneyAchv } from "./system/achv";
@ -42,7 +42,7 @@ import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler"
import CharSprite from "./ui/char-sprite"; import CharSprite from "./ui/char-sprite";
import DamageNumberHandler from "./field/damage-number-handler"; import DamageNumberHandler from "./field/damage-number-handler";
import PokemonInfoContainer from "./ui/pokemon-info-container"; import PokemonInfoContainer from "./ui/pokemon-info-container";
import { biomeDepths, getBiomeName } from "./data/biomes"; import { biomeDepths, getBiomeName } from "./data/balance/biomes";
import { SceneBase } from "./scene-base"; import { SceneBase } from "./scene-base";
import CandyBar from "./ui/candy-bar"; import CandyBar from "./ui/candy-bar";
import { Variant, variantData } from "./data/variant"; import { Variant, variantData } from "./data/variant";
@ -1040,10 +1040,6 @@ export default class BattleScene extends SceneBase {
this.gameMode = getGameMode(GameModes.CLASSIC); this.gameMode = getGameMode(GameModes.CLASSIC);
this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24));
console.log("Seed:", this.seed);
this.resetSeed(); // Properly resets RNG after saving and quitting a session
this.disableMenu = false; this.disableMenu = false;
this.score = 0; this.score = 0;
@ -1078,6 +1074,12 @@ export default class BattleScene extends SceneBase {
//@ts-ignore - allowing `null` for currentBattle causes a lot of trouble //@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
this.currentBattle = null; // TODO: resolve ts-ignore this.currentBattle = null; // TODO: resolve ts-ignore
// Reset RNG after end of game or save & quit.
// This needs to happen after clearing this.currentBattle or the seed will be affected by the last wave played
this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24));
console.log("Seed:", this.seed);
this.resetSeed();
this.biomeWaveText.setText(startingWave.toString()); this.biomeWaveText.setText(startingWave.toString());
this.biomeWaveText.setVisible(false); this.biomeWaveText.setVisible(false);
@ -2443,7 +2445,10 @@ export default class BattleScene extends SceneBase {
} }
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual, this)) { if ((modifier as PersistentModifier).add(this.modifiers, !!virtual, this)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) {
success = modifier.apply([ this.getPokemonById(modifier.pokemonId), true ]); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) {
success = modifier.apply(pokemon, true);
}
} }
if (playSound && !this.sound.get(soundName)) { if (playSound && !this.sound.get(soundName)) {
this.playSound(soundName); this.playSound(soundName);
@ -2470,7 +2475,7 @@ export default class BattleScene extends SceneBase {
for (const p in this.party) { for (const p in this.party) {
const pokemon = this.party[p]; const pokemon = this.party[p];
const args: any[] = [ pokemon ]; const args: unknown[] = [];
if (modifier instanceof PokemonHpRestoreModifier) { if (modifier instanceof PokemonHpRestoreModifier) {
if (!(modifier as PokemonHpRestoreModifier).fainted) { if (!(modifier as PokemonHpRestoreModifier).fainted) {
const hpRestoreMultiplier = new Utils.IntegerHolder(1); const hpRestoreMultiplier = new Utils.IntegerHolder(1);
@ -2483,8 +2488,8 @@ export default class BattleScene extends SceneBase {
args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon);
} }
if (modifier.shouldApply(args)) { if (modifier.shouldApply(pokemon, ...args)) {
const result = modifier.apply(args); const result = modifier.apply(pokemon, ...args);
if (result instanceof Promise) { if (result instanceof Promise) {
modifierPromises.push(result.then(s => success ||= s)); modifierPromises.push(result.then(s => success ||= s));
} else { } else {
@ -2496,8 +2501,8 @@ export default class BattleScene extends SceneBase {
return Promise.allSettled([ this.party.map(p => p.updateInfo(instant)), ...modifierPromises ]).then(() => resolve(success)); return Promise.allSettled([ this.party.map(p => p.updateInfo(instant)), ...modifierPromises ]).then(() => resolve(success));
} else { } else {
const args = [ this ]; const args = [ this ];
if (modifier.shouldApply(args)) { if (modifier.shouldApply(...args)) {
const result = modifier.apply(args); const result = modifier.apply(...args);
if (result instanceof Promise) { if (result instanceof Promise) {
return result.then(success => resolve(success)); return result.then(success => resolve(success));
} else { } else {
@ -2519,7 +2524,10 @@ export default class BattleScene extends SceneBase {
} }
if ((modifier as PersistentModifier).add(this.enemyModifiers, false, this)) { if ((modifier as PersistentModifier).add(this.enemyModifiers, false, this)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) {
modifier.apply([ this.getPokemonById(modifier.pokemonId), true ]); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) {
modifier.apply(pokemon, true);
}
} }
for (const rm of modifiersToRemove) { for (const rm of modifiersToRemove) {
this.removeModifier(rm, true); this.removeModifier(rm, true);
@ -2753,7 +2761,10 @@ export default class BattleScene extends SceneBase {
if (modifierIndex > -1) { if (modifierIndex > -1) {
modifiers.splice(modifierIndex, 1); modifiers.splice(modifierIndex, 1);
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) {
modifier.apply([ this.getPokemonById(modifier.pokemonId), false ]); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) {
modifier.apply(pokemon, false);
}
} }
return true; return true;
} }
@ -2771,16 +2782,36 @@ export default class BattleScene extends SceneBase {
return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType); return (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType);
} }
findModifiers(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier[] { /**
return (player ? this.modifiers : this.enemyModifiers).filter(m => (modifierFilter as ModifierPredicate)(m)); * Get all of the modifiers that pass the `modifierFilter` function
* @param modifierFilter The function used to filter a target's modifiers
* @param isPlayer Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @returns the list of all modifiers that passed the `modifierFilter` function
*/
findModifiers(modifierFilter: ModifierPredicate, isPlayer: boolean = true): PersistentModifier[] {
return (isPlayer ? this.modifiers : this.enemyModifiers).filter(modifierFilter);
} }
/**
* Find the first modifier that pass the `modifierFilter` function
* @param modifierFilter The function used to filter a target's modifiers
* @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @returns the first modifier that passed the `modifierFilter` function; `undefined` if none passed
*/
findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier | undefined { findModifier(modifierFilter: ModifierPredicate, player: boolean = true): PersistentModifier | undefined {
return (player ? this.modifiers : this.enemyModifiers).find(m => (modifierFilter as ModifierPredicate)(m)); return (player ? this.modifiers : this.enemyModifiers).find(modifierFilter);
} }
applyShuffledModifiers(scene: BattleScene, modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier[] { /**
let modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); * Apply all modifiers that match `modifierType` in a random order
* @param scene {@linkcode BattleScene} used to randomize the order of modifiers
* @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier}
* @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @param ...args The list of arguments needed to invoke `modifierType.apply`
* @returns the list of all modifiers that matched `modifierType` and were applied.
*/
applyShuffledModifiers<T extends PersistentModifier>(scene: BattleScene, modifierType: Constructor<T>, player: boolean = true, ...args: Parameters<T["apply"]>): T[] {
let modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args));
scene.executeWithSeedOffset(() => { scene.executeWithSeedOffset(() => {
const shuffleModifiers = mods => { const shuffleModifiers = mods => {
if (mods.length < 1) { if (mods.length < 1) {
@ -2794,15 +2825,23 @@ export default class BattleScene extends SceneBase {
return this.applyModifiersInternal(modifiers, player, args); return this.applyModifiersInternal(modifiers, player, args);
} }
applyModifiers(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier[] { /**
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); * Apply all modifiers that match `modifierType`
* @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier}
* @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @param ...args The list of arguments needed to invoke `modifierType.apply`
* @returns the list of all modifiers that matched `modifierType` and were applied.
*/
applyModifiers<T extends PersistentModifier>(modifierType: Constructor<T>, player: boolean = true, ...args: Parameters<T["apply"]>): T[] {
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args));
return this.applyModifiersInternal(modifiers, player, args); return this.applyModifiersInternal(modifiers, player, args);
} }
applyModifiersInternal(modifiers: PersistentModifier[], player: boolean, args: any[]): PersistentModifier[] { /** Helper function to apply all passed modifiers */
const appliedModifiers: PersistentModifier[] = []; applyModifiersInternal<T extends PersistentModifier>(modifiers: T[], player: boolean, args: Parameters<T["apply"]>): T[] {
const appliedModifiers: T[] = [];
for (const modifier of modifiers) { for (const modifier of modifiers) {
if (modifier.apply(args)) { if (modifier.apply(...args)) {
console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); console.log("Applied", modifier.type.name, !player ? "(enemy)" : "");
appliedModifiers.push(modifier); appliedModifiers.push(modifier);
} }
@ -2811,10 +2850,17 @@ export default class BattleScene extends SceneBase {
return appliedModifiers; return appliedModifiers;
} }
applyModifier(modifierType: Constructor<Modifier>, player: boolean = true, ...args: any[]): PersistentModifier | null { /**
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter(m => m instanceof modifierType && m.shouldApply(args)); * Apply the first modifier that matches `modifierType`
* @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier}
* @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true`
* @param ...args The list of arguments needed to invoke `modifierType.apply`
* @returns the first modifier that matches `modifierType` and was applied; return `null` if none matched
*/
applyModifier<T extends PersistentModifier>(modifierType: Constructor<T>, player: boolean = true, ...args: Parameters<T["apply"]>): T | null {
const modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args));
for (const modifier of modifiers) { for (const modifier of modifiers) {
if (modifier.apply(args)) { if (modifier.apply(...args)) {
console.log("Applied", modifier.type.name, !player ? "(enemy)" : ""); console.log("Applied", modifier.type.name, !player ? "(enemy)" : "");
return modifier; return modifier;
} }
@ -2880,7 +2926,7 @@ export default class BattleScene extends SceneBase {
} }
} }
validateAchv(achv: Achv, args?: any[]): boolean { validateAchv(achv: Achv, args?: unknown[]): boolean {
if (!this.gameData.achvUnlocks.hasOwnProperty(achv.id) && achv.validate(this, args)) { if (!this.gameData.achvUnlocks.hasOwnProperty(achv.id) && achv.validate(this, args)) {
this.gameData.achvUnlocks[achv.id] = new Date().getTime(); this.gameData.achvUnlocks[achv.id] = new Date().getTime();
this.ui.achvBar.showAchv(achv); this.ui.achvBar.showAchv(achv);
@ -2893,7 +2939,7 @@ export default class BattleScene extends SceneBase {
return false; return false;
} }
validateVoucher(voucher: Voucher, args?: any[]): boolean { validateVoucher(voucher: Voucher, args?: unknown[]): boolean {
if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(this, args)) { if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(this, args)) {
this.gameData.voucherUnlocks[voucher.id] = new Date().getTime(); this.gameData.voucherUnlocks[voucher.id] = new Date().getTime();
this.ui.achvBar.showAchv(voucher); this.ui.achvBar.showAchv(voucher);
@ -3091,11 +3137,6 @@ export default class BattleScene extends SceneBase {
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean {
const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves();
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) {
// If ME type is already defined in session data, no need to roll RNG check
if (!isNullOrUndefined(sessionDataEncounterType)) {
return true;
}
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;

View File

@ -497,7 +497,7 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], rand
} }
/* 1/3 chance for evil team grunts to be double battles */ /* 1/3 chance for evil team grunts to be double battles */
const evilTeamGrunts = [TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT]; const evilTeamGrunts = [ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]); const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]);
if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) { if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) {

View File

@ -4,7 +4,7 @@ import { Constructor } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather"; import { Weather, WeatherType } from "./weather";
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags"; import { BattlerTag, GroundedTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender"; import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move";
@ -123,6 +123,7 @@ export class Ability implements Localizable {
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>; type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
type AbAttrCondition = (pokemon: Pokemon) => boolean; type AbAttrCondition = (pokemon: Pokemon) => boolean;
// TODO: Can this be improved?
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean; type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
@ -518,6 +519,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
if (pokemon.isFullHp() && typeMultiplier.value > 0.5) { if (pokemon.isFullHp() && typeMultiplier.value > 0.5) {
typeMultiplier.value = 0.5; typeMultiplier.value = 0.5;
pokemon.turnData.moveEffectiveness = 0.5;
return true; return true;
} }
return false; return false;
@ -536,53 +538,6 @@ export class PostDefendAbAttr extends AbAttr {
} }
} }
/**
* Applies the effects of Gulp Missile when the user is hit by an attack.
* @extends PostDefendAbAttr
*/
export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
constructor() {
super(true);
}
/**
* Damages the attacker and triggers the secondary effect based on the form or the BattlerTagType.
* @param {Pokemon} pokemon - The defending Pokemon.
* @param passive - n/a
* @param {Pokemon} attacker - The attacking Pokemon.
* @param {Move} move - The move being used.
* @param {HitResult} hitResult - n/a
* @param {any[]} args - n/a
* @returns Whether the effects of the ability are applied.
*/
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
const battlerTag = pokemon.getTag(GulpMissileTag);
if (!battlerTag || move.category === MoveCategory.STATUS || pokemon.getTag(SemiInvulnerableTag)) {
return false;
}
if (simulated) {
return true;
}
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), HitResult.OTHER);
}
if (battlerTag.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ Stat.DEF ], -1));
} else {
attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon);
}
pokemon.removeTag(battlerTag.tagType);
return true;
}
}
export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const attackPriority = new Utils.IntegerHolder(move.priority); const attackPriority = new Utils.IntegerHolder(move.priority);
@ -2479,7 +2434,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.setStatStage(s, target.getStatStage(s)); pokemon.setStatStage(s, target.getStatStage(s));
} }
pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m!.moveId, m!.ppUsed, m!.ppUp)); // TODO: are those bangs correct? pokemon.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId ?? Moves.NONE, m?.ppUsed, m?.ppUp));
pokemon.summonData.types = target.getTypes(); pokemon.summonData.types = target.getTypes();
@ -3199,12 +3154,12 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
} else if (move?.getMove().power === -1) { } else if (move?.getMove().power === -1) {
movePower = 80; movePower = 80;
} else { } else {
movePower = move!.getMove().power; // TODO: is this bang correct? movePower = move?.getMove().power ?? 0;
} }
if (movePower > maxPowerSeen) { if (movePower > maxPowerSeen) {
maxPowerSeen = movePower; maxPowerSeen = movePower;
maxMove = move!.getName(); // TODO: is this bang correct? maxMove = move?.getName() ?? "";
} }
} }
} }
@ -5210,8 +5165,7 @@ export function initAbilities() {
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonMoldBreaker", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonMoldBreaker", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(MoveAbilityBypassAbAttr), .attr(MoveAbilityBypassAbAttr),
new Ability(Abilities.SUPER_LUCK, 4) new Ability(Abilities.SUPER_LUCK, 4)
.attr(BonusCritAbAttr) .attr(BonusCritAbAttr),
.partial(),
new Ability(Abilities.AFTERMATH, 4) new Ability(Abilities.AFTERMATH, 4)
.attr(PostFaintContactDamageAbAttr, 4) .attr(PostFaintContactDamageAbAttr, 4)
.bypassFaint(), .bypassFaint(),
@ -5223,8 +5177,7 @@ export function initAbilities() {
.attr(IgnoreOpponentStatStagesAbAttr) .attr(IgnoreOpponentStatStagesAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.TINTED_LENS, 4) new Ability(Abilities.TINTED_LENS, 4)
//@ts-ignore .attr(DamageBoostAbAttr, 2, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5),
.attr(DamageBoostAbAttr, 2, (user, target, move) => target?.getMoveEffectiveness(user, move) <= 0.5), // TODO: fix TS issues
new Ability(Abilities.FILTER, 4) new Ability(Abilities.FILTER, 4)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
.ignorable(), .ignorable(),
@ -5342,8 +5295,9 @@ export function initAbilities() {
.attr(WonderSkinAbAttr) .attr(WonderSkinAbAttr)
.ignorable(), .ignorable(),
new Ability(Abilities.ANALYTIC, 5) new Ability(Abilities.ANALYTIC, 5)
//@ts-ignore .attr(MovePowerBoostAbAttr, (user, target, move) =>
.attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command !== Command.FIGHT, 1.3), // TODO fix TS issues !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn)
|| user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command !== Command.FIGHT, 1.3),
new Ability(Abilities.ILLUSION, 5) new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
@ -5508,8 +5462,7 @@ export function initAbilities() {
.bypassFaint() .bypassFaint()
.partial(), .partial(),
new Ability(Abilities.STAKEOUT, 7) new Ability(Abilities.STAKEOUT, 7)
//@ts-ignore .attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2),
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2), // TODO: fix TS issues
new Ability(Abilities.WATER_BUBBLE, 7) new Ability(Abilities.WATER_BUBBLE, 7)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5)
.attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 2)
@ -5647,8 +5600,7 @@ export function initAbilities() {
new Ability(Abilities.PRISM_ARMOR, 7) new Ability(Abilities.PRISM_ARMOR, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75), .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7) new Ability(Abilities.NEUROFORCE, 7)
//@ts-ignore .attr(MovePowerBoostAbAttr, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) >= 2, 1.25),
.attr(MovePowerBoostAbAttr, (user, target, move) => target?.getMoveEffectiveness(user, move) >= 2, 1.25), // TODO: fix TS issues
new Ability(Abilities.INTREPID_SWORD, 8) new Ability(Abilities.INTREPID_SWORD, 8)
.attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)), .condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
@ -5669,13 +5621,19 @@ export function initAbilities() {
new Ability(Abilities.MIRROR_ARMOR, 8) new Ability(Abilities.MIRROR_ARMOR, 8)
.ignorable() .ignorable()
.unimplemented(), .unimplemented(),
/**
* Right now, the logic is attached to Surf and Dive moves. Ideally, the post-defend/hit should be an
* ability attribute but the current implementation of move effects for BattlerTag does not support this- in the case
* where Cramorant is fainted.
* @see {@linkcode GulpMissileTagAttr} and {@linkcode GulpMissileTag} for Gulp Missile implementation
*/
new Ability(Abilities.GULP_MISSILE, 8) new Ability(Abilities.GULP_MISSILE, 8)
.attr(UnsuppressableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(PostDefendGulpMissileAbAttr), .bypassFaint(),
new Ability(Abilities.STALWART, 8) new Ability(Abilities.STALWART, 8)
.attr(BlockRedirectAbAttr), .attr(BlockRedirectAbAttr),
new Ability(Abilities.STEAM_ENGINE, 8) new Ability(Abilities.STEAM_ENGINE, 8)
@ -5740,10 +5698,8 @@ export function initAbilities() {
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .ignorable(),
new Ability(Abilities.HUNGER_SWITCH, 8) new Ability(Abilities.HUNGER_SWITCH, 8)
//@ts-ignore .attr(PostTurnFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 0 : 1) // TODO: fix ts-ignore .attr(PostTurnFormChangeAbAttr, p => p.getFormKey() ? 1 : 0)
//@ts-ignore
.attr(PostTurnFormChangeAbAttr, p => p.getFormKey ? 1 : 0) // TODO: fix ts-ignore
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr)

View File

@ -511,6 +511,39 @@ class WaterSportTag extends WeakenMoveTypeTag {
} }
} }
/**
* Arena Tag class for the secondary effect of {@link https://bulbapedia.bulbagarden.net/wiki/Plasma_Fists_(move) | Plasma Fists}.
* Converts Normal-type moves to Electric type for the rest of the turn.
*/
export class PlasmaFistsTag extends ArenaTag {
constructor() {
super(ArenaTagType.PLASMA_FISTS, 1, Moves.PLASMA_FISTS);
}
/** Queues Plasma Fists' on-add message */
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd"));
}
onRemove(arena: Arena): void { } // Removes default on-remove message
/**
* Converts Normal-type moves to Electric type
* @param arena n/a
* @param args
* - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type}
* @returns `true` if the given move type changed; `false` otherwise.
*/
apply(arena: Arena, args: any[]): boolean {
const moveType = args[0];
if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) {
moveType.value = Type.ELECTRIC;
return true;
}
return false;
}
}
/** /**
* Abstract class to implement arena traps. * Abstract class to implement arena traps.
*/ */
@ -1010,6 +1043,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new MudSportTag(turnCount, sourceId); return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT: case ArenaTagType.WATER_SPORT:
return new WaterSportTag(turnCount, sourceId); return new WaterSportTag(turnCount, sourceId);
case ArenaTagType.PLASMA_FISTS:
return new PlasmaFistsTag();
case ArenaTagType.SPIKES: case ArenaTagType.SPIKES:
return new SpikesTag(sourceId, side); return new SpikesTag(sourceId, side);
case ArenaTagType.TOXIC_SPIKES: case ArenaTagType.TOXIC_SPIKES:

View File

@ -1,6 +1,6 @@
import { Type } from "./type"; import { Type } from "#app/data/type";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import { pokemonEvolutions, SpeciesFormEvolution } from "./pokemon-evolutions"; import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import i18next from "i18next"; import i18next from "i18next";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Species } from "#enums/species"; import { Species } from "#enums/species";

View File

@ -1,5 +1,5 @@
import { allMoves } from "./move"; import { allMoves } from "#app/data/move";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -81,7 +81,7 @@ export const speciesEggMoves = {
[Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.THIEF, Moves.PARTING_SHOT, Moves.SPORE ], [Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.THIEF, Moves.PARTING_SHOT, Moves.SPORE ],
[Species.SPINARAK]: [ Moves.PARTING_SHOT, Moves.ATTACK_ORDER, Moves.GASTRO_ACID, Moves.STRENGTH_SAP ], [Species.SPINARAK]: [ Moves.PARTING_SHOT, Moves.ATTACK_ORDER, Moves.GASTRO_ACID, Moves.STRENGTH_SAP ],
[Species.CHINCHOU]: [ Moves.THUNDERCLAP, Moves.BOUNCY_BUBBLE, Moves.THUNDER_CAGE, Moves.TAIL_GLOW ], [Species.CHINCHOU]: [ Moves.THUNDERCLAP, Moves.BOUNCY_BUBBLE, Moves.THUNDER_CAGE, Moves.TAIL_GLOW ],
[Species.PICHU]: [ Moves.MOONBLAST, Moves.WAVE_CRASH, Moves.AIR_SLASH, Moves.AURA_WHEEL ], [Species.PICHU]: [ Moves.MOONBLAST, Moves.TRIPLE_AXEL, Moves.FIERY_DANCE, Moves.AURA_WHEEL ],
[Species.CLEFFA]: [ Moves.CALM_MIND, Moves.EARTH_POWER, Moves.WISH, Moves.LIGHT_OF_RUIN ], [Species.CLEFFA]: [ Moves.CALM_MIND, Moves.EARTH_POWER, Moves.WISH, Moves.LIGHT_OF_RUIN ],
[Species.IGGLYBUFF]: [ Moves.DRAIN_PUNCH, Moves.GRAV_APPLE, Moves.SOFT_BOILED, Moves.EXTREME_SPEED ], [Species.IGGLYBUFF]: [ Moves.DRAIN_PUNCH, Moves.GRAV_APPLE, Moves.SOFT_BOILED, Moves.EXTREME_SPEED ],
[Species.TOGEPI]: [ Moves.SCORCHING_SANDS, Moves.ROOST, Moves.RELIC_SONG, Moves.FIERY_DANCE ], [Species.TOGEPI]: [ Moves.SCORCHING_SANDS, Moves.ROOST, Moves.RELIC_SONG, Moves.FIERY_DANCE ],
@ -195,7 +195,7 @@ export const speciesEggMoves = {
[Species.REGISTEEL]: [ Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.RECOVER, Moves.GIGATON_HAMMER ], [Species.REGISTEEL]: [ Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.RECOVER, Moves.GIGATON_HAMMER ],
[Species.LATIAS]: [ Moves.CORE_ENFORCER, Moves.FUSION_FLARE, Moves.SPARKLY_SWIRL, Moves.MYSTICAL_POWER ], [Species.LATIAS]: [ Moves.CORE_ENFORCER, Moves.FUSION_FLARE, Moves.SPARKLY_SWIRL, Moves.MYSTICAL_POWER ],
[Species.LATIOS]: [ Moves.CORE_ENFORCER, Moves.BLUE_FLARE, Moves.NASTY_PLOT, Moves.TACHYON_CUTTER ], [Species.LATIOS]: [ Moves.CORE_ENFORCER, Moves.BLUE_FLARE, Moves.NASTY_PLOT, Moves.TACHYON_CUTTER ],
[Species.KYOGRE]: [ Moves.RECOVER, Moves.HURRICANE, Moves.FLIP_TURN, Moves.WILDBOLT_STORM ], [Species.KYOGRE]: [ Moves.RECOVER, Moves.HURRICANE, Moves.FREEZY_FROST, Moves.WILDBOLT_STORM ],
[Species.GROUDON]: [ Moves.STONE_AXE, Moves.SOLAR_BLADE, Moves.MORNING_SUN, Moves.SACRED_FIRE ], [Species.GROUDON]: [ Moves.STONE_AXE, Moves.SOLAR_BLADE, Moves.MORNING_SUN, Moves.SACRED_FIRE ],
[Species.RAYQUAZA]: [ Moves.V_CREATE, Moves.DRAGON_DARTS, Moves.CORE_ENFORCER, Moves.OBLIVION_WING ], [Species.RAYQUAZA]: [ Moves.V_CREATE, Moves.DRAGON_DARTS, Moves.CORE_ENFORCER, Moves.OBLIVION_WING ],
[Species.JIRACHI]: [ Moves.TACHYON_CUTTER, Moves.TRIPLE_ARROWS, Moves.ROCK_SLIDE, Moves.SHELL_SMASH ], [Species.JIRACHI]: [ Moves.TACHYON_CUTTER, Moves.TRIPLE_ARROWS, Moves.ROCK_SLIDE, Moves.SHELL_SMASH ],

View File

@ -0,0 +1,574 @@
import { Abilities } from "#app/enums/abilities";
import { Species } from "#app/enums/species";
export const starterPassiveAbilities = {
[Species.BULBASAUR]: Abilities.GRASSY_SURGE,
[Species.CHARMANDER]: Abilities.BEAST_BOOST,
[Species.SQUIRTLE]: Abilities.STURDY,
[Species.CATERPIE]: Abilities.MAGICIAN,
[Species.WEEDLE]: Abilities.TINTED_LENS,
[Species.PIDGEY]: Abilities.SHEER_FORCE,
[Species.RATTATA]: Abilities.STRONG_JAW,
[Species.SPEAROW]: Abilities.MOXIE,
[Species.EKANS]: Abilities.REGENERATOR,
[Species.SANDSHREW]: Abilities.TOUGH_CLAWS,
[Species.NIDORAN_F]: Abilities.FLARE_BOOST,
[Species.NIDORAN_M]: Abilities.GUTS,
[Species.VULPIX]: Abilities.FUR_COAT,
[Species.ZUBAT]: Abilities.INTIMIDATE,
[Species.ODDISH]: Abilities.TRIAGE,
[Species.PARAS]: Abilities.TRIAGE,
[Species.VENONAT]: Abilities.SIMPLE,
[Species.DIGLETT]: Abilities.STURDY,
[Species.MEOWTH]: Abilities.TOUGH_CLAWS,
[Species.PSYDUCK]: Abilities.SIMPLE,
[Species.MANKEY]: Abilities.IRON_FIST,
[Species.GROWLITHE]: Abilities.SPEED_BOOST,
[Species.POLIWAG]: Abilities.NO_GUARD,
[Species.ABRA]: Abilities.PSYCHIC_SURGE,
[Species.MACHOP]: Abilities.QUICK_FEET,
[Species.BELLSPROUT]: Abilities.FLOWER_GIFT,
[Species.TENTACOOL]: Abilities.TOXIC_CHAIN,
[Species.GEODUDE]: Abilities.DRY_SKIN,
[Species.PONYTA]: Abilities.MAGIC_GUARD,
[Species.SLOWPOKE]: Abilities.UNAWARE,
[Species.MAGNEMITE]: Abilities.LEVITATE,
[Species.FARFETCHD]: Abilities.SNIPER,
[Species.DODUO]: Abilities.PARENTAL_BOND,
[Species.SEEL]: Abilities.WATER_BUBBLE,
[Species.GRIMER]: Abilities.WATER_ABSORB,
[Species.SHELLDER]: Abilities.ICE_SCALES,
[Species.GASTLY]: Abilities.SHADOW_SHIELD,
[Species.ONIX]: Abilities.ROCKY_PAYLOAD,
[Species.DROWZEE]: Abilities.MAGICIAN,
[Species.KRABBY]: Abilities.THERMAL_EXCHANGE,
[Species.VOLTORB]: Abilities.TRANSISTOR,
[Species.EXEGGCUTE]: Abilities.RIPEN,
[Species.CUBONE]: Abilities.PARENTAL_BOND,
[Species.LICKITUNG]: Abilities.CHEEK_POUCH,
[Species.KOFFING]: Abilities.PARENTAL_BOND,
[Species.RHYHORN]: Abilities.FILTER,
[Species.TANGELA]: Abilities.SEED_SOWER,
[Species.KANGASKHAN]: Abilities.GUTS,
[Species.HORSEA]: Abilities.DRAGONS_MAW,
[Species.GOLDEEN]: Abilities.MULTISCALE,
[Species.STARYU]: Abilities.REGENERATOR,
[Species.SCYTHER]: Abilities.TINTED_LENS,
[Species.PINSIR]: Abilities.TINTED_LENS,
[Species.TAUROS]: Abilities.STAMINA,
[Species.MAGIKARP]: Abilities.MULTISCALE,
[Species.LAPRAS]: Abilities.LIGHTNING_ROD,
[Species.DITTO]: Abilities.ADAPTABILITY,
[Species.EEVEE]: Abilities.PICKUP,
[Species.PORYGON]: Abilities.PROTEAN,
[Species.OMANYTE]: Abilities.STURDY,
[Species.KABUTO]: Abilities.TOUGH_CLAWS,
[Species.AERODACTYL]: Abilities.ORICHALCUM_PULSE,
[Species.ARTICUNO]: Abilities.SNOW_WARNING,
[Species.ZAPDOS]: Abilities.DRIZZLE,
[Species.MOLTRES]: Abilities.DROUGHT,
[Species.DRATINI]: Abilities.AERILATE,
[Species.MEWTWO]: Abilities.NEUROFORCE,
[Species.MEW]: Abilities.PROTEAN,
[Species.CHIKORITA]: Abilities.THICK_FAT,
[Species.CYNDAQUIL]: Abilities.DROUGHT,
[Species.TOTODILE]: Abilities.TOUGH_CLAWS,
[Species.SENTRET]: Abilities.PICKUP,
[Species.HOOTHOOT]: Abilities.AERILATE,
[Species.LEDYBA]: Abilities.PRANKSTER,
[Species.SPINARAK]: Abilities.PRANKSTER,
[Species.CHINCHOU]: Abilities.WATER_BUBBLE,
[Species.PICHU]: Abilities.ELECTRIC_SURGE,
[Species.CLEFFA]: Abilities.ANALYTIC,
[Species.IGGLYBUFF]: Abilities.HUGE_POWER,
[Species.TOGEPI]: Abilities.PIXILATE,
[Species.NATU]: Abilities.TINTED_LENS,
[Species.MAREEP]: Abilities.ELECTROMORPHOSIS,
[Species.HOPPIP]: Abilities.FLUFFY,
[Species.AIPOM]: Abilities.SCRAPPY,
[Species.SUNKERN]: Abilities.DROUGHT,
[Species.YANMA]: Abilities.SHEER_FORCE,
[Species.WOOPER]: Abilities.COMATOSE,
[Species.MURKROW]: Abilities.DARK_AURA,
[Species.MISDREAVUS]: Abilities.BEADS_OF_RUIN,
[Species.UNOWN]: Abilities.PICKUP,
[Species.GIRAFARIG]: Abilities.PARENTAL_BOND,
[Species.PINECO]: Abilities.IRON_BARBS,
[Species.DUNSPARCE]: Abilities.UNAWARE,
[Species.GLIGAR]: Abilities.TOXIC_BOOST,
[Species.SNUBBULL]: Abilities.PIXILATE,
[Species.QWILFISH]: Abilities.TOXIC_DEBRIS,
[Species.SHUCKLE]: Abilities.HARVEST,
[Species.HERACROSS]: Abilities.TECHNICIAN,
[Species.SNEASEL]: Abilities.TOUGH_CLAWS,
[Species.TEDDIURSA]: Abilities.THICK_FAT,
[Species.SLUGMA]: Abilities.DESOLATE_LAND,
[Species.SWINUB]: Abilities.SLUSH_RUSH,
[Species.CORSOLA]: Abilities.STORM_DRAIN,
[Species.REMORAID]: Abilities.SIMPLE,
[Species.DELIBIRD]: Abilities.HUGE_POWER,
[Species.SKARMORY]: Abilities.LIGHTNING_ROD,
[Species.HOUNDOUR]: Abilities.DROUGHT,
[Species.PHANPY]: Abilities.SPEED_BOOST,
[Species.STANTLER]: Abilities.SPEED_BOOST,
[Species.SMEARGLE]: Abilities.PRANKSTER,
[Species.TYROGUE]: Abilities.MOXIE,
[Species.SMOOCHUM]: Abilities.PSYCHIC_SURGE,
[Species.ELEKID]: Abilities.SHEER_FORCE,
[Species.MAGBY]: Abilities.CONTRARY,
[Species.MILTANK]: Abilities.STAMINA,
[Species.RAIKOU]: Abilities.TRANSISTOR,
[Species.ENTEI]: Abilities.MOXIE,
[Species.SUICUNE]: Abilities.UNAWARE,
[Species.LARVITAR]: Abilities.SAND_RUSH,
[Species.LUGIA]: Abilities.DELTA_STREAM,
[Species.HO_OH]: Abilities.MAGIC_GUARD,
[Species.CELEBI]: Abilities.PSYCHIC_SURGE,
[Species.TREECKO]: Abilities.TINTED_LENS,
[Species.TORCHIC]: Abilities.RECKLESS,
[Species.MUDKIP]: Abilities.DRIZZLE,
[Species.POOCHYENA]: Abilities.TOUGH_CLAWS,
[Species.ZIGZAGOON]: Abilities.RUN_AWAY,
[Species.WURMPLE]: Abilities.SIMPLE,
[Species.LOTAD]: Abilities.DRIZZLE,
[Species.SEEDOT]: Abilities.SHARPNESS,
[Species.TAILLOW]: Abilities.AERILATE,
[Species.WINGULL]: Abilities.SWIFT_SWIM,
[Species.RALTS]: Abilities.PSYCHIC_SURGE,
[Species.SURSKIT]: Abilities.WATER_BUBBLE,
[Species.SHROOMISH]: Abilities.GUTS,
[Species.SLAKOTH]: Abilities.GUTS,
[Species.NINCADA]: Abilities.MAGIC_GUARD,
[Species.WHISMUR]: Abilities.PUNK_ROCK,
[Species.MAKUHITA]: Abilities.STAMINA,
[Species.AZURILL]: Abilities.MISTY_SURGE,
[Species.NOSEPASS]: Abilities.LEVITATE,
[Species.SKITTY]: Abilities.SCRAPPY,
[Species.SABLEYE]: Abilities.UNNERVE,
[Species.MAWILE]: Abilities.UNNERVE,
[Species.ARON]: Abilities.EARTH_EATER,
[Species.MEDITITE]: Abilities.MINDS_EYE,
[Species.ELECTRIKE]: Abilities.ELECTRIC_SURGE,
[Species.PLUSLE]: Abilities.POWER_SPOT,
[Species.MINUN]: Abilities.POWER_SPOT,
[Species.VOLBEAT]: Abilities.HONEY_GATHER,
[Species.ILLUMISE]: Abilities.HONEY_GATHER,
[Species.GULPIN]: Abilities.EARTH_EATER,
[Species.CARVANHA]: Abilities.SHEER_FORCE,
[Species.WAILMER]: Abilities.LEVITATE,
[Species.NUMEL]: Abilities.FUR_COAT,
[Species.TORKOAL]: Abilities.ANALYTIC,
[Species.SPOINK]: Abilities.PSYCHIC_SURGE,
[Species.SPINDA]: Abilities.SIMPLE,
[Species.TRAPINCH]: Abilities.ADAPTABILITY,
[Species.CACNEA]: Abilities.SAND_RUSH,
[Species.SWABLU]: Abilities.ADAPTABILITY,
[Species.ZANGOOSE]: Abilities.POISON_HEAL,
[Species.SEVIPER]: Abilities.MULTISCALE,
[Species.LUNATONE]: Abilities.SHADOW_SHIELD,
[Species.SOLROCK]: Abilities.DROUGHT,
[Species.BARBOACH]: Abilities.SIMPLE,
[Species.CORPHISH]: Abilities.TOUGH_CLAWS,
[Species.BALTOY]: Abilities.WELL_BAKED_BODY,
[Species.LILEEP]: Abilities.SEED_SOWER,
[Species.ANORITH]: Abilities.WATER_ABSORB,
[Species.FEEBAS]: Abilities.MAGIC_GUARD,
[Species.CASTFORM]: Abilities.ADAPTABILITY,
[Species.KECLEON]: Abilities.ADAPTABILITY,
[Species.SHUPPET]: Abilities.SHADOW_SHIELD,
[Species.DUSKULL]: Abilities.UNNERVE,
[Species.TROPIUS]: Abilities.RIPEN,
[Species.ABSOL]: Abilities.SHARPNESS,
[Species.WYNAUT]: Abilities.STURDY,
[Species.SNORUNT]: Abilities.SNOW_WARNING,
[Species.SPHEAL]: Abilities.UNAWARE,
[Species.CLAMPERL]: Abilities.DRIZZLE,
[Species.RELICANTH]: Abilities.PRIMORDIAL_SEA,
[Species.LUVDISC]: Abilities.MULTISCALE,
[Species.BAGON]: Abilities.DRAGONS_MAW,
[Species.BELDUM]: Abilities.LEVITATE,
[Species.REGIROCK]: Abilities.SAND_STREAM,
[Species.REGICE]: Abilities.SNOW_WARNING,
[Species.REGISTEEL]: Abilities.FILTER,
[Species.LATIAS]: Abilities.PRISM_ARMOR,
[Species.LATIOS]: Abilities.TINTED_LENS,
[Species.KYOGRE]: Abilities.MOLD_BREAKER,
[Species.GROUDON]: Abilities.TURBOBLAZE,
[Species.RAYQUAZA]: Abilities.UNNERVE,
[Species.JIRACHI]: Abilities.COMATOSE,
[Species.DEOXYS]: Abilities.PROTEAN,
[Species.TURTWIG]: Abilities.THICK_FAT,
[Species.CHIMCHAR]: Abilities.BEAST_BOOST,
[Species.PIPLUP]: Abilities.DRIZZLE,
[Species.STARLY]: Abilities.ROCK_HEAD,
[Species.BIDOOF]: Abilities.SAP_SIPPER,
[Species.KRICKETOT]: Abilities.SHARPNESS,
[Species.SHINX]: Abilities.SPEED_BOOST,
[Species.BUDEW]: Abilities.GRASSY_SURGE,
[Species.CRANIDOS]: Abilities.ROCK_HEAD,
[Species.SHIELDON]: Abilities.EARTH_EATER,
[Species.BURMY]: Abilities.STURDY,
[Species.COMBEE]: Abilities.INTIMIDATE,
[Species.PACHIRISU]: Abilities.HONEY_GATHER,
[Species.BUIZEL]: Abilities.MOXIE,
[Species.CHERUBI]: Abilities.ORICHALCUM_PULSE,
[Species.SHELLOS]: Abilities.REGENERATOR,
[Species.DRIFLOON]: Abilities.MAGIC_GUARD,
[Species.BUNEARY]: Abilities.ADAPTABILITY,
[Species.GLAMEOW]: Abilities.INTIMIDATE,
[Species.CHINGLING]: Abilities.PUNK_ROCK,
[Species.STUNKY]: Abilities.NEUTRALIZING_GAS,
[Species.BRONZOR]: Abilities.BULLETPROOF,
[Species.BONSLY]: Abilities.SAP_SIPPER,
[Species.MIME_JR]: Abilities.OPPORTUNIST,
[Species.HAPPINY]: Abilities.FUR_COAT,
[Species.CHATOT]: Abilities.PUNK_ROCK,
[Species.SPIRITOMB]: Abilities.VESSEL_OF_RUIN,
[Species.GIBLE]: Abilities.SAND_STREAM,
[Species.MUNCHLAX]: Abilities.RIPEN,
[Species.RIOLU]: Abilities.MINDS_EYE,
[Species.HIPPOPOTAS]: Abilities.UNAWARE,
[Species.SKORUPI]: Abilities.SUPER_LUCK,
[Species.CROAGUNK]: Abilities.MOXIE,
[Species.CARNIVINE]: Abilities.ARENA_TRAP,
[Species.FINNEON]: Abilities.WATER_BUBBLE,
[Species.MANTYKE]: Abilities.UNAWARE,
[Species.SNOVER]: Abilities.THICK_FAT,
[Species.ROTOM]: Abilities.HADRON_ENGINE,
[Species.UXIE]: Abilities.UNAWARE,
[Species.MESPRIT]: Abilities.MOODY,
[Species.AZELF]: Abilities.NEUROFORCE,
[Species.DIALGA]: Abilities.LEVITATE,
[Species.PALKIA]: Abilities.SPEED_BOOST,
[Species.HEATRAN]: Abilities.EARTH_EATER,
[Species.REGIGIGAS]: Abilities.SCRAPPY,
[Species.GIRATINA]: Abilities.SHADOW_SHIELD,
[Species.CRESSELIA]: Abilities.SHADOW_SHIELD,
[Species.PHIONE]: Abilities.SIMPLE,
[Species.MANAPHY]: Abilities.PRIMORDIAL_SEA,
[Species.DARKRAI]: Abilities.UNNERVE,
[Species.SHAYMIN]: Abilities.WIND_RIDER,
[Species.ARCEUS]: Abilities.ADAPTABILITY,
[Species.VICTINI]: Abilities.SHEER_FORCE,
[Species.SNIVY]: Abilities.MULTISCALE,
[Species.TEPIG]: Abilities.ROCK_HEAD,
[Species.OSHAWOTT]: Abilities.INTREPID_SWORD,
[Species.PATRAT]: Abilities.NO_GUARD,
[Species.LILLIPUP]: Abilities.FUR_COAT,
[Species.PURRLOIN]: Abilities.PICKUP,
[Species.PANSAGE]: Abilities.WELL_BAKED_BODY,
[Species.PANSEAR]: Abilities.WATER_ABSORB,
[Species.PANPOUR]: Abilities.SAP_SIPPER,
[Species.MUNNA]: Abilities.NEUTRALIZING_GAS,
[Species.PIDOVE]: Abilities.SNIPER,
[Species.BLITZLE]: Abilities.ELECTRIC_SURGE,
[Species.ROGGENROLA]: Abilities.SOLID_ROCK,
[Species.WOOBAT]: Abilities.OPPORTUNIST,
[Species.DRILBUR]: Abilities.SAND_STREAM,
[Species.AUDINO]: Abilities.FRIEND_GUARD,
[Species.TIMBURR]: Abilities.ROCKY_PAYLOAD,
[Species.TYMPOLE]: Abilities.POISON_HEAL,
[Species.THROH]: Abilities.STAMINA,
[Species.SAWK]: Abilities.SCRAPPY,
[Species.SEWADDLE]: Abilities.SHARPNESS,
[Species.VENIPEDE]: Abilities.STAMINA,
[Species.COTTONEE]: Abilities.FLUFFY,
[Species.PETILIL]: Abilities.SIMPLE,
[Species.BASCULIN]: Abilities.SUPREME_OVERLORD,
[Species.SANDILE]: Abilities.TOUGH_CLAWS,
[Species.DARUMAKA]: Abilities.GORILLA_TACTICS,
[Species.MARACTUS]: Abilities.WELL_BAKED_BODY,
[Species.DWEBBLE]: Abilities.ROCKY_PAYLOAD,
[Species.SCRAGGY]: Abilities.PROTEAN,
[Species.SIGILYPH]: Abilities.FLARE_BOOST,
[Species.YAMASK]: Abilities.PURIFYING_SALT,
[Species.TIRTOUGA]: Abilities.WATER_ABSORB,
[Species.ARCHEN]: Abilities.MULTISCALE,
[Species.TRUBBISH]: Abilities.NEUTRALIZING_GAS,
[Species.ZORUA]: Abilities.DARK_AURA,
[Species.MINCCINO]: Abilities.FUR_COAT,
[Species.GOTHITA]: Abilities.UNNERVE,
[Species.SOLOSIS]: Abilities.PSYCHIC_SURGE,
[Species.DUCKLETT]: Abilities.DRIZZLE,
[Species.VANILLITE]: Abilities.SLUSH_RUSH,
[Species.DEERLING]: Abilities.FUR_COAT,
[Species.EMOLGA]: Abilities.TRANSISTOR,
[Species.KARRABLAST]: Abilities.QUICK_DRAW,
[Species.FOONGUS]: Abilities.THICK_FAT,
[Species.FRILLISH]: Abilities.POISON_HEAL,
[Species.ALOMOMOLA]: Abilities.MULTISCALE,
[Species.JOLTIK]: Abilities.TRANSISTOR,
[Species.FERROSEED]: Abilities.ROUGH_SKIN,
[Species.KLINK]: Abilities.STEELY_SPIRIT,
[Species.TYNAMO]: Abilities.POISON_HEAL,
[Species.ELGYEM]: Abilities.PRISM_ARMOR,
[Species.LITWICK]: Abilities.SOUL_HEART,
[Species.AXEW]: Abilities.DRAGONS_MAW,
[Species.CUBCHOO]: Abilities.TOUGH_CLAWS,
[Species.CRYOGONAL]: Abilities.SNOW_WARNING,
[Species.SHELMET]: Abilities.PROTEAN,
[Species.STUNFISK]: Abilities.STORM_DRAIN,
[Species.MIENFOO]: Abilities.NO_GUARD,
[Species.DRUDDIGON]: Abilities.INTIMIDATE,
[Species.GOLETT]: Abilities.SHADOW_SHIELD,
[Species.PAWNIARD]: Abilities.SWORD_OF_RUIN,
[Species.BOUFFALANT]: Abilities.ROCK_HEAD,
[Species.RUFFLET]: Abilities.SPEED_BOOST,
[Species.VULLABY]: Abilities.THICK_FAT,
[Species.HEATMOR]: Abilities.CONTRARY,
[Species.DURANT]: Abilities.COMPOUND_EYES,
[Species.DEINO]: Abilities.PARENTAL_BOND,
[Species.LARVESTA]: Abilities.DROUGHT,
[Species.COBALION]: Abilities.INTREPID_SWORD,
[Species.TERRAKION]: Abilities.ROCKY_PAYLOAD,
[Species.VIRIZION]: Abilities.SHARPNESS,
[Species.TORNADUS]: Abilities.DRIZZLE,
[Species.THUNDURUS]: Abilities.DRIZZLE,
[Species.RESHIRAM]: Abilities.ORICHALCUM_PULSE,
[Species.ZEKROM]: Abilities.HADRON_ENGINE,
[Species.LANDORUS]: Abilities.STORM_DRAIN,
[Species.KYUREM]: Abilities.SNOW_WARNING,
[Species.KELDEO]: Abilities.GRIM_NEIGH,
[Species.MELOETTA]: Abilities.MINDS_EYE,
[Species.GENESECT]: Abilities.PROTEAN,
[Species.CHESPIN]: Abilities.DAUNTLESS_SHIELD,
[Species.FENNEKIN]: Abilities.PSYCHIC_SURGE,
[Species.FROAKIE]: Abilities.STAKEOUT,
[Species.BUNNELBY]: Abilities.GUTS,
[Species.FLETCHLING]: Abilities.MAGIC_GUARD,
[Species.SCATTERBUG]: Abilities.PRANKSTER,
[Species.LITLEO]: Abilities.BEAST_BOOST,
[Species.FLABEBE]: Abilities.GRASSY_SURGE,
[Species.SKIDDO]: Abilities.SEED_SOWER,
[Species.PANCHAM]: Abilities.FUR_COAT,
[Species.FURFROU]: Abilities.FLUFFY,
[Species.ESPURR]: Abilities.FUR_COAT,
[Species.HONEDGE]: Abilities.SHARPNESS,
[Species.SPRITZEE]: Abilities.FUR_COAT,
[Species.SWIRLIX]: Abilities.WELL_BAKED_BODY,
[Species.INKAY]: Abilities.UNNERVE,
[Species.BINACLE]: Abilities.SAP_SIPPER,
[Species.SKRELP]: Abilities.DRAGONS_MAW,
[Species.CLAUNCHER]: Abilities.SWIFT_SWIM,
[Species.HELIOPTILE]: Abilities.PROTEAN,
[Species.TYRUNT]: Abilities.RECKLESS,
[Species.AMAURA]: Abilities.ICE_SCALES,
[Species.HAWLUCHA]: Abilities.MOXIE,
[Species.DEDENNE]: Abilities.PIXILATE,
[Species.CARBINK]: Abilities.SOLID_ROCK,
[Species.GOOMY]: Abilities.REGENERATOR,
[Species.KLEFKI]: Abilities.LEVITATE,
[Species.PHANTUMP]: Abilities.SHADOW_TAG,
[Species.PUMPKABOO]: Abilities.WELL_BAKED_BODY,
[Species.BERGMITE]: Abilities.ICE_SCALES,
[Species.NOIBAT]: Abilities.PUNK_ROCK,
[Species.XERNEAS]: Abilities.HARVEST,
[Species.YVELTAL]: Abilities.SOUL_HEART,
[Species.ZYGARDE]: Abilities.HUGE_POWER,
[Species.DIANCIE]: Abilities.LEVITATE,
[Species.HOOPA]: Abilities.OPPORTUNIST,
[Species.VOLCANION]: Abilities.FILTER,
[Species.ROWLET]: Abilities.SNIPER,
[Species.LITTEN]: Abilities.OPPORTUNIST,
[Species.POPPLIO]: Abilities.PUNK_ROCK,
[Species.PIKIPEK]: Abilities.TECHNICIAN,
[Species.YUNGOOS]: Abilities.TOUGH_CLAWS,
[Species.GRUBBIN]: Abilities.SPEED_BOOST,
[Species.CRABRAWLER]: Abilities.WATER_BUBBLE,
[Species.ORICORIO]: Abilities.ADAPTABILITY,
[Species.CUTIEFLY]: Abilities.TINTED_LENS,
[Species.ROCKRUFF]: Abilities.ROCKY_PAYLOAD,
[Species.WISHIWASHI]: Abilities.REGENERATOR,
[Species.MAREANIE]: Abilities.TOXIC_DEBRIS,
[Species.MUDBRAY]: Abilities.CUD_CHEW,
[Species.DEWPIDER]: Abilities.TINTED_LENS,
[Species.FOMANTIS]: Abilities.SHARPNESS,
[Species.MORELULL]: Abilities.TRIAGE,
[Species.SALANDIT]: Abilities.DRAGONS_MAW,
[Species.STUFFUL]: Abilities.SCRAPPY,
[Species.BOUNSWEET]: Abilities.MOXIE,
[Species.COMFEY]: Abilities.FRIEND_GUARD,
[Species.ORANGURU]: Abilities.POWER_SPOT,
[Species.PASSIMIAN]: Abilities.LIBERO,
[Species.WIMPOD]: Abilities.REGENERATOR,
[Species.SANDYGAST]: Abilities.SAND_SPIT,
[Species.PYUKUMUKU]: Abilities.PURIFYING_SALT,
[Species.TYPE_NULL]: Abilities.ADAPTABILITY,
[Species.MINIOR]: Abilities.STURDY,
[Species.KOMALA]: Abilities.GUTS,
[Species.TURTONATOR]: Abilities.DAUNTLESS_SHIELD,
[Species.TOGEDEMARU]: Abilities.ROUGH_SKIN,
[Species.MIMIKYU]: Abilities.TOUGH_CLAWS,
[Species.BRUXISH]: Abilities.MULTISCALE,
[Species.DRAMPA]: Abilities.THICK_FAT,
[Species.DHELMISE]: Abilities.WATER_BUBBLE,
[Species.JANGMO_O]: Abilities.DAUNTLESS_SHIELD,
[Species.TAPU_KOKO]: Abilities.TRANSISTOR,
[Species.TAPU_LELE]: Abilities.SHEER_FORCE,
[Species.TAPU_BULU]: Abilities.TRIAGE,
[Species.TAPU_FINI]: Abilities.FAIRY_AURA,
[Species.COSMOG]: Abilities.BEAST_BOOST,
[Species.NIHILEGO]: Abilities.LEVITATE,
[Species.BUZZWOLE]: Abilities.MOXIE,
[Species.PHEROMOSA]: Abilities.TINTED_LENS,
[Species.XURKITREE]: Abilities.TRANSISTOR,
[Species.CELESTEELA]: Abilities.HEATPROOF,
[Species.KARTANA]: Abilities.SHARPNESS,
[Species.GUZZLORD]: Abilities.POISON_HEAL,
[Species.NECROZMA]: Abilities.BEAST_BOOST,
[Species.MAGEARNA]: Abilities.STEELY_SPIRIT,
[Species.MARSHADOW]: Abilities.IRON_FIST,
[Species.POIPOLE]: Abilities.SHEER_FORCE,
[Species.STAKATAKA]: Abilities.SOLID_ROCK,
[Species.BLACEPHALON]: Abilities.MAGIC_GUARD,
[Species.ZERAORA]: Abilities.TOUGH_CLAWS,
[Species.MELTAN]: Abilities.STEELY_SPIRIT,
[Species.GROOKEY]: Abilities.GRASS_PELT,
[Species.SCORBUNNY]: Abilities.NO_GUARD,
[Species.SOBBLE]: Abilities.SUPER_LUCK,
[Species.SKWOVET]: Abilities.HARVEST,
[Species.ROOKIDEE]: Abilities.IRON_BARBS,
[Species.BLIPBUG]: Abilities.PSYCHIC_SURGE,
[Species.NICKIT]: Abilities.MAGICIAN,
[Species.GOSSIFLEUR]: Abilities.GRASSY_SURGE,
[Species.WOOLOO]: Abilities.SIMPLE,
[Species.CHEWTLE]: Abilities.ROCKY_PAYLOAD,
[Species.YAMPER]: Abilities.SHEER_FORCE,
[Species.ROLYCOLY]: Abilities.SOLID_ROCK,
[Species.APPLIN]: Abilities.DRAGONS_MAW,
[Species.SILICOBRA]: Abilities.SAND_RUSH,
[Species.CRAMORANT]: Abilities.LIGHTNING_ROD,
[Species.ARROKUDA]: Abilities.INTIMIDATE,
[Species.TOXEL]: Abilities.ELECTRIC_SURGE,
[Species.SIZZLIPEDE]: Abilities.SPEED_BOOST,
[Species.CLOBBOPUS]: Abilities.WATER_BUBBLE,
[Species.SINISTEA]: Abilities.SHADOW_SHIELD,
[Species.HATENNA]: Abilities.FAIRY_AURA,
[Species.IMPIDIMP]: Abilities.FUR_COAT,
[Species.MILCERY]: Abilities.REGENERATOR,
[Species.FALINKS]: Abilities.PARENTAL_BOND,
[Species.PINCURCHIN]: Abilities.ELECTROMORPHOSIS,
[Species.SNOM]: Abilities.SNOW_WARNING,
[Species.STONJOURNER]: Abilities.STURDY,
[Species.EISCUE]: Abilities.ICE_SCALES,
[Species.INDEEDEE]: Abilities.FRIEND_GUARD,
[Species.MORPEKO]: Abilities.MOODY,
[Species.CUFANT]: Abilities.EARTH_EATER,
[Species.DRACOZOLT]: Abilities.NO_GUARD,
[Species.ARCTOZOLT]: Abilities.TRANSISTOR,
[Species.DRACOVISH]: Abilities.SWIFT_SWIM,
[Species.ARCTOVISH]: Abilities.STRONG_JAW,
[Species.DURALUDON]: Abilities.STEELWORKER,
[Species.DREEPY]: Abilities.PARENTAL_BOND,
[Species.ZACIAN]: Abilities.UNNERVE,
[Species.ZAMAZENTA]: Abilities.UNNERVE,
[Species.ETERNATUS]: Abilities.NEUTRALIZING_GAS,
[Species.KUBFU]: Abilities.IRON_FIST,
[Species.ZARUDE]: Abilities.TOUGH_CLAWS,
[Species.REGIELEKI]: Abilities.ELECTRIC_SURGE,
[Species.REGIDRAGO]: Abilities.MULTISCALE,
[Species.GLASTRIER]: Abilities.FILTER,
[Species.SPECTRIER]: Abilities.SHADOW_SHIELD,
[Species.CALYREX]: Abilities.HARVEST,
[Species.ENAMORUS]: Abilities.FAIRY_AURA,
[Species.SPRIGATITO]: Abilities.MAGICIAN,
[Species.FUECOCO]: Abilities.PUNK_ROCK,
[Species.QUAXLY]: Abilities.OPPORTUNIST,
[Species.LECHONK]: Abilities.SIMPLE,
[Species.TAROUNTULA]: Abilities.HONEY_GATHER,
[Species.NYMBLE]: Abilities.GUTS,
[Species.PAWMI]: Abilities.TRANSISTOR,
[Species.TANDEMAUS]: Abilities.SCRAPPY,
[Species.FIDOUGH]: Abilities.WATER_ABSORB,
[Species.SMOLIV]: Abilities.RIPEN,
[Species.SQUAWKABILLY]: Abilities.MOXIE,
[Species.NACLI]: Abilities.SOLID_ROCK,
[Species.CHARCADET]: Abilities.PRISM_ARMOR,
[Species.TADBULB]: Abilities.STAMINA,
[Species.WATTREL]: Abilities.SHEER_FORCE,
[Species.MASCHIFF]: Abilities.STRONG_JAW,
[Species.SHROODLE]: Abilities.CORROSION,
[Species.BRAMBLIN]: Abilities.SHADOW_SHIELD,
[Species.TOEDSCOOL]: Abilities.PRANKSTER,
[Species.KLAWF]: Abilities.WATER_ABSORB,
[Species.CAPSAKID]: Abilities.PARENTAL_BOND,
[Species.RELLOR]: Abilities.PRANKSTER,
[Species.FLITTLE]: Abilities.DAZZLING,
[Species.TINKATINK]: Abilities.STEELWORKER,
[Species.WIGLETT]: Abilities.STURDY,
[Species.BOMBIRDIER]: Abilities.UNBURDEN,
[Species.FINIZEN]: Abilities.IRON_FIST,
[Species.VAROOM]: Abilities.LEVITATE,
[Species.CYCLIZAR]: Abilities.PROTEAN,
[Species.ORTHWORM]: Abilities.REGENERATOR,
[Species.GLIMMET]: Abilities.LEVITATE,
[Species.GREAVARD]: Abilities.FUR_COAT,
[Species.FLAMIGO]: Abilities.MOXIE,
[Species.CETODDLE]: Abilities.ICE_SCALES,
[Species.VELUZA]: Abilities.SUPER_LUCK,
[Species.DONDOZO]: Abilities.PARENTAL_BOND,
[Species.TATSUGIRI]: Abilities.ADAPTABILITY,
[Species.GREAT_TUSK]: Abilities.INTIMIDATE,
[Species.SCREAM_TAIL]: Abilities.UNAWARE,
[Species.BRUTE_BONNET]: Abilities.CHLOROPHYLL,
[Species.FLUTTER_MANE]: Abilities.DAZZLING,
[Species.SLITHER_WING]: Abilities.SCRAPPY,
[Species.SANDY_SHOCKS]: Abilities.EARTH_EATER,
[Species.IRON_TREADS]: Abilities.STEELY_SPIRIT,
[Species.IRON_BUNDLE]: Abilities.SNOW_WARNING,
[Species.IRON_HANDS]: Abilities.IRON_FIST,
[Species.IRON_JUGULIS]: Abilities.LIGHTNING_ROD,
[Species.IRON_MOTH]: Abilities.LEVITATE,
[Species.IRON_THORNS]: Abilities.SAND_STREAM,
[Species.FRIGIBAX]: Abilities.SNOW_WARNING,
[Species.GIMMIGHOUL]: Abilities.HONEY_GATHER,
[Species.WO_CHIEN]: Abilities.VESSEL_OF_RUIN,
[Species.CHIEN_PAO]: Abilities.INTIMIDATE,
[Species.TING_LU]: Abilities.STAMINA,
[Species.CHI_YU]: Abilities.BERSERK,
[Species.ROARING_MOON]: Abilities.TOUGH_CLAWS,
[Species.IRON_VALIANT]: Abilities.ADAPTABILITY,
[Species.KORAIDON]: Abilities.OPPORTUNIST,
[Species.MIRAIDON]: Abilities.OPPORTUNIST,
[Species.WALKING_WAKE]: Abilities.BEAST_BOOST,
[Species.IRON_LEAVES]: Abilities.SHARPNESS,
[Species.POLTCHAGEIST]: Abilities.TRIAGE,
[Species.OKIDOGI]: Abilities.FUR_COAT,
[Species.MUNKIDORI]: Abilities.NEUROFORCE,
[Species.FEZANDIPITI]: Abilities.LEVITATE,
[Species.OGERPON]: Abilities.OPPORTUNIST,
[Species.GOUGING_FIRE]: Abilities.BEAST_BOOST,
[Species.RAGING_BOLT]: Abilities.BEAST_BOOST,
[Species.IRON_BOULDER]: Abilities.SHARPNESS,
[Species.IRON_CROWN]: Abilities.SHARPNESS,
[Species.TERAPAGOS]: Abilities.SOUL_HEART,
[Species.PECHARUNT]: Abilities.TOXIC_CHAIN,
[Species.ALOLA_RATTATA]: Abilities.ADAPTABILITY,
[Species.ALOLA_SANDSHREW]: Abilities.ICE_SCALES,
[Species.ALOLA_VULPIX]: Abilities.SHEER_FORCE,
[Species.ALOLA_DIGLETT]: Abilities.STURDY,
[Species.ALOLA_MEOWTH]: Abilities.DARK_AURA,
[Species.ALOLA_GEODUDE]: Abilities.DRY_SKIN,
[Species.ALOLA_GRIMER]: Abilities.TOXIC_DEBRIS,
[Species.ETERNAL_FLOETTE]: Abilities.MAGIC_GUARD,
[Species.GALAR_MEOWTH]: Abilities.STEELWORKER,
[Species.GALAR_PONYTA]: Abilities.MOXIE,
[Species.GALAR_SLOWPOKE]: Abilities.UNAWARE,
[Species.GALAR_FARFETCHD]: Abilities.INTREPID_SWORD,
[Species.GALAR_ARTICUNO]: Abilities.SERENE_GRACE,
[Species.GALAR_ZAPDOS]: Abilities.TOUGH_CLAWS,
[Species.GALAR_MOLTRES]: Abilities.DARK_AURA,
[Species.GALAR_CORSOLA]: Abilities.SHADOW_SHIELD,
[Species.GALAR_ZIGZAGOON]: Abilities.POISON_HEAL,
[Species.GALAR_DARUMAKA]: Abilities.FLASH_FIRE,
[Species.GALAR_YAMASK]: Abilities.TABLETS_OF_RUIN,
[Species.GALAR_STUNFISK]: Abilities.ARENA_TRAP,
[Species.HISUI_GROWLITHE]: Abilities.RECKLESS,
[Species.HISUI_VOLTORB]: Abilities.TRANSISTOR,
[Species.HISUI_QWILFISH]: Abilities.MERCILESS,
[Species.HISUI_SNEASEL]: Abilities.SCRAPPY,
[Species.HISUI_ZORUA]: Abilities.ADAPTABILITY,
[Species.PALDEA_TAUROS]: Abilities.ADAPTABILITY,
[Species.PALDEA_WOOPER]: Abilities.THICK_FAT,
[Species.BLOODMOON_URSALUNA]: Abilities.BERSERK
};

View File

@ -1,16 +1,18 @@
import { Gender } from "./gender"; import { Gender } from "#app/data/gender";
import { PokeballType } from "./pokeball"; import { PokeballType } from "#app/data/pokeball";
import Pokemon from "../field/pokemon"; import Pokemon from "#app/field/pokemon";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Type } from "./type"; import { Type } from "#app/data/type";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import { SpeciesFormKey } from "./pokemon-species"; import { WeatherType } from "#app/data/weather";
import { WeatherType } from "./weather"; import { Nature } from "#app/data/nature";
import { Nature } from "./nature";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier } from "#app/modifier/modifier";
import { SpeciesFormKey } from "#enums/species-form-key";
export enum SpeciesWildEvolutionDelay { export enum SpeciesWildEvolutionDelay {
NONE, NONE,
@ -1647,8 +1649,14 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM)
], ],
[Species.GIMMIGHOUL]: [ [Species.GIMMIGHOUL]: [
new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition( p => p.evoCounter > 9 ), SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter
new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition( p => p.evoCounter > 9 ), SpeciesWildEvolutionDelay.VERY_LONG) + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier
|| m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG),
new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter
+ p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length
+ p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier
|| m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG)
] ]
}; };

53
src/data/balance/rates.ts Normal file
View File

@ -0,0 +1,53 @@
/**
* Rates for shinies and other random properties are defined in this file.
* CHANCE is defined as x/65536
* RATE is defined as 1/x
*/
// #region Encounterable properties
/** `64/65536 -> 1/1024` */
export const BASE_SHINY_CHANCE = 64;
/** `256/65536 -> 1/256` */
export const BASE_HIDDEN_ABILITY_CHANCE = 256;
// #region Egg properties
// Threshold x at which a gacha egg is determined to be a certain tier
// Specifically, the tier is determined by the highest threshold a random value between 0-255 meets or exceeds
// Legendary Up Gacha raises these thresholds by 1, thereby giving Legendary eggs 2/256 chance
export const GACHA_DEFAULT_COMMON_EGG_THRESHOLD = 52; // Default 204/256 chance, 203/256 chance in Legendary Up Gacha
export const GACHA_DEFAULT_RARE_EGG_THRESHOLD = 8; // Default 44/256 chance
export const GACHA_DEFAULT_EPIC_EGG_THRESHOLD = 1; // Default 7/256 chance, leaving Legendary as 1/256 chance
export const GACHA_LEGENDARY_UP_THRESHOLD_OFFSET = 1; // The offset to threshold for Legendary Up gacha eggs. +x/256 Legendary Egg chance, -x/256 Common Egg chance
// The number of eggs without finding a certain tier egg it takes for egg pity to kick in and that tier to be forced
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
export const EGG_PITY_LEGENDARY_THRESHOLD = 412;
export const EGG_PITY_EPIC_THRESHOLD = 59;
export const EGG_PITY_RARE_THRESHOLD = 9;
// Waves to hatch an egg of a given tier
export const HATCH_WAVES_COMMON_EGG = 10;
export const HATCH_WAVES_RARE_EGG = 25;
export const HATCH_WAVES_EPIC_EGG = 50;
export const HATCH_WAVES_LEGENDARY_EGG = 100;
export const HATCH_WAVES_MANAPHY_EGG = 50;
// Rates for specific random properties in 1/x
export const GACHA_DEFAULT_SHINY_RATE = 128;
export const GACHA_SHINY_UP_SHINY_RATE = 64;
export const SAME_SPECIES_EGG_SHINY_RATE = 12;
export const SAME_SPECIES_EGG_HA_RATE = 8;
export const MANAPHY_EGG_MANAPHY_RATE = 8;
export const GACHA_EGG_HA_RATE = 192;
// 1/x for legendary eggs, 1/x*2 for epic eggs, 1/x*4 for rare eggs, and 1/x*8 for common eggs
export const GACHA_DEFAULT_RARE_EGGMOVE_RATE = 6;
export const SAME_SPECIES_EGG_RARE_EGGMOVE_RATE = 3;
export const GACHA_MOVE_UP_RARE_EGGMOVE_RATE = 3;
// #region Variant properties
// The chance x/10 of a shiny being a variant, then of being specifically an epic variant
export const SHINY_VARIANT_CHANCE = 4;
export const SHINY_EPIC_CHANCE = 1;

View File

@ -0,0 +1,671 @@
import { Species } from "#enums/species";
export const POKERUS_STARTER_COUNT = 5;
/**
* Function to get the cumulative friendship threshold at which a candy is earned
* @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts}
* @returns aforementioned threshold
*/
export function getStarterValueFriendshipCap(starterCost: number): number {
switch (starterCost) {
case 1:
return 20;
case 2:
return 40;
case 3:
return 60;
case 4:
return 100;
case 5:
return 140;
case 6:
return 200;
case 7:
return 280;
case 8:
case 9:
return 450;
default:
return 600;
}
}
export const speciesStarterCosts = {
[Species.BULBASAUR]: 3,
[Species.CHARMANDER]: 3,
[Species.SQUIRTLE]: 3,
[Species.CATERPIE]: 2,
[Species.WEEDLE]: 1,
[Species.PIDGEY]: 1,
[Species.RATTATA]: 1,
[Species.SPEAROW]: 1,
[Species.EKANS]: 2,
[Species.PIKACHU]: 3,
[Species.SANDSHREW]: 2,
[Species.NIDORAN_F]: 3,
[Species.NIDORAN_M]: 3,
[Species.CLEFAIRY]: 3,
[Species.VULPIX]: 3,
[Species.JIGGLYPUFF]: 2,
[Species.ZUBAT]: 3,
[Species.ODDISH]: 3,
[Species.PARAS]: 2,
[Species.VENONAT]: 2,
[Species.DIGLETT]: 2,
[Species.MEOWTH]: 3,
[Species.PSYDUCK]: 2,
[Species.MANKEY]: 4,
[Species.GROWLITHE]: 4,
[Species.POLIWAG]: 2,
[Species.ABRA]: 4,
[Species.MACHOP]: 3,
[Species.BELLSPROUT]: 2,
[Species.TENTACOOL]: 3,
[Species.GEODUDE]: 3,
[Species.PONYTA]: 2,
[Species.SLOWPOKE]: 3,
[Species.MAGNEMITE]: 4,
[Species.FARFETCHD]: 2,
[Species.DODUO]: 3,
[Species.SEEL]: 1,
[Species.GRIMER]: 2,
[Species.SHELLDER]: 5,
[Species.GASTLY]: 4,
[Species.ONIX]: 3,
[Species.DROWZEE]: 2,
[Species.KRABBY]: 3,
[Species.VOLTORB]: 2,
[Species.EXEGGCUTE]: 3,
[Species.CUBONE]: 3,
[Species.HITMONLEE]: 4,
[Species.HITMONCHAN]: 4,
[Species.LICKITUNG]: 3,
[Species.KOFFING]: 2,
[Species.RHYHORN]: 3,
[Species.CHANSEY]: 3,
[Species.TANGELA]: 3,
[Species.KANGASKHAN]: 4,
[Species.HORSEA]: 3,
[Species.GOLDEEN]: 2,
[Species.STARYU]: 3,
[Species.MR_MIME]: 3,
[Species.SCYTHER]: 5,
[Species.JYNX]: 4,
[Species.ELECTABUZZ]: 4,
[Species.MAGMAR]: 4,
[Species.PINSIR]: 4,
[Species.TAUROS]: 4,
[Species.MAGIKARP]: 4,
[Species.LAPRAS]: 4,
[Species.DITTO]: 2,
[Species.EEVEE]: 3,
[Species.PORYGON]: 4,
[Species.OMANYTE]: 3,
[Species.KABUTO]: 3,
[Species.AERODACTYL]: 5,
[Species.SNORLAX]: 5,
[Species.ARTICUNO]: 6,
[Species.ZAPDOS]: 6,
[Species.MOLTRES]: 6,
[Species.DRATINI]: 4,
[Species.MEWTWO]: 8,
[Species.MEW]: 6,
[Species.CHIKORITA]: 2,
[Species.CYNDAQUIL]: 3,
[Species.TOTODILE]: 3,
[Species.SENTRET]: 1,
[Species.HOOTHOOT]: 2,
[Species.LEDYBA]: 1,
[Species.SPINARAK]: 1,
[Species.CHINCHOU]: 2,
[Species.PICHU]: 2,
[Species.CLEFFA]: 2,
[Species.IGGLYBUFF]: 1,
[Species.TOGEPI]: 3,
[Species.NATU]: 2,
[Species.MAREEP]: 2,
[Species.MARILL]: 4,
[Species.SUDOWOODO]: 3,
[Species.HOPPIP]: 2,
[Species.AIPOM]: 2,
[Species.SUNKERN]: 1,
[Species.YANMA]: 3,
[Species.WOOPER]: 2,
[Species.MURKROW]: 3,
[Species.MISDREAVUS]: 2,
[Species.UNOWN]: 1,
[Species.WOBBUFFET]: 2,
[Species.GIRAFARIG]: 3,
[Species.PINECO]: 2,
[Species.DUNSPARCE]: 3,
[Species.GLIGAR]: 3,
[Species.SNUBBULL]: 2,
[Species.QWILFISH]: 3,
[Species.SHUCKLE]: 3,
[Species.HERACROSS]: 5,
[Species.SNEASEL]: 4,
[Species.TEDDIURSA]: 4,
[Species.SLUGMA]: 2,
[Species.SWINUB]: 3,
[Species.CORSOLA]: 2,
[Species.REMORAID]: 2,
[Species.DELIBIRD]: 2,
[Species.MANTINE]: 3,
[Species.SKARMORY]: 4,
[Species.HOUNDOUR]: 3,
[Species.PHANPY]: 3,
[Species.STANTLER]: 3,
[Species.SMEARGLE]: 1,
[Species.TYROGUE]: 3,
[Species.SMOOCHUM]: 3,
[Species.ELEKID]: 3,
[Species.MAGBY]: 3,
[Species.MILTANK]: 4,
[Species.RAIKOU]: 6,
[Species.ENTEI]: 6,
[Species.SUICUNE]: 6,
[Species.LARVITAR]: 4,
[Species.LUGIA]: 8,
[Species.HO_OH]: 8,
[Species.CELEBI]: 6,
[Species.TREECKO]: 3,
[Species.TORCHIC]: 4,
[Species.MUDKIP]: 3,
[Species.POOCHYENA]: 2,
[Species.ZIGZAGOON]: 2,
[Species.WURMPLE]: 1,
[Species.LOTAD]: 3,
[Species.SEEDOT]: 2,
[Species.TAILLOW]: 3,
[Species.WINGULL]: 2,
[Species.RALTS]: 3,
[Species.SURSKIT]: 2,
[Species.SHROOMISH]: 3,
[Species.SLAKOTH]: 4,
[Species.NINCADA]: 4,
[Species.WHISMUR]: 2,
[Species.MAKUHITA]: 3,
[Species.AZURILL]: 4,
[Species.NOSEPASS]: 2,
[Species.SKITTY]: 1,
[Species.SABLEYE]: 2,
[Species.MAWILE]: 3,
[Species.ARON]: 3,
[Species.MEDITITE]: 3,
[Species.ELECTRIKE]: 2,
[Species.PLUSLE]: 2,
[Species.MINUN]: 2,
[Species.VOLBEAT]: 2,
[Species.ILLUMISE]: 2,
[Species.ROSELIA]: 3,
[Species.GULPIN]: 1,
[Species.CARVANHA]: 3,
[Species.WAILMER]: 2,
[Species.NUMEL]: 2,
[Species.TORKOAL]: 3,
[Species.SPOINK]: 2,
[Species.SPINDA]: 1,
[Species.TRAPINCH]: 3,
[Species.CACNEA]: 2,
[Species.SWABLU]: 2,
[Species.ZANGOOSE]: 4,
[Species.SEVIPER]: 3,
[Species.LUNATONE]: 3,
[Species.SOLROCK]: 3,
[Species.BARBOACH]: 2,
[Species.CORPHISH]: 3,
[Species.BALTOY]: 2,
[Species.LILEEP]: 3,
[Species.ANORITH]: 3,
[Species.FEEBAS]: 4,
[Species.CASTFORM]: 1,
[Species.KECLEON]: 2,
[Species.SHUPPET]: 2,
[Species.DUSKULL]: 3,
[Species.TROPIUS]: 3,
[Species.CHIMECHO]: 3,
[Species.ABSOL]: 4,
[Species.WYNAUT]: 2,
[Species.SNORUNT]: 2,
[Species.SPHEAL]: 2,
[Species.CLAMPERL]: 3,
[Species.RELICANTH]: 3,
[Species.LUVDISC]: 1,
[Species.BAGON]: 4,
[Species.BELDUM]: 4,
[Species.REGIROCK]: 6,
[Species.REGICE]: 6,
[Species.REGISTEEL]: 6,
[Species.LATIAS]: 7,
[Species.LATIOS]: 7,
[Species.KYOGRE]: 9,
[Species.GROUDON]: 9,
[Species.RAYQUAZA]: 9,
[Species.JIRACHI]: 7,
[Species.DEOXYS]: 7,
[Species.TURTWIG]: 3,
[Species.CHIMCHAR]: 3,
[Species.PIPLUP]: 3,
[Species.STARLY]: 3,
[Species.BIDOOF]: 2,
[Species.KRICKETOT]: 1,
[Species.SHINX]: 2,
[Species.BUDEW]: 3,
[Species.CRANIDOS]: 3,
[Species.SHIELDON]: 3,
[Species.BURMY]: 2,
[Species.COMBEE]: 2,
[Species.PACHIRISU]: 2,
[Species.BUIZEL]: 2,
[Species.CHERUBI]: 1,
[Species.SHELLOS]: 3,
[Species.DRIFLOON]: 2,
[Species.BUNEARY]: 2,
[Species.GLAMEOW]: 2,
[Species.CHINGLING]: 2,
[Species.STUNKY]: 2,
[Species.BRONZOR]: 3,
[Species.BONSLY]: 2,
[Species.MIME_JR]: 2,
[Species.HAPPINY]: 2,
[Species.CHATOT]: 2,
[Species.SPIRITOMB]: 4,
[Species.GIBLE]: 4,
[Species.MUNCHLAX]: 4,
[Species.RIOLU]: 3,
[Species.HIPPOPOTAS]: 3,
[Species.SKORUPI]: 3,
[Species.CROAGUNK]: 2,
[Species.CARNIVINE]: 2,
[Species.FINNEON]: 1,
[Species.MANTYKE]: 2,
[Species.SNOVER]: 2,
[Species.ROTOM]: 5,
[Species.UXIE]: 6,
[Species.MESPRIT]: 6,
[Species.AZELF]: 6,
[Species.DIALGA]: 8,
[Species.PALKIA]: 8,
[Species.HEATRAN]: 6,
[Species.REGIGIGAS]: 7,
[Species.GIRATINA]: 8,
[Species.CRESSELIA]: 6,
[Species.PHIONE]: 4,
[Species.MANAPHY]: 7,
[Species.DARKRAI]: 7,
[Species.SHAYMIN]: 6,
[Species.ARCEUS]: 9,
[Species.VICTINI]: 7,
[Species.SNIVY]: 3,
[Species.TEPIG]: 3,
[Species.OSHAWOTT]: 3,
[Species.PATRAT]: 1,
[Species.LILLIPUP]: 3,
[Species.PURRLOIN]: 2,
[Species.PANSAGE]: 2,
[Species.PANSEAR]: 2,
[Species.PANPOUR]: 2,
[Species.MUNNA]: 2,
[Species.PIDOVE]: 1,
[Species.BLITZLE]: 2,
[Species.ROGGENROLA]: 3,
[Species.WOOBAT]: 3,
[Species.DRILBUR]: 4,
[Species.AUDINO]: 3,
[Species.TIMBURR]: 4,
[Species.TYMPOLE]: 3,
[Species.THROH]: 4,
[Species.SAWK]: 4,
[Species.SEWADDLE]: 2,
[Species.VENIPEDE]: 3,
[Species.COTTONEE]: 3,
[Species.PETILIL]: 3,
[Species.BASCULIN]: 4,
[Species.SANDILE]: 4,
[Species.DARUMAKA]: 4,
[Species.MARACTUS]: 2,
[Species.DWEBBLE]: 2,
[Species.SCRAGGY]: 3,
[Species.SIGILYPH]: 4,
[Species.YAMASK]: 3,
[Species.TIRTOUGA]: 3,
[Species.ARCHEN]: 3,
[Species.TRUBBISH]: 2,
[Species.ZORUA]: 3,
[Species.MINCCINO]: 3,
[Species.GOTHITA]: 3,
[Species.SOLOSIS]: 3,
[Species.DUCKLETT]: 2,
[Species.VANILLITE]: 3,
[Species.DEERLING]: 2,
[Species.EMOLGA]: 2,
[Species.KARRABLAST]: 3,
[Species.FOONGUS]: 2,
[Species.FRILLISH]: 3,
[Species.ALOMOMOLA]: 4,
[Species.JOLTIK]: 3,
[Species.FERROSEED]: 3,
[Species.KLINK]: 3,
[Species.TYNAMO]: 2,
[Species.ELGYEM]: 2,
[Species.LITWICK]: 3,
[Species.AXEW]: 4,
[Species.CUBCHOO]: 2,
[Species.CRYOGONAL]: 4,
[Species.SHELMET]: 2,
[Species.STUNFISK]: 3,
[Species.MIENFOO]: 3,
[Species.DRUDDIGON]: 4,
[Species.GOLETT]: 3,
[Species.PAWNIARD]: 4,
[Species.BOUFFALANT]: 4,
[Species.RUFFLET]: 3,
[Species.VULLABY]: 3,
[Species.HEATMOR]: 3,
[Species.DURANT]: 4,
[Species.DEINO]: 4,
[Species.LARVESTA]: 4,
[Species.COBALION]: 6,
[Species.TERRAKION]: 6,
[Species.VIRIZION]: 6,
[Species.TORNADUS]: 7,
[Species.THUNDURUS]: 7,
[Species.RESHIRAM]: 8,
[Species.ZEKROM]: 8,
[Species.LANDORUS]: 7,
[Species.KYUREM]: 8,
[Species.KELDEO]: 6,
[Species.MELOETTA]: 7,
[Species.GENESECT]: 6,
[Species.CHESPIN]: 3,
[Species.FENNEKIN]: 3,
[Species.FROAKIE]: 4,
[Species.BUNNELBY]: 3,
[Species.FLETCHLING]: 3,
[Species.SCATTERBUG]: 2,
[Species.LITLEO]: 2,
[Species.FLABEBE]: 3,
[Species.SKIDDO]: 2,
[Species.PANCHAM]: 3,
[Species.FURFROU]: 3,
[Species.ESPURR]: 2,
[Species.HONEDGE]: 4,
[Species.SPRITZEE]: 2,
[Species.SWIRLIX]: 3,
[Species.INKAY]: 3,
[Species.BINACLE]: 3,
[Species.SKRELP]: 2,
[Species.CLAUNCHER]: 3,
[Species.HELIOPTILE]: 3,
[Species.TYRUNT]: 3,
[Species.AMAURA]: 3,
[Species.HAWLUCHA]: 4,
[Species.DEDENNE]: 2,
[Species.CARBINK]: 2,
[Species.GOOMY]: 4,
[Species.KLEFKI]: 3,
[Species.PHANTUMP]: 2,
[Species.PUMPKABOO]: 2,
[Species.BERGMITE]: 3,
[Species.NOIBAT]: 3,
[Species.XERNEAS]: 8,
[Species.YVELTAL]: 8,
[Species.ZYGARDE]: 8,
[Species.DIANCIE]: 7,
[Species.HOOPA]: 7,
[Species.VOLCANION]: 6,
[Species.ETERNAL_FLOETTE]: 4,
[Species.ROWLET]: 3,
[Species.LITTEN]: 3,
[Species.POPPLIO]: 4,
[Species.PIKIPEK]: 2,
[Species.YUNGOOS]: 2,
[Species.GRUBBIN]: 3,
[Species.CRABRAWLER]: 3,
[Species.ORICORIO]: 3,
[Species.CUTIEFLY]: 3,
[Species.ROCKRUFF]: 3,
[Species.WISHIWASHI]: 2,
[Species.MAREANIE]: 2,
[Species.MUDBRAY]: 3,
[Species.DEWPIDER]: 3,
[Species.FOMANTIS]: 2,
[Species.MORELULL]: 2,
[Species.SALANDIT]: 3,
[Species.STUFFUL]: 3,
[Species.BOUNSWEET]: 3,
[Species.COMFEY]: 4,
[Species.ORANGURU]: 4,
[Species.PASSIMIAN]: 4,
[Species.WIMPOD]: 3,
[Species.SANDYGAST]: 3,
[Species.PYUKUMUKU]: 2,
[Species.TYPE_NULL]: 5,
[Species.MINIOR]: 4,
[Species.KOMALA]: 3,
[Species.TURTONATOR]: 4,
[Species.TOGEDEMARU]: 3,
[Species.MIMIKYU]: 4,
[Species.BRUXISH]: 4,
[Species.DRAMPA]: 4,
[Species.DHELMISE]: 4,
[Species.JANGMO_O]: 4,
[Species.TAPU_KOKO]: 6,
[Species.TAPU_LELE]: 6,
[Species.TAPU_BULU]: 6,
[Species.TAPU_FINI]: 6,
[Species.COSMOG]: 7,
[Species.NIHILEGO]: 6,
[Species.BUZZWOLE]: 6,
[Species.PHEROMOSA]: 7,
[Species.XURKITREE]: 6,
[Species.CELESTEELA]: 6,
[Species.KARTANA]: 7,
[Species.GUZZLORD]: 6,
[Species.NECROZMA]: 8,
[Species.MAGEARNA]: 7,
[Species.MARSHADOW]: 7,
[Species.POIPOLE]: 7,
[Species.STAKATAKA]: 6,
[Species.BLACEPHALON]: 7,
[Species.ZERAORA]: 6,
[Species.MELTAN]: 6,
[Species.ALOLA_RATTATA]: 1,
[Species.ALOLA_SANDSHREW]: 2,
[Species.ALOLA_VULPIX]: 3,
[Species.ALOLA_DIGLETT]: 2,
[Species.ALOLA_MEOWTH]: 3,
[Species.ALOLA_GEODUDE]: 3,
[Species.ALOLA_GRIMER]: 3,
[Species.GROOKEY]: 3,
[Species.SCORBUNNY]: 4,
[Species.SOBBLE]: 3,
[Species.SKWOVET]: 2,
[Species.ROOKIDEE]: 3,
[Species.BLIPBUG]: 2,
[Species.NICKIT]: 1,
[Species.GOSSIFLEUR]: 2,
[Species.WOOLOO]: 2,
[Species.CHEWTLE]: 3,
[Species.YAMPER]: 2,
[Species.ROLYCOLY]: 3,
[Species.APPLIN]: 3,
[Species.SILICOBRA]: 3,
[Species.CRAMORANT]: 3,
[Species.ARROKUDA]: 3,
[Species.TOXEL]: 3,
[Species.SIZZLIPEDE]: 3,
[Species.CLOBBOPUS]: 2,
[Species.SINISTEA]: 3,
[Species.HATENNA]: 3,
[Species.IMPIDIMP]: 3,
[Species.MILCERY]: 3,
[Species.FALINKS]: 4,
[Species.PINCURCHIN]: 3,
[Species.SNOM]: 3,
[Species.STONJOURNER]: 3,
[Species.EISCUE]: 3,
[Species.INDEEDEE]: 4,
[Species.MORPEKO]: 3,
[Species.CUFANT]: 3,
[Species.DRACOZOLT]: 5,
[Species.ARCTOZOLT]: 4,
[Species.DRACOVISH]: 5,
[Species.ARCTOVISH]: 4,
[Species.DURALUDON]: 5,
[Species.DREEPY]: 4,
[Species.ZACIAN]: 9,
[Species.ZAMAZENTA]: 8,
[Species.ETERNATUS]: 10,
[Species.KUBFU]: 6,
[Species.ZARUDE]: 6,
[Species.REGIELEKI]: 6,
[Species.REGIDRAGO]: 6,
[Species.GLASTRIER]: 6,
[Species.SPECTRIER]: 7,
[Species.CALYREX]: 8,
[Species.GALAR_MEOWTH]: 3,
[Species.GALAR_PONYTA]: 2,
[Species.GALAR_SLOWPOKE]: 3,
[Species.GALAR_FARFETCHD]: 3,
[Species.GALAR_CORSOLA]: 3,
[Species.GALAR_ZIGZAGOON]: 3,
[Species.GALAR_DARUMAKA]: 4,
[Species.GALAR_YAMASK]: 3,
[Species.GALAR_STUNFISK]: 2,
[Species.GALAR_MR_MIME]: 3,
[Species.GALAR_ARTICUNO]: 6,
[Species.GALAR_ZAPDOS]: 6,
[Species.GALAR_MOLTRES]: 6,
[Species.HISUI_GROWLITHE]: 4,
[Species.HISUI_VOLTORB]: 3,
[Species.HISUI_QWILFISH]: 4,
[Species.HISUI_SNEASEL]: 5,
[Species.HISUI_ZORUA]: 3,
[Species.ENAMORUS]: 7,
[Species.SPRIGATITO]: 4,
[Species.FUECOCO]: 4,
[Species.QUAXLY]: 4,
[Species.LECHONK]: 2,
[Species.TAROUNTULA]: 1,
[Species.NYMBLE]: 3,
[Species.PAWMI]: 3,
[Species.TANDEMAUS]: 4,
[Species.FIDOUGH]: 2,
[Species.SMOLIV]: 3,
[Species.SQUAWKABILLY]: 2,
[Species.NACLI]: 4,
[Species.CHARCADET]: 4,
[Species.TADBULB]: 3,
[Species.WATTREL]: 3,
[Species.MASCHIFF]: 3,
[Species.SHROODLE]: 2,
[Species.BRAMBLIN]: 3,
[Species.TOEDSCOOL]: 3,
[Species.KLAWF]: 3,
[Species.CAPSAKID]: 3,
[Species.RELLOR]: 2,
[Species.FLITTLE]: 3,
[Species.TINKATINK]: 4,
[Species.WIGLETT]: 2,
[Species.BOMBIRDIER]: 3,
[Species.FINIZEN]: 3,
[Species.VAROOM]: 4,
[Species.CYCLIZAR]: 4,
[Species.ORTHWORM]: 4,
[Species.GLIMMET]: 4,
[Species.GREAVARD]: 3,
[Species.FLAMIGO]: 4,
[Species.CETODDLE]: 3,
[Species.VELUZA]: 4,
[Species.DONDOZO]: 4,
[Species.TATSUGIRI]: 4,
[Species.GREAT_TUSK]: 6,
[Species.SCREAM_TAIL]: 6,
[Species.BRUTE_BONNET]: 6,
[Species.FLUTTER_MANE]: 7,
[Species.SLITHER_WING]: 6,
[Species.SANDY_SHOCKS]: 6,
[Species.IRON_TREADS]: 6,
[Species.IRON_BUNDLE]: 6,
[Species.IRON_HANDS]: 6,
[Species.IRON_JUGULIS]: 6,
[Species.IRON_MOTH]: 6,
[Species.IRON_THORNS]: 6,
[Species.FRIGIBAX]: 4,
[Species.GIMMIGHOUL]: 4,
[Species.WO_CHIEN]: 6,
[Species.CHIEN_PAO]: 7,
[Species.TING_LU]: 6,
[Species.CHI_YU]: 7,
[Species.ROARING_MOON]: 6,
[Species.IRON_VALIANT]: 6,
[Species.KORAIDON]: 9,
[Species.MIRAIDON]: 9,
[Species.WALKING_WAKE]: 6,
[Species.IRON_LEAVES]: 6,
[Species.POLTCHAGEIST]: 4,
[Species.OKIDOGI]: 6,
[Species.MUNKIDORI]: 6,
[Species.FEZANDIPITI]: 6,
[Species.OGERPON]: 7,
[Species.GOUGING_FIRE]: 7,
[Species.RAGING_BOLT]: 6,
[Species.IRON_BOULDER]: 7,
[Species.IRON_CROWN]: 6,
[Species.TERAPAGOS]: 8,
[Species.PECHARUNT]: 6,
[Species.PALDEA_TAUROS]: 5,
[Species.PALDEA_WOOPER]: 3,
[Species.BLOODMOON_URSALUNA]: 6,
};
const starterCandyCosts: { passive: number; costReduction: [number, number]; egg: number; }[] = [
{ passive: 40, costReduction: [ 25, 60 ], egg: 30 }, // 1 Cost
{ passive: 40, costReduction: [ 25, 60 ], egg: 30 }, // 2 Cost
{ passive: 35, costReduction: [ 20, 50 ], egg: 25 }, // 3 Cost
{ passive: 30, costReduction: [ 15, 40 ], egg: 20 }, // 4 Cost
{ passive: 25, costReduction: [ 12, 35 ], egg: 18 }, // 5 Cost
{ passive: 20, costReduction: [ 10, 30 ], egg: 15 }, // 6 Cost
{ passive: 15, costReduction: [ 8, 20 ], egg: 12 }, // 7 Cost
{ passive: 10, costReduction: [ 5, 15 ], egg: 10 }, // 8 Cost
{ passive: 10, costReduction: [ 5, 15 ], egg: 10 }, // 9 Cost
{ passive: 10, costReduction: [ 5, 15 ], egg: 10 }, // 10 Cost
];
/**
* Getter for {@linkcode starterCandyCosts} for passive unlock candy cost based on initial point cost
* @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts}
* @returns the candy cost for passive unlock
*/
export function getPassiveCandyCount(starterCost: number): number {
return starterCandyCosts[starterCost - 1].passive;
}
/**
* Getter for {@linkcode starterCandyCosts} for value reduction unlock candy cost based on initial point cost
* @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts}
* @returns respective candy cost for the two cost reductions as an array 2 numbers
*/
export function getValueReductionCandyCounts(starterCost: number): [number, number] {
return starterCandyCosts[starterCost - 1].costReduction;
}
/**
* Getter for {@linkcode starterCandyCosts} for egg purchase candy cost based on initial point cost
* @param starterCost the default point cost of the starter found in {@linkcode speciesStarterCosts}
* @returns the candy cost for the purchasable egg
*/
export function getSameSpeciesEggCandyCounts(starterCost: number): number {
return starterCandyCosts[starterCost - 1].egg;
}

View File

@ -1,4 +1,4 @@
import { ModifierTier } from "../modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";

View File

@ -2123,7 +2123,36 @@ export class StockpilingTag extends BattlerTag {
*/ */
export class GulpMissileTag extends BattlerTag { export class GulpMissileTag extends BattlerTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) { constructor(tagType: BattlerTagType, sourceMove: Moves) {
super(tagType, BattlerTagLapseType.CUSTOM, 0, sourceMove); super(tagType, BattlerTagLapseType.HIT, 0, sourceMove);
}
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (pokemon.getTag(BattlerTagType.UNDERWATER)) {
return true;
}
const moveEffectPhase = pokemon.scene.getCurrentPhase();
if (moveEffectPhase instanceof MoveEffectPhase) {
const attacker = moveEffectPhase.getUserPokemon();
if (!attacker) {
return false;
}
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), HitResult.OTHER);
}
if (this.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ Stat.DEF ], -1));
} else {
attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon);
}
}
return false;
} }
/** /**
@ -2238,7 +2267,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
* Uses DisabledTag's selectionDeniedText() message * Uses DisabledTag's selectionDeniedText() message
*/ */
override selectionDeniedText(pokemon: Pokemon, move: Moves): string { override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name, healBlockName: allMoves[Moves.HEAL_BLOCK].name });
} }
/** /**
@ -2248,7 +2277,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
* @returns {string} text to display when the move is interrupted * @returns {string} text to display when the move is interrupted
*/ */
override interruptedText(pokemon: Pokemon, move: Moves): string { override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name, healBlockName: allMoves[Moves.HEAL_BLOCK].name });
} }
override onRemove(pokemon: Pokemon): void { override onRemove(pokemon: Pokemon): void {
@ -2501,8 +2530,8 @@ export class TormentTag extends MoveRestrictionBattlerTag {
return false; return false;
} }
override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { override selectionDeniedText(pokemon: Pokemon, _move: Moves): string {
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledTorment", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
} }
} }
@ -2530,12 +2559,12 @@ export class TauntTag extends MoveRestrictionBattlerTag {
return allMoves[move].category === MoveCategory.STATUS; return allMoves[move].category === MoveCategory.STATUS;
} }
override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledTaunt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
} }
override interruptedText(pokemon: Pokemon, move: Moves): string { override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledTaunt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
} }
} }
@ -2580,15 +2609,52 @@ export class ImprisonTag extends MoveRestrictionBattlerTag {
return false; return false;
} }
override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledImprison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
} }
override interruptedText(pokemon: Pokemon, move: Moves): string { override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name }); return i18next.t("battle:moveDisabledImprison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
} }
} }
/**
* Battler Tag that applies the effects of Syrup Bomb to the target Pokemon.
* For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1.
* The tag can also expire by taking the target Pokemon off the field.
*/
export class SyrupBombTag extends BattlerTag {
constructor() {
super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB);
}
/**
* Adds the Syrup Bomb battler tag to the target Pokemon.
* @param {Pokemon} pokemon the target Pokemon
*/
override onAdd(pokemon: Pokemon) {
super.onAdd(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
/**
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
* @param {Pokemon} pokemon the target Pokemon
* @param {BattlerTagLapseType} _lapseType
* @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field
*/
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
if (!pokemon.isActive(true)) {
return false;
}
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline
pokemon.scene.unshiftPhase(new StatStageChangePhase(
pokemon.scene, pokemon.getBattlerIndex(), true,
[ Stat.SPD ], -1, true, false, true
));
return --this.turnCount > 0;
}
}
/** /**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
@ -2763,6 +2829,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new TauntTag(); return new TauntTag();
case BattlerTagType.IMPRISON: case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId); return new ImprisonTag(sourceId);
case BattlerTagType.SYRUP_BOMB:
return new SyrupBombTag();
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -1,20 +1,21 @@
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import i18next from "i18next"; import i18next from "i18next";
import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data"; import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species"; import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import Pokemon, { PokemonMove } from "#app/field/pokemon"; import Pokemon, { PokemonMove } from "#app/field/pokemon";
import { BattleType, FixedBattleConfig } from "#app/battle"; import { BattleType, FixedBattleConfig } from "#app/battle";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer, { TrainerVariant } from "#app/field/trainer";
import { GameMode } from "#app/game-mode"; import { GameMode } from "#app/game-mode";
import { Type } from "./type"; import { Type } from "#app/data/type";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { Nature } from "./nature"; import { Nature } from "#app/data/nature";
import { Moves } from "#app/enums/moves"; import { Moves } from "#enums/moves";
import { TypeColor, TypeShadow } from "#app/enums/color"; import { TypeColor, TypeShadow } from "#enums/color";
import { pokemonEvolutions } from "./pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
/** A constant for the default max cost of the starting party before a run */ /** A constant for the default max cost of the starting party before a run */
@ -814,7 +815,7 @@ export class FreshStartChallenge extends Challenge {
applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean {
if (defaultStarterSpecies.includes(species)) { if (defaultStarterSpecies.includes(species)) {
cost.value = speciesStarters[species]; cost.value = speciesStarterCosts[species];
return true; return true;
} }
return false; return false;
@ -896,7 +897,7 @@ export class LowerStarterMaxCostChallenge extends Challenge {
} }
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean {
if (speciesStarters[pokemon.speciesId] > DEFAULT_PARTY_MAX_COST - this.value) { if (speciesStarterCosts[pokemon.speciesId] > DEFAULT_PARTY_MAX_COST - this.value) {
valid.value = false; valid.value = false;
return true; return true;
} }

View File

@ -1,10 +1,11 @@
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "../battle-scene"; import BattleScene from "#app/battle-scene";
import { PlayerPokemon } from "../field/pokemon"; import { PlayerPokemon } from "#app/field/pokemon";
import { Starter } from "../ui/starter-select-ui-handler"; import { Starter } from "#app/ui/starter-select-ui-handler";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species"; import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
export interface DailyRunConfig { export interface DailyRunConfig {
seed: integer; seed: integer;
@ -46,9 +47,9 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[]
for (let c = 0; c < starterCosts.length; c++) { for (let c = 0; c < starterCosts.length; c++) {
const cost = starterCosts[c]; const cost = starterCosts[c];
const costSpecies = Object.keys(speciesStarters) const costSpecies = Object.keys(speciesStarterCosts)
.map(s => parseInt(s) as Species) .map(s => parseInt(s) as Species)
.filter(s => speciesStarters[s] === cost); .filter(s => speciesStarterCosts[s] === cost);
const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies)); const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies));
const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER)); const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER));
starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel)); starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel));

View File

@ -1,30 +1,19 @@
import BattleScene from "../battle-scene"; import BattleScene from "#app/battle-scene";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import { VariantTier } from "../enums/variant-tier"; import { speciesStarterCosts } from "#app/data/balance/starters";
import * as Utils from "../utils"; import { VariantTier } from "#enums/variant-tier";
import * as Utils from "#app/utils";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { pokemonPrevolutions } from "./pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { PlayerPokemon } from "#app/field/pokemon"; import { PlayerPokemon } from "#app/field/pokemon";
import i18next from "i18next"; import i18next from "i18next";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { EggSourceType } from "#app/enums/egg-source-types"; import { EggSourceType } from "#enums/egg-source-types";
import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates";
export const EGG_SEED = 1073741824; export const EGG_SEED = 1073741824;
// Rates for specific random properties in 1/x
const DEFAULT_SHINY_RATE = 128;
const GACHA_SHINY_UP_SHINY_RATE = 64;
const SAME_SPECIES_EGG_SHINY_RATE = 12;
const SAME_SPECIES_EGG_HA_RATE = 8;
const MANAPHY_EGG_MANAPHY_RATE = 8;
const GACHA_EGG_HA_RATE = 192;
// 1/x for legendary eggs, 1/x*2 for epic eggs, 1/x*4 for rare eggs, and 1/x*8 for common eggs
const DEFAULT_RARE_EGGMOVE_RATE = 6;
const SAME_SPECIES_EGG_RARE_EGGMOVE_RATE = 3;
const GACHA_MOVE_UP_RARE_EGGMOVE_RATE = 3;
/** Egg options to override egg properties */ /** Egg options to override egg properties */
export interface IEggOptions { export interface IEggOptions {
/** Id. Used to check if egg type will be manaphy (id % 204 === 0) */ /** Id. Used to check if egg type will be manaphy (id % 204 === 0) */
@ -323,7 +312,7 @@ export class Egg {
//// ////
private rollEggMoveIndex() { private rollEggMoveIndex() {
let baseChance = DEFAULT_RARE_EGGMOVE_RATE; let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE;
switch (this._sourceType) { switch (this._sourceType) {
case EggSourceType.SAME_SPECIES_EGG: case EggSourceType.SAME_SPECIES_EGG:
baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE;
@ -341,24 +330,24 @@ export class Egg {
private getEggTierDefaultHatchWaves(eggTier?: EggTier): number { private getEggTierDefaultHatchWaves(eggTier?: EggTier): number {
if (this._species === Species.PHIONE || this._species === Species.MANAPHY) { if (this._species === Species.PHIONE || this._species === Species.MANAPHY) {
return 50; return HATCH_WAVES_MANAPHY_EGG;
} }
switch (eggTier ?? this._tier) { switch (eggTier ?? this._tier) {
case EggTier.COMMON: case EggTier.COMMON:
return 10; return HATCH_WAVES_COMMON_EGG;
case EggTier.GREAT: case EggTier.GREAT:
return 25; return HATCH_WAVES_RARE_EGG;
case EggTier.ULTRA: case EggTier.ULTRA:
return 50; return HATCH_WAVES_EPIC_EGG;
} }
return 100; return HATCH_WAVES_LEGENDARY_EGG;
} }
private rollEggTier(): EggTier { private rollEggTier(): EggTier {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? 1 : 0; const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
const tierValue = Utils.randInt(256); const tierValue = Utils.randInt(256);
return tierValue >= 52 + tierValueOffset ? EggTier.COMMON : tierValue >= 8 + tierValueOffset ? EggTier.GREAT : tierValue >= 1 + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
} }
private rollSpecies(scene: BattleScene): Species | null { private rollSpecies(scene: BattleScene): Species | null {
@ -409,8 +398,8 @@ export class Egg {
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
let speciesPool = Object.keys(speciesStarters) let speciesPool = Object.keys(speciesStarterCosts)
.filter(s => speciesStarters[s] >= minStarterValue && speciesStarters[s] <= maxStarterValue) .filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue)
.map(s => parseInt(s) as Species) .map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
@ -441,7 +430,7 @@ export class Egg {
let totalWeight = 0; let totalWeight = 0;
const speciesWeights : number[] = []; const speciesWeights : number[] = [];
for (const speciesId of speciesPool) { for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarters[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId); const species = getPokemonSpecies(speciesId);
if (species.isRegional()) { if (species.isRegional()) {
weight = Math.floor(weight / 2); weight = Math.floor(weight / 2);
@ -475,7 +464,7 @@ export class Egg {
* @returns True if the egg is shiny * @returns True if the egg is shiny
**/ **/
private rollShiny(): boolean { private rollShiny(): boolean {
let shinyChance = DEFAULT_SHINY_RATE; let shinyChance = GACHA_DEFAULT_SHINY_RATE;
switch (this._sourceType) { switch (this._sourceType) {
case EggSourceType.GACHA_SHINY: case EggSourceType.GACHA_SHINY:
shinyChance = GACHA_SHINY_UP_SHINY_RATE; shinyChance = GACHA_SHINY_UP_SHINY_RATE;
@ -498,9 +487,9 @@ export class Egg {
} }
const rand = Utils.randSeedInt(10); const rand = Utils.randSeedInt(10);
if (rand >= 4) { if (rand >= SHINY_VARIANT_CHANCE) {
return VariantTier.STANDARD; // 6/10 return VariantTier.STANDARD; // 6/10
} else if (rand >= 1) { } else if (rand >= SHINY_EPIC_CHANCE) {
return VariantTier.RARE; // 3/10 return VariantTier.RARE; // 3/10
} else { } else {
return VariantTier.EPIC; // 1/10 return VariantTier.EPIC; // 1/10
@ -508,16 +497,16 @@ export class Egg {
} }
private checkForPityTierOverrides(scene: BattleScene): void { private checkForPityTierOverrides(scene: BattleScene): void {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? 1 : 0; const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
scene.gameData.eggPity[EggTier.GREAT] += 1; scene.gameData.eggPity[EggTier.GREAT] += 1;
scene.gameData.eggPity[EggTier.ULTRA] += 1; scene.gameData.eggPity[EggTier.ULTRA] += 1;
scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset;
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
if (scene.gameData.eggPity[EggTier.MASTER] >= 412 && this._tier === EggTier.COMMON) { if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.MASTER; this._tier = EggTier.MASTER;
} else if (scene.gameData.eggPity[EggTier.ULTRA] >= 59 && this._tier === EggTier.COMMON) { } else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.ULTRA; this._tier = EggTier.ULTRA;
} else if (scene.gameData.eggPity[EggTier.GREAT] >= 9 && this._tier === EggTier.COMMON) { } else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.GREAT; this._tier = EggTier.GREAT;
} }
scene.gameData.eggPity[this._tier] = 0; scene.gameData.eggPity[this._tier] = 0;
@ -544,7 +533,7 @@ export class Egg {
} }
private getEggTierFromSpeciesStarterValue(): EggTier { private getEggTierFromSpeciesStarterValue(): EggTier {
const speciesStartValue = speciesStarters[this.species]; const speciesStartValue = speciesStarterCosts[this.species];
if (speciesStartValue >= 1 && speciesStartValue <= 3) { if (speciesStartValue >= 1 && speciesStartValue <= 3) {
return EggTier.COMMON; return EggTier.COMMON;
} }
@ -567,7 +556,7 @@ export class Egg {
} }
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
const legendarySpecies = Object.entries(speciesStarters) const legendarySpecies = Object.entries(speciesStarterCosts)
.filter(s => s[1] >= 8 && s[1] <= 9) .filter(s => s[1] >= 8 && s[1] <= 9)
.map(s => parseInt(s[0])) .map(s => parseInt(s[0]))
.filter(s => getPokemonSpecies(s).isObtainable()); .filter(s => getPokemonSpecies(s).isObtainable());
@ -594,7 +583,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
* @returns The egg tier of a given pokemon species * @returns The egg tier of a given pokemon species
*/ */
export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier { export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier {
const speciesBaseValue = speciesStarters[pokemonSpecies.getRootSpeciesId()]; const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()];
if (speciesBaseValue <= 3) { if (speciesBaseValue <= 3) {
return EggTier.COMMON; return EggTier.COMMON;
} else if (speciesBaseValue <= 5) { } else if (speciesBaseValue <= 5) {

View File

@ -25,7 +25,6 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { MoveUsedEvent } from "#app/events/battle-scene"; import { MoveUsedEvent } from "#app/events/battle-scene";
import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat"; import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat";
import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
@ -34,6 +33,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
import { GameMode } from "#app/game-mode"; import { GameMode } from "#app/game-mode";
import { applyChallenges, ChallengeType } from "./challenge"; import { applyChallenges, ChallengeType } from "./challenge";
@ -341,7 +341,8 @@ export default class Move implements Localizable {
* @returns `true` if the move can bypass the target's Substitute; `false` otherwise. * @returns `true` if the move can bypass the target's Substitute; `false` otherwise.
*/ */
hitsSubstitute(user: Pokemon, target: Pokemon | null): boolean { hitsSubstitute(user: Pokemon, target: Pokemon | null): boolean {
if (this.moveTarget === MoveTarget.USER || !target?.getTag(BattlerTagType.SUBSTITUTE)) { if ([ MoveTarget.USER, MoveTarget.USER_SIDE, MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES ].includes(this.moveTarget)
|| !target?.getTag(BattlerTagType.SUBSTITUTE)) {
return false; return false;
} }
@ -398,220 +399,202 @@ export default class Move implements Localizable {
/** /**
* Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move * Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move
* @param makesContact The value (boolean) to set the flag to * @param setFlag Default `true`, set to `false` if the move doesn't make contact
* @see {@linkcode Abilities.STATIC}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
makesContact(makesContact: boolean = true): this { makesContact(setFlag: boolean = true): this {
this.setFlag(MoveFlags.MAKES_CONTACT, makesContact); this.setFlag(MoveFlags.MAKES_CONTACT, setFlag);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_PROTECT} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_PROTECT} flag for the calling Move
* @param ignoresProtect The value (boolean) to set the flag to * @see {@linkcode Moves.CURSE}
* example: @see {@linkcode Moves.CURSE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresProtect(ignoresProtect: boolean = true): this { ignoresProtect(): this {
this.setFlag(MoveFlags.IGNORE_PROTECT, ignoresProtect); this.setFlag(MoveFlags.IGNORE_PROTECT, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move
* @param ignoresVirtual The value (boolean) to set the flag to * @see {@linkcode Moves.NATURE_POWER}
* example: @see {@linkcode Moves.NATURE_POWER}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresVirtual(ignoresVirtual: boolean = true): this { ignoresVirtual(): this {
this.setFlag(MoveFlags.IGNORE_VIRTUAL, ignoresVirtual); this.setFlag(MoveFlags.IGNORE_VIRTUAL, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move * Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move
* @param soundBased The value (boolean) to set the flag to * @see {@linkcode Moves.UPROAR}
* example: @see {@linkcode Moves.UPROAR}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
soundBased(soundBased: boolean = true): this { soundBased(): this {
this.setFlag(MoveFlags.SOUND_BASED, soundBased); this.setFlag(MoveFlags.SOUND_BASED, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.HIDE_USER} flag for the calling Move * Sets the {@linkcode MoveFlags.HIDE_USER} flag for the calling Move
* @param hidesUser The value (boolean) to set the flag to * @see {@linkcode Moves.TELEPORT}
* example: @see {@linkcode Moves.TELEPORT}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
hidesUser(hidesUser: boolean = true): this { hidesUser(): this {
this.setFlag(MoveFlags.HIDE_USER, hidesUser); this.setFlag(MoveFlags.HIDE_USER, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.HIDE_TARGET} flag for the calling Move * Sets the {@linkcode MoveFlags.HIDE_TARGET} flag for the calling Move
* @param hidesTarget The value (boolean) to set the flag to * @see {@linkcode Moves.WHIRLWIND}
* example: @see {@linkcode Moves.WHIRLWIND}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
hidesTarget(hidesTarget: boolean = true): this { hidesTarget(): this {
this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget); this.setFlag(MoveFlags.HIDE_TARGET, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.BITING_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.BITING_MOVE} flag for the calling Move
* @param bitingMove The value (boolean) to set the flag to * @see {@linkcode Moves.BITE}
* example: @see {@linkcode Moves.BITE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
bitingMove(bitingMove: boolean = true): this { bitingMove(): this {
this.setFlag(MoveFlags.BITING_MOVE, bitingMove); this.setFlag(MoveFlags.BITING_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.PULSE_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.PULSE_MOVE} flag for the calling Move
* @param pulseMove The value (boolean) to set the flag to * @see {@linkcode Moves.WATER_PULSE}
* example: @see {@linkcode Moves.WATER_PULSE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
pulseMove(pulseMove: boolean = true): this { pulseMove(): this {
this.setFlag(MoveFlags.PULSE_MOVE, pulseMove); this.setFlag(MoveFlags.PULSE_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.PUNCHING_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.PUNCHING_MOVE} flag for the calling Move
* @param punchingMove The value (boolean) to set the flag to * @see {@linkcode Moves.DRAIN_PUNCH}
* example: @see {@linkcode Moves.DRAIN_PUNCH}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
punchingMove(punchingMove: boolean = true): this { punchingMove(): this {
this.setFlag(MoveFlags.PUNCHING_MOVE, punchingMove); this.setFlag(MoveFlags.PUNCHING_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.SLICING_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.SLICING_MOVE} flag for the calling Move
* @param slicingMove The value (boolean) to set the flag to * @see {@linkcode Moves.X_SCISSOR}
* example: @see {@linkcode Moves.X_SCISSOR}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
slicingMove(slicingMove: boolean = true): this { slicingMove(): this {
this.setFlag(MoveFlags.SLICING_MOVE, slicingMove); this.setFlag(MoveFlags.SLICING_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.RECKLESS_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.RECKLESS_MOVE} flag for the calling Move
* @see {@linkcode Abilities.RECKLESS} * @see {@linkcode Abilities.RECKLESS}
* @param recklessMove The value to set the flag to
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
recklessMove(recklessMove: boolean = true): this { recklessMove(): this {
this.setFlag(MoveFlags.RECKLESS_MOVE, recklessMove); this.setFlag(MoveFlags.RECKLESS_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.BALLBOMB_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.BALLBOMB_MOVE} flag for the calling Move
* @param ballBombMove The value (boolean) to set the flag to * @see {@linkcode Moves.ELECTRO_BALL}
* example: @see {@linkcode Moves.ELECTRO_BALL}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ballBombMove(ballBombMove: boolean = true): this { ballBombMove(): this {
this.setFlag(MoveFlags.BALLBOMB_MOVE, ballBombMove); this.setFlag(MoveFlags.BALLBOMB_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.POWDER_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.POWDER_MOVE} flag for the calling Move
* @param powderMove The value (boolean) to set the flag to * @see {@linkcode Moves.STUN_SPORE}
* example: @see {@linkcode Moves.STUN_SPORE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
powderMove(powderMove: boolean = true): this { powderMove(): this {
this.setFlag(MoveFlags.POWDER_MOVE, powderMove); this.setFlag(MoveFlags.POWDER_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.DANCE_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.DANCE_MOVE} flag for the calling Move
* @param danceMove The value (boolean) to set the flag to * @see {@linkcode Moves.PETAL_DANCE}
* example: @see {@linkcode Moves.PETAL_DANCE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
danceMove(danceMove: boolean = true): this { danceMove(): this {
this.setFlag(MoveFlags.DANCE_MOVE, danceMove); this.setFlag(MoveFlags.DANCE_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.WIND_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.WIND_MOVE} flag for the calling Move
* @param windMove The value (boolean) to set the flag to * @see {@linkcode Moves.HURRICANE}
* example: @see {@linkcode Moves.HURRICANE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
windMove(windMove: boolean = true): this { windMove(): this {
this.setFlag(MoveFlags.WIND_MOVE, windMove); this.setFlag(MoveFlags.WIND_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move * Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move
* @param triageMove The value (boolean) to set the flag to * @see {@linkcode Moves.ABSORB}
* example: @see {@linkcode Moves.ABSORB}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
triageMove(triageMove: boolean = true): this { triageMove(): this {
this.setFlag(MoveFlags.TRIAGE_MOVE, triageMove); this.setFlag(MoveFlags.TRIAGE_MOVE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_ABILITIES} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_ABILITIES} flag for the calling Move
* @param ignoresAbilities sThe value (boolean) to set the flag to * @see {@linkcode Moves.SUNSTEEL_STRIKE}
* example: @see {@linkcode Moves.SUNSTEEL_STRIKE}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresAbilities(ignoresAbilities: boolean = true): this { ignoresAbilities(): this {
this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities); this.setFlag(MoveFlags.IGNORE_ABILITIES, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.CHECK_ALL_HITS} flag for the calling Move * Sets the {@linkcode MoveFlags.CHECK_ALL_HITS} flag for the calling Move
* @param checkAllHits The value (boolean) to set the flag to * @see {@linkcode Moves.TRIPLE_AXEL}
* example: @see {@linkcode Moves.TRIPLE_AXEL}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
checkAllHits(checkAllHits: boolean = true): this { checkAllHits(): this {
this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits); this.setFlag(MoveFlags.CHECK_ALL_HITS, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.IGNORE_SUBSTITUTE} flag for the calling Move * Sets the {@linkcode MoveFlags.IGNORE_SUBSTITUTE} flag for the calling Move
* @param ignoresSubstitute The value (boolean) to set the flag to * @see {@linkcode Moves.WHIRLWIND}
* example: @see {@linkcode Moves.WHIRLWIND}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
ignoresSubstitute(ignoresSubstitute: boolean = true): this { ignoresSubstitute(): this {
this.setFlag(MoveFlags.IGNORE_SUBSTITUTE, ignoresSubstitute); this.setFlag(MoveFlags.IGNORE_SUBSTITUTE, true);
return this; return this;
} }
/** /**
* Sets the {@linkcode MoveFlags.REDIRECT_COUNTER} flag for the calling Move * Sets the {@linkcode MoveFlags.REDIRECT_COUNTER} flag for the calling Move
* @param redirectCounter The value (boolean) to set the flag to * @see {@linkcode Moves.METAL_BURST}
* example: @see {@linkcode Moves.METAL_BURST}
* @returns The {@linkcode Move} that called this function * @returns The {@linkcode Move} that called this function
*/ */
redirectCounter(redirectCounter: boolean = true): this { redirectCounter(): this {
this.setFlag(MoveFlags.REDIRECT_COUNTER, redirectCounter); this.setFlag(MoveFlags.REDIRECT_COUNTER, true);
return this; return this;
} }
@ -1602,12 +1585,31 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
if (!this.canApply(user, target, move, args)) { if (!this.canApply(user, target, move, args)) {
return false; return false;
} }
this.addPartyCurePhase(user); const partyPokemon = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty();
partyPokemon.forEach(p => this.cureStatus(p, user.id));
if (this.message) {
user.scene.queueMessage(this.message);
}
return true; return true;
} }
addPartyCurePhase(user: Pokemon) { /**
user.scene.unshiftPhase(new PartyStatusCurePhase(user.scene, user, this.message, this.abilityCondition)); * Tries to cure the status of the given {@linkcode Pokemon}
* @param pokemon The {@linkcode Pokemon} to cure.
* @param userId The ID of the (move) {@linkcode Pokemon | user}.
*/
public cureStatus(pokemon: Pokemon, userId: number) {
if (!pokemon.isOnField() || pokemon.id === userId) { // user always cures its own status, regardless of ability
pokemon.resetStatus(false);
pokemon.updateInfo();
} else if (!pokemon.hasAbility(this.abilityCondition)) {
pokemon.resetStatus();
pokemon.updateInfo();
} else {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition));
}
} }
} }
@ -2685,20 +2687,42 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
} }
} }
/**
* Attribute used for moves that change stat stages
* @param stats {@linkcode BattleStat} array of stats to be changed
* @param stages stages by which to change the stats, from -6 to 6
* @param selfTarget whether the changes are applied to the user (true) or the target (false)
* @param condition {@linkcode MoveConditionFunc} optional condition to trigger the stat change
* @param firstHitOnly whether the stat change only applies on the first hit of a multi hit move
* @param moveEffectTrigger {@linkcode MoveEffectTrigger} the trigger for the effect to take place
* @param firstTargetOnly whether, if this is a multi target move, to only apply the effect after the first target is hit, rather than once for each target
* @param lastHitOnly whether the effect should only apply after the last hit of a multi hit move
*
* @extends MoveEffectAttr
* @see {@linkcode apply}
*/
export class StatStageChangeAttr extends MoveEffectAttr { export class StatStageChangeAttr extends MoveEffectAttr {
public stats: BattleStat[]; public stats: BattleStat[];
public stages: integer; public stages: integer;
private condition: MoveConditionFunc | null; private condition: MoveConditionFunc | null;
private showMessage: boolean; private showMessage: boolean;
constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false) { constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false) {
super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly); super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly);
this.stats = stats; this.stats = stats;
this.stages = stages; this.stages = stages;
this.condition = condition!; // TODO: is this bang correct? this.condition = condition!; // TODO: is this bang correct?
this.showMessage = showMessage; this.showMessage = showMessage;
} }
/**
* Attempts to change stats of the user or target (depending on value of selfTarget) if conditions are met
* @param user {@linkcode Pokemon} the user of the move
* @param target {@linkcode Pokemon} the target of the move
* @param move {@linkcode Move} the move
* @param args unused
* @returns whether stat stages were changed
*/
apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> { apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise<boolean> {
if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) { if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) {
return false; return false;
@ -3918,7 +3942,14 @@ export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr {
} }
} }
export class TeraBlastCategoryAttr extends VariableMoveCategoryAttr { /**
* Attribute used for tera moves that change category based on the user's Atk and SpAtk stats
* Note: Currently, `getEffectiveStat` does not ignore all abilities that affect stats except those
* with the attribute of `StatMultiplierAbAttr`
* TODO: Remove the `.partial()` tag from Tera Blast and Tera Starstorm when the above issue is resolved
* @extends VariableMoveCategoryAttr
*/
export class TeraMoveCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder); const category = (args[0] as Utils.NumberHolder);
@ -4007,6 +4038,30 @@ export class VariableMoveTypeAttr extends MoveAttr {
} }
} }
/**
* Attribute used for Tera Starstorm that changes the move type to Stellar
* @extends VariableMoveTypeAttr
*/
export class TeraStarstormTypeAttr extends VariableMoveTypeAttr {
/**
*
* @param user the {@linkcode Pokemon} using the move
* @param target n/a
* @param move n/a
* @param args[0] {@linkcode Utils.NumberHolder} the move type
* @returns `true` if the move type is changed to {@linkcode Type.STELLAR}, `false` otherwise
*/
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (user.isTerastallized() && (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS)) {
const moveType = args[0] as Utils.NumberHolder;
moveType.value = Type.STELLAR;
return true;
}
return false;
}
}
export class FormChangeItemTypeAttr extends VariableMoveTypeAttr { export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const moveType = args[0]; const moveType = args[0];
@ -4544,6 +4599,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.DROWSY: case BattlerTagType.DROWSY:
case BattlerTagType.DISABLED: case BattlerTagType.DISABLED:
case BattlerTagType.HEAL_BLOCK: case BattlerTagType.HEAL_BLOCK:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return -5; return -5;
case BattlerTagType.SEEDED: case BattlerTagType.SEEDED:
case BattlerTagType.SALT_CURED: case BattlerTagType.SALT_CURED:
@ -4564,6 +4620,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.ENCORE: case BattlerTagType.ENCORE:
return -2; return -2;
case BattlerTagType.MINIMIZED: case BattlerTagType.MINIMIZED:
case BattlerTagType.ALWAYS_GET_HIT:
return 0; return 0;
case BattlerTagType.INGRAIN: case BattlerTagType.INGRAIN:
case BattlerTagType.IGNORE_ACCURACY: case BattlerTagType.IGNORE_ACCURACY:
@ -5226,6 +5283,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
*/ */
const switchOutTarget = this.selfSwitch ? user : target; const switchOutTarget = this.selfSwitch ? user : target;
if (switchOutTarget instanceof PlayerPokemon) { if (switchOutTarget instanceof PlayerPokemon) {
if (switchOutTarget.scene.getParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
return false;
}
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
if (switchOutTarget.hp > 0) { if (switchOutTarget.hp > 0) {
@ -5234,6 +5294,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
return false; return false;
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) { } else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
return false;
}
// Switch out logic for trainer battles // Switch out logic for trainer battles
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
@ -5244,6 +5307,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
false, false), MoveEndPhase); false, false), MoveEndPhase);
} }
} else { } else {
if (user.scene.currentBattle.waveIndex % 10 === 0) {
return false;
}
// Switch out logic for everything else (eg: WILD battles) // Switch out logic for everything else (eg: WILD battles)
switchOutTarget.leaveField(false); switchOutTarget.leaveField(false);
@ -5339,7 +5405,7 @@ export class ChillyReceptionAttr extends ForceSwitchOutAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
// chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail // chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail
return (user, target, move) => user.scene.arena.trySetWeather(WeatherType.SNOW, true) || super.getSwitchOutCondition()(user, target, move); return (user, target, move) => user.scene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move);
} }
} }
export class RemoveTypeAttr extends MoveEffectAttr { export class RemoveTypeAttr extends MoveEffectAttr {
@ -6580,7 +6646,7 @@ export class MoveCondition {
export class FirstMoveCondition extends MoveCondition { export class FirstMoveCondition extends MoveCondition {
constructor() { constructor() {
super((user, target, move) => user.battleSummonData?.turnCount === 1); super((user, target, move) => user.battleSummonData?.waveTurnCount === 1);
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
@ -7257,7 +7323,7 @@ export function initMoves() {
new StatusMove(Moves.CURSE, Type.GHOST, -1, 10, -1, 0, 2) new StatusMove(Moves.CURSE, Type.GHOST, -1, 10, -1, 0, 2)
.attr(CurseAttr) .attr(CurseAttr)
.ignoresSubstitute() .ignoresSubstitute()
.ignoresProtect(true) .ignoresProtect()
.target(MoveTarget.CURSE), .target(MoveTarget.CURSE),
new AttackMove(Moves.FLAIL, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2) new AttackMove(Moves.FLAIL, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2)
.attr(LowHpPowerAttr), .attr(LowHpPowerAttr),
@ -8897,8 +8963,8 @@ export function initMoves() {
.attr(HalfSacrificialAttr) .attr(HalfSacrificialAttr)
.target(MoveTarget.ALL_NEAR_OTHERS), .target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.PLASMA_FISTS, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 15, -1, 0, 7) new AttackMove(Moves.PLASMA_FISTS, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 15, -1, 0, 7)
.punchingMove() .attr(AddArenaTagAttr, ArenaTagType.PLASMA_FISTS, 1)
.partial(), .punchingMove(),
new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
.attr(PhotonGeyserCategoryAttr) .attr(PhotonGeyserCategoryAttr)
.ignoresAbilities() .ignoresAbilities()
@ -8928,7 +8994,7 @@ export function initMoves() {
.partial() .partial()
.ignoresVirtual(), .ignoresVirtual(),
/* End Unused */ /* End Unused */
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, 100, 2, 7) //LGPE Implementation new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) //LGPE Implementation
.attr(CritOnlyAttr), .attr(CritOnlyAttr),
new AttackMove(Moves.SPLISHY_SPLASH, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 30, 0, 7) new AttackMove(Moves.SPLISHY_SPLASH, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 30, 0, 7)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS) .attr(StatusEffectAttr, StatusEffect.PARALYSIS)
@ -9155,16 +9221,15 @@ export function initMoves() {
.attr(HalfSacrificialAttr), .attr(HalfSacrificialAttr),
new AttackMove(Moves.EXPANDING_FORCE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8) new AttackMove(Moves.EXPANDING_FORCE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1) .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1)
.attr(VariableTargetAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 6 : 3), .attr(VariableTargetAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER),
new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8) new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8)
.attr(ClearTerrainAttr) .attr(ClearTerrainAttr)
.condition((user, target, move) => !!user.scene.arena.terrain), .condition((user, target, move) => !!user.scene.arena.terrain),
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8) new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
//.attr(StatStageChangeAttr, Stat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true, null, true, false, MoveEffectTrigger.HIT, false, true)
//.attr(StatStageChangeAttr, Stat.DEF, -1, true) .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, null, true, false, MoveEffectTrigger.HIT, false, true)
.attr(MultiHitAttr) .attr(MultiHitAttr)
.makesContact(false) .makesContact(false),
.partial(),
new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8) new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8)
.attr(ChargeAttr, ChargeAnim.METEOR_BEAM_CHARGING, i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" }), null, true) .attr(ChargeAttr, ChargeAnim.METEOR_BEAM_CHARGING, i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" }), null, true)
.attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true)
@ -9430,10 +9495,11 @@ export function initMoves() {
.unimplemented(), .unimplemented(),
End Unused */ End Unused */
new AttackMove(Moves.TERA_BLAST, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9) new AttackMove(Moves.TERA_BLAST, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
.attr(TeraBlastCategoryAttr) .attr(TeraMoveCategoryAttr)
.attr(TeraBlastTypeAttr) .attr(TeraBlastTypeAttr)
.attr(TeraBlastPowerAttr) .attr(TeraBlastPowerAttr)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)), .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR))
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9) new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.SILK_TRAP) .attr(ProtectAttr, BattlerTagType.SILK_TRAP)
.condition(failIfLastCondition), .condition(failIfLastCondition),
@ -9613,9 +9679,8 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES) .target(MoveTarget.ALL_NEAR_ENEMIES)
.triageMove(), .triageMove(),
new AttackMove(Moves.SYRUP_BOMB, Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9) new AttackMove(Moves.SYRUP_BOMB, Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9)
.attr(StatStageChangeAttr, [ Stat.SPD ], -1) //Temporary .attr(AddBattlerTagAttr, BattlerTagType.SYRUP_BOMB, false, false, 3)
.ballBombMove() .ballBombMove(),
.partial(),
new AttackMove(Moves.IVY_CUDGEL, Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9) new AttackMove(Moves.IVY_CUDGEL, Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9)
.attr(IvyCudgelTypeAttr) .attr(IvyCudgelTypeAttr)
.attr(HighCritAttr) .attr(HighCritAttr)
@ -9624,8 +9689,10 @@ export function initMoves() {
.attr(ElectroShotChargeAttr) .attr(ElectroShotChargeAttr)
.ignoresVirtual(), .ignoresVirtual(),
new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
.attr(TeraBlastCategoryAttr) .attr(TeraMoveCategoryAttr)
.partial(), .attr(TeraStarstormTypeAttr)
.attr(VariableTargetAttr, (user, target, move) => (user.hasFusionSpecies(Species.TERAPAGOS) || user.species.speciesId === Species.TERAPAGOS) && user.isTerastallized() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER)
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9) new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc) .attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
.attr(DoublePowerChanceAttr), .attr(DoublePowerChanceAttr),

View File

@ -18,7 +18,7 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:aTrainersTest"; const namespace = "mysteryEncounters/aTrainersTest";
/** /**
* A Trainer's Test encounter. * A Trainer's Test encounter.
@ -32,7 +32,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
@ -76,24 +76,24 @@ export const ATrainersTestEncounter: MysteryEncounter =
encounter.dialogue.intro = [ encounter.dialogue.intro = [
{ {
speaker: `trainerNames:${trainerNameKey}`, speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.intro_dialogue` text: `${namespace}:${trainerNameKey}.intro_dialogue`
} }
]; ];
encounter.options[0].dialogue!.selected = [ encounter.options[0].dialogue!.selected = [
{ {
speaker: `trainerNames:${trainerNameKey}`, speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.accept` text: `${namespace}:${trainerNameKey}.accept`
} }
]; ];
encounter.options[1].dialogue!.selected = [ encounter.options[1].dialogue!.selected = [
{ {
speaker: `trainerNames:${trainerNameKey}`, speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.decline` text: `${namespace}:${trainerNameKey}.decline`
} }
]; ];
encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`)); encounter.setDialogueToken("statTrainerName", i18next.t(`trainerNames:${trainerNameKey}`));
const eggDescription = i18next.t(`${namespace}.title`) + ":\n" + i18next.t(`trainerNames:${trainerNameKey}`); const eggDescription = i18next.t(`${namespace}:title`) + ":\n" + i18next.t(`trainerNames:${trainerNameKey}`);
encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription }; encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription };
// Trainer config // Trainer config
@ -128,14 +128,14 @@ export const ATrainersTestEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withIntroDialogue() .withIntroDialogue()
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip` buttonTooltip: `${namespace}:option.1.tooltip`
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -151,15 +151,15 @@ export const ATrainersTestEncounter: MysteryEncounter =
eggDescriptor: encounter.misc.trainerEggDescription, eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.ULTRA tier: EggTier.ULTRA
}; };
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`)); encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
return initBattleWithEnemyConfig(scene, config); return initBattleWithEnemyConfig(scene, config);
} }
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip` buttonTooltip: `${namespace}:option.2.tooltip`
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -173,14 +173,14 @@ export const ATrainersTestEncounter: MysteryEncounter =
eggDescriptor: encounter.misc.trainerEggDescription, eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.GREAT tier: EggTier.GREAT
}; };
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.rare`)); encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} }
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro` text: `${namespace}:outro`
} }
]) ])
.build(); .build();

View File

@ -27,7 +27,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import i18next from "i18next"; import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:absoluteAvarice"; const namespace = "mysteryEncounters/absoluteAvarice";
/** /**
* Absolute Avarice encounter. * Absolute Avarice encounter.
@ -163,12 +163,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -219,7 +219,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
modifierConfigs: bossModifierConfigs, modifierConfigs: bossModifierConfigs,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}:option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
} }
} }
@ -250,11 +250,11 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -274,7 +274,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
scene.addModifier(seedModifier, false, false, false, true); scene.addModifier(seedModifier, false, false, false, true);
} }
}); });
queueEncounterMessage(scene, `${namespace}.option.1.food_stash`); queueEncounterMessage(scene, `${namespace}:option.1.food_stash`);
}; };
setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds); setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
@ -294,11 +294,11 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}) })
@ -336,11 +336,11 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}) })

View File

@ -8,14 +8,15 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse"; const namespace = "mysteryEncounters/anOfferYouCantRefuse";
/** /**
* Money offered starts at base value of Relic Gold, increasing linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased * Money offered starts at base value of Relic Gold, increasing linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased
@ -56,22 +57,22 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true); const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(); const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId();
const starterValue: number = speciesStarters[baseSpecies] ?? 1; const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1;
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER); const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
const price = scene.getWaveMoneyAmount(multiplier); const price = scene.getWaveMoneyAmount(multiplier);
@ -104,12 +105,12 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
], ],
}) })
@ -135,13 +136,13 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
new AbilityRequirement(EXTORTION_ABILITIES)) new AbilityRequirement(EXTORTION_ABILITIES))
) )
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`, disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}) })
@ -159,12 +160,12 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, },

View File

@ -33,7 +33,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:berriesAbound"; const namespace = "mysteryEncounters/berriesAbound";
/** /**
* Berries Abound encounter. * Berries Abound encounter.
@ -50,7 +50,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
.withIntroSpriteConfigs([]) // Set in onInit() .withIntroSpriteConfigs([]) // Set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -110,16 +110,16 @@ export const BerriesAboundEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -129,7 +129,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
const numBerries = encounter.misc.numBerries; const numBerries = encounter.misc.numBerries;
const doBerryRewards = () => { const doBerryRewards = () => {
const berryText = i18next.t(`${namespace}.berries`); const berryText = i18next.t(`${namespace}:berries`);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries })); queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
@ -157,8 +157,8 @@ export const BerriesAboundEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip` buttonTooltip: `${namespace}:option.2.tooltip`
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Pick race for berries // Pick race for berries
@ -180,7 +180,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
if (speedDiff < 1) { if (speedDiff < 1) {
// Caught and attacked by boss, gets +1 to all stats at start of fight // Caught and attacked by boss, gets +1 to all stats at start of fight
const doBerryRewards = () => { const doBerryRewards = () => {
const berryText = i18next.t(`${namespace}.berries`); const berryText = i18next.t(`${namespace}:berries`);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries })); queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries }));
@ -199,11 +199,11 @@ export const BerriesAboundEncounter: MysteryEncounter =
const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ]; config.pokemonConfigs![0].tags = [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}:option.2.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}; };
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected_bad`); await showEncounterText(scene, `${namespace}:option.2.selected_bad`);
await initBattleWithEnemyConfig(scene, config); await initBattleWithEnemyConfig(scene, config);
return; return;
} else { } else {
@ -211,7 +211,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2); const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2);
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed)); encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
const doFasterBerryRewards = () => { const doFasterBerryRewards = () => {
const berryText = i18next.t(`${namespace}.berries`); const berryText = i18next.t(`${namespace}:berries`);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed })); queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed }));
@ -224,7 +224,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards); setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected`); await showEncounterText(scene, `${namespace}:option.2.selected`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} }
}) })
@ -232,11 +232,11 @@ export const BerriesAboundEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, },

View File

@ -52,7 +52,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:bugTypeSuperfan"; const namespace = "mysteryEncounters/bugTypeSuperfan";
const POOL_1_POKEMON = [ const POOL_1_POKEMON = [
Species.PARASECT, Species.PARASECT,
@ -203,11 +203,11 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -276,17 +276,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -317,9 +317,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new TypeRequirement(Type.BUG, false, 1)) // Must have 1 Bug type on team .withPrimaryPokemonRequirement(new TypeRequirement(Type.BUG, false, 1)) // Must have 1 Bug type on team
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip` disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`
}) })
.withPreOptionPhase(async (scene: BattleScene) => { .withPreOptionPhase(async (scene: BattleScene) => {
// Player shows off their bug types // Player shows off their bug types
@ -327,31 +327,31 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
// Player gets different rewards depending on the number of bug types they have // Player gets different rewards depending on the number of bug types they have
const numBugTypes = scene.getParty().filter(p => p.isOfType(Type.BUG, true)).length; const numBugTypes = scene.getParty().filter(p => p.isOfType(Type.BUG, true)).length;
const numBugTypesText = i18next.t(`${namespace}.numBugTypes`, { count: numBugTypes }); const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes });
encounter.setDialogueToken("numBugTypes", numBugTypesText); encounter.setDialogueToken("numBugTypes", numBugTypesText);
if (numBugTypes < 2) { if (numBugTypes < 2) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected_0_to_1`, text: `${namespace}:option.2.selected_0_to_1`,
}, },
]; ];
} else if (numBugTypes < 4) { } else if (numBugTypes < 4) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected_2_to_3`, text: `${namespace}:option.2.selected_2_to_3`,
}, },
]; ];
} else if (numBugTypes < 6) { } else if (numBugTypes < 6) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected_4_to_5`, text: `${namespace}:option.2.selected_4_to_5`,
}, },
]; ];
} else { } else {
@ -382,8 +382,8 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false });
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected_6`, text: `${namespace}:option.2.selected_6`,
}, },
]; ];
} }
@ -401,19 +401,19 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1) new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1)
)) ))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.3.selected_dialogue`, text: `${namespace}:option.3.selected_dialogue`,
}, },
], ],
secondOptionPrompt: `${namespace}.option.3.select_prompt`, secondOptionPrompt: `${namespace}:option.3.select_prompt`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -452,7 +452,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG); (item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG);
}); });
if (!hasValidItem) { if (!hasValidItem) {
return getEncounterText(scene, `${namespace}.option.3.invalid_selection`) ?? null; return getEncounterText(scene, `${namespace}:option.3.invalid_selection`) ?? null;
} }
return null; return null;
@ -480,7 +480,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
.build()) .build())
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]) ])
.build(); .build();
@ -634,7 +634,7 @@ function getTrainerConfigForWave(waveIndex: number) {
function doBugTypeMoveTutor(scene: BattleScene): Promise<void> { function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
return new Promise<void>(async resolve => { return new Promise<void>(async resolve => {
const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions; const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
await showEncounterDialogue(scene, `${namespace}.battle_won`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:battle_won`, `${namespace}:speaker`);
const overlayScale = 1; const overlayScale = 1;
const moveInfoOverlay = new MoveInfoOverlay(scene, { const moveInfoOverlay = new MoveInfoOverlay(scene, {
@ -669,7 +669,7 @@ function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
moveInfoOverlay.setVisible(false); moveInfoOverlay.setVisible(false);
}; };
const result = await selectOptionThenPokemon(scene, optionSelectItems, `${namespace}.teach_move_prompt`, undefined, onHoverOverCancel); const result = await selectOptionThenPokemon(scene, optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel);
// let forceExit = !!result; // let forceExit = !!result;
if (!result) { if (!result) {
moveInfoOverlay.active = false; moveInfoOverlay.active = false;

View File

@ -34,7 +34,7 @@ import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:clowningAround"; const namespace = "mysteryEncounters/clowningAround";
const RANDOM_ABILITY_POOL = [ const RANDOM_ABILITY_POOL = [
Abilities.STURDY, Abilities.STURDY,
@ -98,11 +98,11 @@ export const ClowningAroundEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -148,19 +148,19 @@ export const ClowningAroundEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
}, },
], ],
}) })
@ -199,7 +199,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
// After the battle, offer the player the opportunity to permanently swap ability // After the battle, offer the player the opportunity to permanently swap ability
const abilityWasSwapped = await handleSwapAbility(scene); const abilityWasSwapped = await handleSwapAbility(scene);
if (abilityWasSwapped) { if (abilityWasSwapped) {
await showEncounterText(scene, `${namespace}.option.1.ability_gained`); await showEncounterText(scene, `${namespace}:option.1.ability_gained`);
} }
// Play animations once ability swap is complete // Play animations once ability swap is complete
@ -222,19 +222,19 @@ export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
}, },
{ {
text: `${namespace}.option.2.selected_2`, text: `${namespace}:option.2.selected_2`,
}, },
{ {
text: `${namespace}.option.2.selected_3`, text: `${namespace}:option.2.selected_3`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
}, },
], ],
}) })
@ -308,19 +308,19 @@ export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
}, },
{ {
text: `${namespace}.option.3.selected_2`, text: `${namespace}:option.3.selected_2`,
}, },
{ {
text: `${namespace}.option.3.selected_3`, text: `${namespace}:option.3.selected_3`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
}, },
], ],
}) })
@ -377,15 +377,15 @@ export const ClowningAroundEncounter: MysteryEncounter =
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]) ])
.build(); .build();
async function handleSwapAbility(scene: BattleScene) { async function handleSwapAbility(scene: BattleScene) {
return new Promise<boolean>(async resolve => { return new Promise<boolean>(async resolve => {
await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`); await showEncounterText(scene, `${namespace}:option.1.apply_ability_message`);
scene.ui.setMode(Mode.MESSAGE).then(() => { scene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(scene, resolve); displayYesNoOptions(scene, resolve);
@ -394,7 +394,7 @@ async function handleSwapAbility(scene: BattleScene) {
} }
function displayYesNoOptions(scene: BattleScene, resolve) { function displayYesNoOptions(scene: BattleScene, resolve) {
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, null, 500, false); showEncounterText(scene, `${namespace}:option.1.ability_prompt`, null, 500, false);
const fullOptions = [ const fullOptions = [
{ {
label: i18next.t("menu:yes"), label: i18next.t("menu:yes"),

View File

@ -30,7 +30,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import i18next from "i18next"; import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:dancingLessons"; const namespace = "mysteryEncounters/dancingLessons";
// Fire form // Fire form
const BAILE_STYLE_BIOMES = [ const BAILE_STYLE_BIOMES = [
@ -99,12 +99,12 @@ export const DancingLessonsEncounter: MysteryEncounter =
}) })
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -155,7 +155,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
// Gets +1 to all stats except SPD on battle start // Gets +1 to all stats except SPD on battle start
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}:option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1));
} }
}], }],
@ -173,11 +173,11 @@ export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -202,11 +202,11 @@ export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}) })
@ -237,13 +237,13 @@ export const DancingLessonsEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
secondOptionPrompt: `${namespace}.option.3.select_prompt`, secondOptionPrompt: `${namespace}:option.3.select_prompt`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}) })
@ -278,7 +278,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
} }
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon); const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) { if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null; return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
} }
return null; return null;

View File

@ -16,7 +16,7 @@ import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/mod
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounter:darkDeal"; const namespace = "mysteryEncounters/darkDeal";
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and Mythicals */ /** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and Mythicals */
const excludedBosses = [ const excludedBosses = [
@ -107,32 +107,32 @@ export const DarkDealEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
.withCatchAllowed(true) .withCatchAllowed(true)
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.1.selected_dialogue`, text: `${namespace}:option.1.selected_dialogue`,
}, },
{ {
text: `${namespace}.option.1.selected_message`, text: `${namespace}:option.1.selected_message`,
}, },
], ],
}) })
@ -187,12 +187,12 @@ export const DarkDealEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
@ -204,7 +204,7 @@ export const DarkDealEncounter: MysteryEncounter =
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro` text: `${namespace}:outro`
} }
]) ])
.build(); .build();

View File

@ -19,7 +19,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:delibirdy"; const namespace = "mysteryEncounters/delibirdy";
/** Berries only */ /** Berries only */
const OPTION_2_ALLOWED_MODIFIERS = [ "BerryModifier", "PokemonInstantReviveModifier" ]; const OPTION_2_ALLOWED_MODIFIERS = [ "BerryModifier", "PokemonInstantReviveModifier" ];
@ -81,15 +81,15 @@ export const DelibirdyEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
} }
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -108,11 +108,11 @@ export const DelibirdyEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn .withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -145,12 +145,12 @@ export const DelibirdyEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS)) .withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
secondOptionPrompt: `${namespace}.option.2.select_prompt`, secondOptionPrompt: `${namespace}:option.2.select_prompt`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}) })
@ -183,7 +183,7 @@ export const DelibirdyEncounter: MysteryEncounter =
// If pokemon has valid item, it can be selected // If pokemon has valid item, it can be selected
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon); const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) { if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null; return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
} }
return null; return null;
@ -193,7 +193,7 @@ export const DelibirdyEncounter: MysteryEncounter =
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier; const modifier: BerryModifier | HealingBoosterModifier = encounter.misc.chosenModifier;
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed // Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
if (modifier instanceof BerryModifier) { if (modifier instanceof BerryModifier) {
@ -239,12 +239,12 @@ export const DelibirdyEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true)) .withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
secondOptionPrompt: `${namespace}.option.3.select_prompt`, secondOptionPrompt: `${namespace}:option.3.select_prompt`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}) })
@ -277,7 +277,7 @@ export const DelibirdyEncounter: MysteryEncounter =
// If pokemon has valid item, it can be selected // If pokemon has valid item, it can be selected
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon); const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) { if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null; return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
} }
return null; return null;

View File

@ -14,7 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounter:departmentStoreSale"; const namespace = "mysteryEncounters/departmentStoreSale";
/** /**
* Department Store Sale encounter. * Department Store Sale encounter.
@ -43,21 +43,21 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Choose TMs // Choose TMs
@ -82,8 +82,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Choose Vitamins // Choose Vitamins
@ -106,8 +106,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Choose X Items // Choose X Items
@ -130,8 +130,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.4.label`, buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}.option.4.tooltip`, buttonTooltip: `${namespace}:option.4.tooltip`,
}, },
async (scene: BattleScene) => { async (scene: BattleScene) => {
// Choose Pokeballs // Choose Pokeballs
@ -158,7 +158,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
} }
]) ])
.build(); .build();

View File

@ -14,7 +14,7 @@ import i18next from "i18next";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for the encounter */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieldTrip"; const namespace = "mysteryEncounters/fieldTrip";
/** /**
* Field Trip encounter. * Field Trip encounter.
@ -44,24 +44,24 @@ export const FieldTripEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
secondOptionPrompt: `${namespace}.second_option_prompt`, secondOptionPrompt: `${namespace}:second_option_prompt`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -72,7 +72,7 @@ export const FieldTripEncounter: MysteryEncounter =
label: move.getName(), label: move.getName(),
handler: () => { handler: () => {
// Pokemon and move selected // Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}.physical`)); encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`));
pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.PHYSICAL); pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.PHYSICAL);
return true; return true;
}, },
@ -105,9 +105,9 @@ export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
secondOptionPrompt: `${namespace}.second_option_prompt`, secondOptionPrompt: `${namespace}:second_option_prompt`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -118,7 +118,7 @@ export const FieldTripEncounter: MysteryEncounter =
label: move.getName(), label: move.getName(),
handler: () => { handler: () => {
// Pokemon and move selected // Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}.special`)); encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`));
pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.SPECIAL); pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.SPECIAL);
return true; return true;
}, },
@ -151,9 +151,9 @@ export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
secondOptionPrompt: `${namespace}.second_option_prompt`, secondOptionPrompt: `${namespace}:second_option_prompt`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -164,7 +164,7 @@ export const FieldTripEncounter: MysteryEncounter =
label: move.getName(), label: move.getName(),
handler: () => { handler: () => {
// Pokemon and move selected // Pokemon and move selected
encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}.status`)); encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`));
pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.STATUS); pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.STATUS);
return true; return true;
}, },
@ -203,28 +203,28 @@ function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move:
if (!correctMove) { if (!correctMove) {
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
{ {
text: `${namespace}.incorrect`, text: `${namespace}:incorrect`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}.incorrect_exp`, text: `${namespace}:incorrect_exp`,
}, },
]; ];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else { } else {
encounter.selectedOption!.dialogue!.selected = [ encounter.selectedOption!.dialogue!.selected = [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
{ {
text: `${namespace}.correct`, text: `${namespace}:correct`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}.correct_exp`, text: `${namespace}:correct_exp`,
}, },
]; ];
setEncounterExp(scene, [ pokemon.id ], 100); setEncounterExp(scene, [ pokemon.id ], 100);

View File

@ -24,7 +24,7 @@ import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieryFallout"; const namespace = "mysteryEncounters/fieryFallout";
/** /**
* Damage percentage taken when suffering the heat. * Damage percentage taken when suffering the heat.
@ -49,7 +49,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
.withFleeAllowed(false) .withFleeAllowed(false)
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -122,16 +122,16 @@ export const FieryFalloutEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -170,11 +170,11 @@ export const FieryFalloutEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
@ -197,7 +197,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied // Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.option.2.target_burned`); queueEncounterMessage(scene, `${namespace}:option.2.target_burned`);
} }
} }
@ -211,12 +211,12 @@ export const FieryFalloutEncounter: MysteryEncounter =
.withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically
.withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically .withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}) })
@ -251,6 +251,6 @@ function giveLeadPokemonCharcoal(scene: BattleScene) {
const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType;
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal); applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
scene.currentBattle.mysteryEncounter!.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); scene.currentBattle.mysteryEncounter!.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.found_charcoal`); queueEncounterMessage(scene, `${namespace}:found_charcoal`);
} }
} }

View File

@ -31,7 +31,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fightOrFlight"; const namespace = "mysteryEncounters/fightOrFlight";
/** /**
* Fight or Flight encounter. * Fight or Flight encounter.
@ -48,7 +48,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
.withIntroSpriteConfigs([]) // Set in onInit() .withIntroSpriteConfigs([]) // Set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -67,7 +67,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
isBoss: true, isBoss: true,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`); queueEncounterMessage(pokemon.scene, `${namespace}:option.1.stat_boost`);
// Randomly boost 1 stat 2 stages // Randomly boost 1 stat 2 stages
// Cannot boost Spd, Acc, or Evasion // Cannot boost Spd, Acc, or Evasion
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2));
@ -120,16 +120,16 @@ export const FightOrFlightEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -146,12 +146,12 @@ export const FightOrFlightEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected` text: `${namespace}:option.2.selected`
} }
] ]
}) })
@ -170,11 +170,11 @@ export const FightOrFlightEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, },

View File

@ -25,7 +25,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:funAndGames"; const namespace = "mysteryEncounters/funAndGames";
/** /**
* Fun and Games! encounter. * Fun and Games! encounter.
@ -72,13 +72,13 @@ export const FunAndGamesEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
scene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3"); scene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3");
@ -93,11 +93,11 @@ export const FunAndGamesEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion .withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -112,7 +112,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be selected // Only Pokemon that are not KOed/legal can be selected
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -141,11 +141,11 @@ export const FunAndGamesEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
@ -231,7 +231,7 @@ function handleLoseMinigame(scene: BattleScene) {
scene.currentBattle.enemyParty = []; scene.currentBattle.enemyParty = [];
scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
await showEncounterText(scene, `${namespace}.ko`); await showEncounterText(scene, `${namespace}:ko`);
const reviveCost = scene.getWaveMoneyAmount(1.5); const reviveCost = scene.getWaveMoneyAmount(1.5);
updatePlayerMoney(scene, -reviveCost, true, false); updatePlayerMoney(scene, -reviveCost, true, false);
} }
@ -257,19 +257,19 @@ function handleNextTurn(scene: BattleScene) {
if (healthRatio < 0.03) { if (healthRatio < 0.03) {
// Grand prize // Grand prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false });
resultMessageKey = `${namespace}.best_result`; resultMessageKey = `${namespace}:best_result`;
} else if (healthRatio < 0.15) { } else if (healthRatio < 0.15) {
// 2nd prize // 2nd prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false });
resultMessageKey = `${namespace}.great_result`; resultMessageKey = `${namespace}:great_result`;
} else if (healthRatio < 0.33) { } else if (healthRatio < 0.33) {
// 3rd prize // 3rd prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false }); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false });
resultMessageKey = `${namespace}.good_result`; resultMessageKey = `${namespace}:good_result`;
} else { } else {
// No prize // No prize
isHealPhase = true; isHealPhase = true;
resultMessageKey = `${namespace}.bad_result`; resultMessageKey = `${namespace}:bad_result`;
} }
// End the battle // End the battle
@ -279,7 +279,7 @@ function handleNextTurn(scene: BattleScene) {
scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, isHealPhase); leaveEncounterWithoutBattle(scene, isHealPhase);
// Must end the TurnInit phase prematurely so battle phases aren't added to queue // Must end the TurnInit phase prematurely so battle phases aren't added to queue
queueEncounterMessage(scene, `${namespace}.end_game`); queueEncounterMessage(scene, `${namespace}:end_game`);
queueEncounterMessage(scene, resultMessageKey); queueEncounterMessage(scene, resultMessageKey);
// Skip remainder of TurnInitPhase // Skip remainder of TurnInitPhase
@ -287,9 +287,9 @@ function handleNextTurn(scene: BattleScene) {
} else { } else {
if (encounter.misc.turnsRemaining < 3) { if (encounter.misc.turnsRemaining < 3) {
// Display charging messages on turns that aren't the initial turn // Display charging messages on turns that aren't the initial turn
queueEncounterMessage(scene, `${namespace}.charging_continue`); queueEncounterMessage(scene, `${namespace}:charging_continue`);
} }
queueEncounterMessage(scene, `${namespace}.turn_remaining_${encounter.misc.turnsRemaining}`); queueEncounterMessage(scene, `${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`);
encounter.misc.turnsRemaining--; encounter.misc.turnsRemaining--;
} }

View File

@ -27,7 +27,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:globalTradeSystem"; const namespace = "mysteryEncounters/globalTradeSystem";
/** Base shiny chance of 512/65536 -> 1/128 odds, affected by events and Shiny Charms. Cannot exceed 1/16 odds. */ /** Base shiny chance of 512/65536 -> 1/128 odds, affected by events and Shiny Charms. Cannot exceed 1/16 odds. */
const WONDER_TRADE_SHINY_CHANCE = 512; const WONDER_TRADE_SHINY_CHANCE = 512;
@ -93,12 +93,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -133,9 +133,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
secondOptionPrompt: `${namespace}.option.1.trade_options_prompt`, secondOptionPrompt: `${namespace}:option.1.trade_options_prompt`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -159,7 +159,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true; return true;
}, },
onHover: () => { onHover: () => {
const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[pokemon.formIndex].formName : null; const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[tradePokemon.formIndex].formName : null;
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : ""); const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : ""); const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false); showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false);
@ -201,7 +201,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Show the trade animation // Show the trade animation
await showTradeBackground(scene); await showTradeBackground(scene);
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon); await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000); await showEncounterText(scene, `${namespace}:trade_received`, null, 0, true, 4000);
scene.playBgm(encounter.misc.bgmKey); scene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon); await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon);
await hideTradeBackground(scene); await hideTradeBackground(scene);
@ -216,8 +216,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -309,7 +309,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Show the trade animation // Show the trade animation
await showTradeBackground(scene); await showTradeBackground(scene);
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon); await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000); await showEncounterText(scene, `${namespace}:trade_received`, null, 0, true, 4000);
scene.playBgm(encounter.misc.bgmKey); scene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon); await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon);
await hideTradeBackground(scene); await hideTradeBackground(scene);
@ -323,9 +323,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
secondOptionPrompt: `${namespace}.option.3.trade_options_prompt`, secondOptionPrompt: `${namespace}:option.3.trade_options_prompt`,
}) })
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => { .withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -355,7 +355,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return it.isTransferable; return it.isTransferable;
}).length > 0; }).length > 0;
if (!meetsReqs) { if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.option.3.invalid_selection`) ?? null; return getEncounterText(scene, `${namespace}:option.3.invalid_selection`) ?? null;
} }
return null; return null;
@ -403,18 +403,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Generate a trainer name // Generate a trainer name
const traderName = generateRandomTraderName(); const traderName = generateRandomTraderName();
encounter.setDialogueToken("tradeTrainerName", traderName.trim()); encounter.setDialogueToken("tradeTrainerName", traderName.trim());
await showEncounterText(scene, `${namespace}.item_trade_selected`); await showEncounterText(scene, `${namespace}:item_trade_selected`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
}) })
.build() .build()
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.4.label`, buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}.option.4.tooltip`, buttonTooltip: `${namespace}:option.4.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.4.selected`, text: `${namespace}:option.4.selected`,
}, },
], ],
}, },
@ -628,10 +628,10 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
duration: 500, duration: 500,
onComplete: async () => { onComplete: async () => {
scene.fadeOutBgm(1000, false); scene.fadeOutBgm(1000, false);
await showEncounterText(scene, `${namespace}.pokemon_trade_selected`); await showEncounterText(scene, `${namespace}:pokemon_trade_selected`);
tradedPokemon.cry(); tradedPokemon.cry();
scene.playBgm("evolution"); scene.playBgm("evolution");
await showEncounterText(scene, `${namespace}.pokemon_trade_goodbye`); await showEncounterText(scene, `${namespace}:pokemon_trade_goodbye`);
tradedPokeball.setAlpha(0); tradedPokeball.setAlpha(0);
tradedPokeball.setVisible(true); tradedPokeball.setVisible(true);

View File

@ -21,7 +21,7 @@ const OPTION_2_REQUIRED_MOVE = Moves.FLY;
*/ */
const DAMAGE_PERCENTAGE: number = 25; const DAMAGE_PERCENTAGE: number = 25;
/** The i18n namespace for the encounter */ /** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:lostAtSea"; const namespace = "mysteryEncounters/lostAtSea";
/** /**
* Lost at sea encounter. * Lost at sea encounter.
@ -40,7 +40,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
y: 3, y: 3,
}, },
]) ])
.withIntroDialogue([{ text: `${namespace}.intro` }]) .withIntroDialogue([{ text: `${namespace}:intro` }])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -50,22 +50,22 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/ // Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE) .withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
disabledButtonLabel: `${namespace}.option.1.label_disabled`, disabledButtonLabel: `${namespace}:option.1.label_disabled`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
disabledButtonTooltip: `${namespace}.option.1.tooltip_disabled`, disabledButtonTooltip: `${namespace}:option.1.tooltip_disabled`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -78,13 +78,13 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE) .withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
disabledButtonLabel: `${namespace}.option.2.label_disabled`, disabledButtonLabel: `${namespace}:option.2.label_disabled`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`, disabledButtonTooltip: `${namespace}:option.2.tooltip_disabled`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}) })
@ -94,11 +94,11 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
.withSimpleOption( .withSimpleOption(
// Option 3: Wander aimlessly // Option 3: Wander aimlessly
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, },
@ -118,7 +118,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]) ])
.build(); .build();

View File

@ -20,7 +20,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:mysteriousChallengers"; const namespace = "mysteryEncounters/mysteriousChallengers";
/** /**
* Mysterious Challengers encounter. * Mysterious Challengers encounter.
@ -34,7 +34,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -125,16 +125,16 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}, },
@ -155,11 +155,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}, },
@ -180,11 +180,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}, },
@ -208,7 +208,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]) ])
.build(); .build();

View File

@ -16,7 +16,7 @@ import { GameOverPhase } from "#app/phases/game-over-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounter:mysteriousChest"; const namespace = "mysteryEncounters/mysteriousChest";
const RAND_LENGTH = 100; const RAND_LENGTH = 100;
const TRAP_PERCENT = 35; const TRAP_PERCENT = 35;
@ -58,12 +58,12 @@ export const MysteriousChestEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -96,11 +96,11 @@ export const MysteriousChestEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -141,7 +141,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
], ],
}); });
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.normal`); queueEncounterMessage(scene, `${namespace}:option.1.normal`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
// Choose between 3 ULTRA tier items (30%) // Choose between 3 ULTRA tier items (30%)
@ -153,19 +153,19 @@ export const MysteriousChestEncounter: MysteryEncounter =
], ],
}); });
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.good`); queueEncounterMessage(scene, `${namespace}:option.1.good`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
// Choose between 2 ROGUE tier items (10%) // Choose between 2 ROGUE tier items (10%)
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]}); setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]});
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.great`); queueEncounterMessage(scene, `${namespace}:option.1.great`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) {
// Choose 1 MASTER tier item (5%) // Choose 1 MASTER tier item (5%)
setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.MASTER ]}); setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.MASTER ]});
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.amazing`); queueEncounterMessage(scene, `${namespace}:option.1.amazing`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else { } else {
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%) // Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
@ -173,7 +173,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
koPlayerPokemon(scene, highestLevelPokemon); koPlayerPokemon(scene, highestLevelPokemon);
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender()); encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
await showEncounterText(scene, `${namespace}.option.1.bad`); await showEncounterText(scene, `${namespace}:option.1.bad`);
// Handle game over edge case // Handle game over edge case
const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle()); const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle());
@ -193,11 +193,11 @@ export const MysteriousChestEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },

View File

@ -15,7 +15,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:partTimer"; const namespace = "mysteryEncounters/partTimer";
/** /**
* Part Timer encounter. * Part Timer encounter.
@ -45,11 +45,11 @@ export const PartTimerEncounter: MysteryEncounter =
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -69,17 +69,17 @@ export const PartTimerEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption(MysteryEncounterOptionBuilder .withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected` text: `${namespace}:option.1.selected`
} }
] ]
}) })
@ -118,7 +118,7 @@ export const PartTimerEncounter: MysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected // Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -132,14 +132,14 @@ export const PartTimerEncounter: MysteryEncounter =
// Give money and do dialogue // Give money and do dialogue
if (moneyMultiplier > 2.5) { if (moneyMultiplier > 2.5) {
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`);
} else { } else {
await showEncounterDialogue(scene, `${namespace}.job_complete_bad`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:job_complete_bad`, `${namespace}:speaker`);
} }
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier); const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(scene, moneyChange, true, false); updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}.pokemon_tired`); await showEncounterText(scene, `${namespace}:pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true }); setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
@ -149,11 +149,11 @@ export const PartTimerEncounter: MysteryEncounter =
.withOption(MysteryEncounterOptionBuilder .withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected` text: `${namespace}:option.2.selected`
} }
] ]
}) })
@ -195,7 +195,7 @@ export const PartTimerEncounter: MysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected // Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -209,14 +209,14 @@ export const PartTimerEncounter: MysteryEncounter =
// Give money and do dialogue // Give money and do dialogue
if (moneyMultiplier > 2.5) { if (moneyMultiplier > 2.5) {
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`);
} else { } else {
await showEncounterDialogue(scene, `${namespace}.job_complete_bad`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:job_complete_bad`, `${namespace}:speaker`);
} }
const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier); const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier);
updatePlayerMoney(scene, moneyChange, true, false); updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}.pokemon_tired`); await showEncounterText(scene, `${namespace}:pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true }); setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
@ -228,12 +228,12 @@ export const PartTimerEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}) })
@ -264,11 +264,11 @@ export const PartTimerEncounter: MysteryEncounter =
await transitionMysteryEncounterIntroVisuals(scene, false, false); await transitionMysteryEncounterIntroVisuals(scene, false, false);
// Give money and do dialogue // Give money and do dialogue
await showEncounterDialogue(scene, `${namespace}.job_complete_good`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`);
const moneyChange = scene.getWaveMoneyAmount(2.5); const moneyChange = scene.getWaveMoneyAmount(2.5);
updatePlayerMoney(scene, moneyChange, true, false); updatePlayerMoney(scene, moneyChange, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange }));
await showEncounterText(scene, `${namespace}.pokemon_tired`); await showEncounterText(scene, `${namespace}:pokemon_tired`);
setEncounterRewards(scene, { fillRemaining: true }); setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
@ -277,8 +277,8 @@ export const PartTimerEncounter: MysteryEncounter =
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.outro`, text: `${namespace}:outro`,
} }
]) ])
.build(); .build();

View File

@ -21,7 +21,7 @@ import { SummonPhase } from "#app/phases/summon-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safariZone"; const namespace = "mysteryEncounters/safariZone";
const TRAINER_THROW_ANIMATION_TIMES = [ 512, 184, 768 ]; const TRAINER_THROW_ANIMATION_TIMES = [ 512, 184, 768 ];
@ -51,12 +51,12 @@ export const SafariZoneEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString()); scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
return true; return true;
@ -65,11 +65,11 @@ export const SafariZoneEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive .withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -98,11 +98,11 @@ export const SafariZoneEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
@ -133,11 +133,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.safari.1.label`, buttonLabel: `${namespace}:safari.1.label`,
buttonTooltip: `${namespace}.safari.1.tooltip`, buttonTooltip: `${namespace}:safari.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.safari.1.selected`, text: `${namespace}:safari.1.selected`,
} }
], ],
}) })
@ -168,11 +168,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.safari.2.label`, buttonLabel: `${namespace}:safari.2.label`,
buttonTooltip: `${namespace}.safari.2.tooltip`, buttonTooltip: `${namespace}:safari.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.safari.2.selected`, text: `${namespace}:safari.2.selected`,
}, },
], ],
}) })
@ -186,9 +186,9 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// 80% chance to increase flee stage +1 // 80% chance to increase flee stage +1
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8); const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
if (!fleeChangeResult) { if (!fleeChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`) ?? "", null, 1000, false ); await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.busy_eating`) ?? "", null, 1000, false );
} else { } else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`) ?? "", null, 1000, false); await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.eating`) ?? "", null, 1000, false);
} }
await doEndTurn(scene, 1); await doEndTurn(scene, 1);
@ -198,11 +198,11 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.safari.3.label`, buttonLabel: `${namespace}:safari.3.label`,
buttonTooltip: `${namespace}.safari.3.tooltip`, buttonTooltip: `${namespace}:safari.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.safari.3.selected`, text: `${namespace}:safari.3.selected`,
}, },
], ],
}) })
@ -215,9 +215,9 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// 80% chance to decrease catch stage -1 // 80% chance to decrease catch stage -1
const catchChangeResult = tryChangeCatchStage(scene, -1, 8); const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
if (!catchChangeResult) { if (!catchChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`) ?? "", null, 1000, false ); await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false );
} else { } else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`) ?? "", null, 1000, false ); await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.angry`) ?? "", null, 1000, false );
} }
await doEndTurn(scene, 2); await doEndTurn(scene, 2);
@ -227,8 +227,8 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.safari.4.label`, buttonLabel: `${namespace}:safari.4.label`,
buttonTooltip: `${namespace}.safari.4.tooltip`, buttonTooltip: `${namespace}:safari.4.tooltip`,
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Flee option // Flee option
@ -253,7 +253,7 @@ async function summonSafariPokemon(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
// Message pokemon remaining // Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining); encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.remaining_count`) ?? "", null, true); scene.queueMessage(getEncounterText(scene, `${namespace}:safari.remaining_count`) ?? "", null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken // Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal // Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
@ -518,7 +518,7 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) {
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
} }
} else { } else {
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.watching`) ?? "", 0, null, 1000); scene.queueMessage(getEncounterText(scene, `${namespace}:safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} }
} }

View File

@ -18,7 +18,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import i18next from "i18next"; import i18next from "i18next";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:shadyVitaminDealer"; const namespace = "mysteryEncounters/shadyVitaminDealer";
const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5; const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5;
const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5; const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5;
@ -55,26 +55,26 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER) .withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -103,7 +103,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null; return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
} }
if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) { if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null; return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null;
} }
return null; return null;
@ -139,7 +139,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
chosenPokemon.nature = newNature; chosenPokemon.nature = newNature;
encounter.setDialogueToken("newNature", getNatureName(newNature)); encounter.setDialogueToken("newNature", getNatureName(newNature));
queueEncounterMessage(scene, `${namespace}.cheap_side_effects`); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100); setEncounterExp(scene, [ chosenPokemon.id ], 100);
chosenPokemon.updateInfo(); chosenPokemon.updateInfo();
}) })
@ -150,11 +150,11 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER) .withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -178,7 +178,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
// Only Pokemon that can gain benefits are unfainted // Only Pokemon that can gain benefits are unfainted
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -200,7 +200,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon; const chosenPokemon = encounter.misc.chosenPokemon;
queueEncounterMessage(scene, `${namespace}.no_bad_effects`); queueEncounterMessage(scene, `${namespace}:no_bad_effects`);
setEncounterExp(scene, [ chosenPokemon.id ], 100); setEncounterExp(scene, [ chosenPokemon.id ], 100);
chosenPokemon.updateInfo(); chosenPokemon.updateInfo();
@ -209,12 +209,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
speaker: `${namespace}.speaker` speaker: `${namespace}:speaker`
} }
] ]
}, },

View File

@ -21,7 +21,7 @@ import { BerryType } from "#enums/berry-type";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
/** i18n namespace for the encounter */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounter:slumberingSnorlax"; const namespace = "mysteryEncounters/slumberingSnorlax";
/** /**
* Sleeping Snorlax encounter. * Sleeping Snorlax encounter.
@ -48,7 +48,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -88,16 +88,16 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -123,11 +123,11 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
@ -135,7 +135,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
// Fall asleep waiting for Snorlax // Fall asleep waiting for Snorlax
// Full heal party // Full heal party
scene.unshiftPhase(new PartyHealPhase(scene, true)); scene.unshiftPhase(new PartyHealPhase(scene, true));
queueEncounterMessage(scene, `${namespace}.option.2.rest_result`); queueEncounterMessage(scene, `${namespace}:option.2.rest_result`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} }
) )
@ -144,12 +144,12 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected` text: `${namespace}:option.3.selected`
} }
] ]
}) })

View File

@ -23,7 +23,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:teleportingHijinks"; const namespace = "mysteryEncounters/teleportingHijinks";
const MONEY_COST_MULTIPLIER = 1.75; const MONEY_COST_MULTIPLIER = 1.75;
const BIOME_CANDIDATES = [ Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO ]; const BIOME_CANDIDATES = [ Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO ];
@ -55,12 +55,12 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const price = scene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER); const price = scene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
@ -76,11 +76,11 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost .withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
} }
], ],
}) })
@ -100,12 +100,12 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type .withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
} }
], ],
}) })
@ -119,11 +119,11 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected`, text: `${namespace}:option.3.selected`,
}, },
], ],
}, },
@ -162,10 +162,10 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)]; const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)];
// Show dialogue and transition biome // Show dialogue and transition biome
await showEncounterText(scene, `${namespace}.transport`); await showEncounterText(scene, `${namespace}:transport`);
await Promise.all([ animateBiomeChange(scene, newBiome), transitionMysteryEncounterIntroVisuals(scene) ]); await Promise.all([ animateBiomeChange(scene, newBiome), transitionMysteryEncounterIntroVisuals(scene) ]);
scene.playBgm(); scene.playBgm();
await showEncounterText(scene, `${namespace}.attacked`); await showEncounterText(scene, `${namespace}:attacked`);
// Init enemy // Init enemy
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
@ -186,7 +186,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
isBoss: true, isBoss: true,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}:boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
} }
}], }],

View File

@ -10,7 +10,8 @@ import { Biome } from "#enums/biome";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { PlayerPokemon } from "#app/field/pokemon"; import { PlayerPokemon } from "#app/field/pokemon";
@ -26,7 +27,7 @@ import { Type } from "#app/data/type";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:expertPokemonBreeder"; const namespace = "mysteryEncounters/theExpertPokemonBreeder";
const trainerNameKey = "trainerNames:expert_pokemon_breeder"; const trainerNameKey = "trainerNames:expert_pokemon_breeder";
@ -85,11 +86,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -138,45 +139,45 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
// Dialogue and egg calcs for Pokemon 1 // Dialogue and egg calcs for Pokemon 1
const [ pokemon1CommonEggs, pokemon1RareEggs ] = calculateEggRewardsForPokemon(pokemon1); const [ pokemon1CommonEggs, pokemon1RareEggs ] = calculateEggRewardsForPokemon(pokemon1);
let pokemon1Tooltip = getEncounterText(scene, `${namespace}.option.1.tooltip_base`)!; let pokemon1Tooltip = getEncounterText(scene, `${namespace}:option.1.tooltip_base`)!;
if (pokemon1RareEggs > 0) { if (pokemon1RareEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon1Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1RareEggs", eggsText); encounter.setDialogueToken("pokemon1RareEggs", eggsText);
} }
if (pokemon1CommonEggs > 0) { if (pokemon1CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon1Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText); encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
} }
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip; encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip;
// Dialogue and egg calcs for Pokemon 2 // Dialogue and egg calcs for Pokemon 2
const [ pokemon2CommonEggs, pokemon2RareEggs ] = calculateEggRewardsForPokemon(pokemon2); const [ pokemon2CommonEggs, pokemon2RareEggs ] = calculateEggRewardsForPokemon(pokemon2);
let pokemon2Tooltip = getEncounterText(scene, `${namespace}.option.2.tooltip_base`)!; let pokemon2Tooltip = getEncounterText(scene, `${namespace}:option.2.tooltip_base`)!;
if (pokemon2RareEggs > 0) { if (pokemon2RareEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon2Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon2RareEggs", eggsText); encounter.setDialogueToken("pokemon2RareEggs", eggsText);
} }
if (pokemon2CommonEggs > 0) { if (pokemon2CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon2Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText); encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
} }
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
// Dialogue and egg calcs for Pokemon 3 // Dialogue and egg calcs for Pokemon 3
const [ pokemon3CommonEggs, pokemon3RareEggs ] = calculateEggRewardsForPokemon(pokemon3); const [ pokemon3CommonEggs, pokemon3RareEggs ] = calculateEggRewardsForPokemon(pokemon3);
let pokemon3Tooltip = getEncounterText(scene, `${namespace}.option.3.tooltip_base`)!; let pokemon3Tooltip = getEncounterText(scene, `${namespace}:option.3.tooltip_base`)!;
if (pokemon3RareEggs > 0) { if (pokemon3RareEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon3Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon3RareEggs", eggsText); encounter.setDialogueToken("pokemon3RareEggs", eggsText);
} }
if (pokemon3CommonEggs > 0) { if (pokemon3CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") }); const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon3Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText }); pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon3CommonEggs", eggsText); encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
} }
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip; encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip;
@ -195,18 +196,18 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
selected: [ selected: [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -228,17 +229,17 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.dialogue.outro = [ encounter.dialogue.outro = [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]; ];
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
encounter.dialogue.outro.push({ encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }), text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }),
}); });
} }
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
encounter.dialogue.outro.push({ encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }), text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }),
}); });
} }
@ -254,11 +255,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
selected: [ selected: [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -280,17 +281,17 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.dialogue.outro = [ encounter.dialogue.outro = [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]; ];
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
encounter.dialogue.outro.push({ encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }), text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }),
}); });
} }
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
encounter.dialogue.outro.push({ encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }), text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }),
}); });
} }
@ -306,11 +307,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
selected: [ selected: [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -332,17 +333,17 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
encounter.dialogue.outro = [ encounter.dialogue.outro = [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]; ];
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
encounter.dialogue.outro.push({ encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3CommonEggs"] }), text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3CommonEggs"] }),
}); });
} }
if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) { if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
encounter.dialogue.outro.push({ encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3RareEggs"] }), text: i18next.t(`${namespace}:gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3RareEggs"] }),
}); });
} }
@ -357,7 +358,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
.withOutroDialogue([ .withOutroDialogue([
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.outro`, text: `${namespace}:outro`,
}, },
]) ])
.build(); .build();
@ -374,7 +375,7 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
trainerType: TrainerType.EXPERT_POKEMON_BREEDER, trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
pokemonConfigs: [ pokemonConfigs: [
{ {
nickname: i18next.t(`${namespace}.cleffa_1_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), nickname: i18next.t(`${namespace}:cleffa_1_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
species: getPokemonSpecies(cleffaSpecies), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 1, // Magic Guard abilityIndex: 1, // Magic Guard
@ -394,7 +395,7 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
if (scene.arena.biomeType === Biome.SPACE) { if (scene.arena.biomeType === Biome.SPACE) {
// All 3 members always Cleffa line, but different configs // All 3 members always Cleffa line, but different configs
baseConfig.pokemonConfigs!.push({ baseConfig.pokemonConfigs!.push({
nickname: i18next.t(`${namespace}.cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
species: getPokemonSpecies(cleffaSpecies), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 1, // Magic Guard abilityIndex: 1, // Magic Guard
@ -405,7 +406,7 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
ivs: [ 31, 31, 31, 31, 31, 31 ] ivs: [ 31, 31, 31, 31, 31, 31 ]
}, },
{ {
nickname: i18next.t(`${namespace}.cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), nickname: i18next.t(`${namespace}:cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
species: getPokemonSpecies(cleffaSpecies), species: getPokemonSpecies(cleffaSpecies),
isBoss: false, isBoss: false,
abilityIndex: 2, // Friend Guard / Unaware abilityIndex: 2, // Friend Guard / Unaware
@ -454,8 +455,8 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number]
const rootSpecies = pokemon.species.getRootSpeciesId(); const rootSpecies = pokemon.species.getRootSpeciesId();
let pointsFromStarterTier = 0; let pointsFromStarterTier = 0;
// 2 points for every 1 below 7 that the pokemon's starter tier is (max 12, min 0) // 2 points for every 1 below 7 that the pokemon's starter tier is (max 12, min 0)
if (speciesStarters.hasOwnProperty(rootSpecies)) { if (speciesStarterCosts.hasOwnProperty(rootSpecies)) {
const starterTier = speciesStarters[rootSpecies]; const starterTier = speciesStarterCosts[rootSpecies];
pointsFromStarterTier = Math.min(Math.max(Math.floor(7 - starterTier) * 2, 0), 12); pointsFromStarterTier = Math.min(Math.max(Math.floor(7 - starterTier) * 2, 0), 12);
} }
@ -471,7 +472,7 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number]
} }
function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) { function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) {
const eggDescription = i18next.t(`${namespace}.title`) + ":\n" + i18next.t(trainerNameKey); const eggDescription = i18next.t(`${namespace}:title`) + ":\n" + i18next.t(trainerNameKey);
const eggOptions: IEggOptions[] = []; const eggOptions: IEggOptions[] = [];
if (commonEggs > 0) { if (commonEggs > 0) {
@ -540,7 +541,7 @@ function onGameOver(scene: BattleScene) {
encounter.dialogue.outro = [ encounter.dialogue.outro = [
{ {
speaker: trainerNameKey, speaker: trainerNameKey,
text: `${namespace}.outro_failed`, text: `${namespace}:outro_failed`,
}, },
]; ];
@ -558,6 +559,10 @@ function onGameOver(scene: BattleScene) {
// Revert BGM // Revert BGM
scene.playBgm(scene.arena.bgm); scene.playBgm(scene.arena.bgm);
// Clear any leftover battle phases
scene.clearPhaseQueue();
scene.clearPhaseQueueSplice();
// Return enemy Pokemon // Return enemy Pokemon
const pokemon = scene.getEnemyPokemon(); const pokemon = scene.getEnemyPokemon();
if (pokemon) { if (pokemon) {

View File

@ -5,7 +5,8 @@ import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { PokeballType } from "#app/data/pokeball"; import { PokeballType } from "#app/data/pokeball";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
@ -18,7 +19,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:pokemonSalesman"; const namespace = "mysteryEncounters/thePokemonSalesman";
const MAX_POKEMON_PRICE_MULTIPLIER = 4; const MAX_POKEMON_PRICE_MULTIPLIER = 4;
@ -45,16 +46,16 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -88,15 +89,15 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
isPokemon: true isPokemon: true
}); });
const starterTier = speciesStarters[species.speciesId]; const starterTier = speciesStarterCosts[species.speciesId];
// Prices decrease by starter tier less than 5, but only reduces cost by half at max // Prices decrease by starter tier less than 5, but only reduces cost by half at max
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5); let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
if (pokemon.shiny) { if (pokemon.shiny) {
// Always max price for shiny (flip HA back to normal), and add special messaging // Always max price for shiny (flip HA back to normal), and add special messaging
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER; priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
pokemon.abilityIndex = 0; pokemon.abilityIndex = 0;
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}.description_shiny`; encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
encounter.options[0].dialogue!.buttonTooltip = `${namespace}.option.1.tooltip_shiny`; encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
} }
const price = scene.getWaveMoneyAmount(priceMultiplier); const price = scene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender()); encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
@ -116,11 +117,11 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
.withHasDexProgress(true) .withHasDexProgress(true)
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 .withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected_message`, text: `${namespace}:option.1.selected_message`,
} }
], ],
}) })
@ -133,7 +134,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
updatePlayerMoney(scene, -price, true, false); updatePlayerMoney(scene, -price, true, false);
// Show dialogue // Show dialogue
await showEncounterDialogue(scene, `${namespace}.option.1.selected_dialogue`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
await transitionMysteryEncounterIntroVisuals(scene); await transitionMysteryEncounterIntroVisuals(scene);
// "Catch" purchased pokemon // "Catch" purchased pokemon
@ -147,11 +148,11 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },

View File

@ -20,7 +20,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theStrongStuff"; const namespace = "mysteryEncounters/theStrongStuff";
// Halved for HP stat // Halved for HP stat
const HIGH_BST_REDUCTION_VALUE = 15; const HIGH_BST_REDUCTION_VALUE = 15;
@ -64,7 +64,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
]) // Set in onInit() ]) // Set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -102,7 +102,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
], ],
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.stat_boost`); queueEncounterMessage(pokemon.scene, `${namespace}:option.2.stat_boost`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2));
} }
} }
@ -117,16 +117,16 @@ export const TheStrongStuffEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected` text: `${namespace}:option.1.selected`
} }
] ]
}, },
@ -159,11 +159,11 @@ export const TheStrongStuffEncounter: MysteryEncounter =
encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString()); encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString());
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString()); encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, undefined, true); await showEncounterText(scene, `${namespace}:option.1.selected_2`, null, undefined, true);
encounter.dialogue.outro = [ encounter.dialogue.outro = [
{ {
text: `${namespace}.outro`, text: `${namespace}:outro`,
} }
]; ];
setEncounterRewards(scene, { fillRemaining: true }); setEncounterRewards(scene, { fillRemaining: true });
@ -173,11 +173,11 @@ export const TheStrongStuffEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },

View File

@ -25,7 +25,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theWinstrateChallenge"; const namespace = "mysteryEncounters/theWinstrateChallenge";
/** /**
* The Winstrate Challenge encounter. * The Winstrate Challenge encounter.
@ -74,11 +74,11 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
@ -94,17 +94,17 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -119,12 +119,12 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },
@ -142,7 +142,7 @@ async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
const nextConfig = encounter.enemyPartyConfigs.pop(); const nextConfig = encounter.enemyPartyConfigs.pop();
if (!nextConfig) { if (!nextConfig) {
await transitionMysteryEncounterIntroVisuals(scene, false, false); await transitionMysteryEncounterIntroVisuals(scene, false, false);
await showEncounterDialogue(scene, `${namespace}.victory`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:victory`, `${namespace}:speaker`);
// Give 10x Voucher // Give 10x Voucher
const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier(); const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier();
@ -150,7 +150,7 @@ async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name })); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));
await showEncounterDialogue(scene, `${namespace}.victory_2`, `${namespace}.speaker`); await showEncounterDialogue(scene, `${namespace}:victory_2`, `${namespace}:speaker`);
scene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in scene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in
const machoBrace = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; const machoBrace = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!;
machoBrace.type.tier = ModifierTier.MASTER; machoBrace.type.tier = ModifierTier.MASTER;

View File

@ -1,7 +1,7 @@
import { Ability, allAbilities } from "#app/data/ability"; import { Ability, allAbilities } from "#app/data/ability";
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getNatureName, Nature } from "#app/data/nature"; import { getNatureName, Nature } from "#app/data/nature";
import { speciesStarters } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data"; import { AbilityAttr } from "#app/system/game-data";
@ -23,7 +23,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** The i18n namespace for the encounter */ /** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:trainingSession"; const namespace = "mysteryEncounters/trainingSession";
/** /**
* Training Session encounter. * Training Session encounter.
@ -49,22 +49,22 @@ export const TrainingSessionEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
} }
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -78,7 +78,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be trained // Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -156,7 +156,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
scene.addModifier(mod, true, false, false, true); scene.addModifier(mod, true, false, false, true);
} }
scene.updateModifiers(true); scene.updateModifiers(true);
queueEncounterMessage(scene, `${namespace}.option.1.finished`); queueEncounterMessage(scene, `${namespace}:option.1.finished`);
}; };
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
@ -170,12 +170,12 @@ export const TrainingSessionEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
secondOptionPrompt: `${namespace}.option.2.select_prompt`, secondOptionPrompt: `${namespace}:option.2.select_prompt`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -204,7 +204,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be trained // Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -221,7 +221,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
scene.removePokemonFromPlayerParty(playerPokemon, false); scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => { const onBeforeRewardsPhase = () => {
queueEncounterMessage(scene, `${namespace}.option.2.finished`); queueEncounterMessage(scene, `${namespace}:option.2.finished`);
// Add the pokemon back to party with Nature change // Add the pokemon back to party with Nature change
playerPokemon.setNature(encounter.misc.chosenNature); playerPokemon.setNature(encounter.misc.chosenNature);
scene.gameData.setPokemonCaught(playerPokemon, false); scene.gameData.setPokemonCaught(playerPokemon, false);
@ -246,12 +246,12 @@ export const TrainingSessionEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
secondOptionPrompt: `${namespace}.option.3.select_prompt`, secondOptionPrompt: `${namespace}:option.3.select_prompt`,
selected: [ selected: [
{ {
text: `${namespace}.option.selected`, text: `${namespace}:option.selected`,
}, },
], ],
}) })
@ -295,7 +295,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only Pokemon that are not KOed/legal can be trained // Only Pokemon that are not KOed/legal can be trained
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`); return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`);
}; };
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
@ -316,7 +316,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
scene.removePokemonFromPlayerParty(playerPokemon, false); scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => { const onBeforeRewardsPhase = () => {
queueEncounterMessage(scene, `${namespace}.option.3.finished`); queueEncounterMessage(scene, `${namespace}:option.3.finished`);
// Add the pokemon back to party with ability change // Add the pokemon back to party with ability change
const abilityIndex = encounter.misc.abilityIndex; const abilityIndex = encounter.misc.abilityIndex;
@ -326,7 +326,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals) // Only update the fusion's dex data if the Pokemon is already caught in dex (ignore rentals)
const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId(); const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId();
if (!isNullOrUndefined(rootFusionSpecies) if (!isNullOrUndefined(rootFusionSpecies)
&& speciesStarters.hasOwnProperty(rootFusionSpecies) && speciesStarterCosts.hasOwnProperty(rootFusionSpecies)
&& !!scene.gameData.dexData[rootFusionSpecies].caughtAttr) { && !!scene.gameData.dexData[rootFusionSpecies].caughtAttr) {
scene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2 scene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2
? 1 << playerPokemon.fusionAbilityIndex ? 1 << playerPokemon.fusionAbilityIndex
@ -356,11 +356,11 @@ export const TrainingSessionEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.4.label`, buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}.option.4.tooltip`, buttonTooltip: `${namespace}:option.4.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.4.selected`, text: `${namespace}:option.4.selected`,
}, },
], ],
}, },

View File

@ -19,7 +19,7 @@ import { PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:trashToTreasure"; const namespace = "mysteryEncounters/trashToTreasure";
const SOUND_EFFECT_WAIT_TIME = 700; const SOUND_EFFECT_WAIT_TIME = 700;
@ -51,12 +51,12 @@ export const TrashToTreasureEncounter: MysteryEncounter =
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
@ -90,11 +90,11 @@ export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}) })
@ -123,18 +123,18 @@ export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Investigate garbage, battle Gmax Garbodor // Investigate garbage, battle Gmax Garbodor
scene.setFieldScale(0.75); scene.setFieldScale(0.75);
await showEncounterText(scene, `${namespace}.option.2.selected_2`); await showEncounterText(scene, `${namespace}:option.2.selected_2`);
transitionMysteryEncounterIntroVisuals(scene); transitionMysteryEncounterIntroVisuals(scene);
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;

View File

@ -25,7 +25,7 @@ import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:uncommonBreed"; const namespace = "mysteryEncounters/uncommonBreed";
/** /**
* Uncommon Breed encounter. * Uncommon Breed encounter.
@ -42,7 +42,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
.withIntroSpriteConfigs([]) // Set in onInit() .withIntroSpriteConfigs([]) // Set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
]) ])
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
@ -85,7 +85,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
isBoss: false, isBoss: false,
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`); queueEncounterMessage(pokemon.scene, `${namespace}:option.1.stat_boost`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
} }
}], }],
@ -125,16 +125,16 @@ export const UncommonBreedEncounter: MysteryEncounter =
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
return true; return true;
}) })
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
}, },
], ],
}, },
@ -167,12 +167,12 @@ export const UncommonBreedEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected` text: `${namespace}:option.2.selected`
} }
] ]
}) })
@ -211,12 +211,12 @@ export const UncommonBreedEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.3.label`, buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`, buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`, disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.3.selected` text: `${namespace}:option.3.selected`
} }
] ]
}) })

View File

@ -23,7 +23,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
/** i18n namespace for encounter */ /** i18n namespace for encounter */
const namespace = "mysteryEncounter:weirdDream"; const namespace = "mysteryEncounters/weirdDream";
/** Exclude Ultra Beasts, Paradox, Eternatus, and all legendary/mythical/trio pokemon that are below 570 BST */ /** Exclude Ultra Beasts, Paradox, Eternatus, and all legendary/mythical/trio pokemon that are below 570 BST */
const EXCLUDED_TRANSFORMATION_SPECIES = [ const EXCLUDED_TRANSFORMATION_SPECIES = [
@ -118,16 +118,16 @@ export const WeirdDreamEncounter: MysteryEncounter =
]) ])
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}:intro`,
}, },
{ {
speaker: `${namespace}.speaker`, speaker: `${namespace}:speaker`,
text: `${namespace}.intro_dialogue`, text: `${namespace}:intro_dialogue`,
}, },
]) ])
.withTitle(`${namespace}.title`) .withTitle(`${namespace}:title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3"); scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
return true; return true;
@ -141,11 +141,11 @@ export const WeirdDreamEncounter: MysteryEncounter =
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.1.selected`, text: `${namespace}:option.1.selected`,
} }
], ],
}) })
@ -165,7 +165,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue // Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}.option.1.cutscene`); const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}:option.1.cutscene`);
// Change the entire player's party // Change the entire player's party
// Wait for all new Pokemon assets to be loaded before showing transformation animations // Wait for all new Pokemon assets to be loaded before showing transformation animations
@ -189,7 +189,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
await cutsceneDialoguePromise; await cutsceneDialoguePromise;
doHideDreamBackground(scene); doHideDreamBackground(scene);
await showEncounterText(scene, `${namespace}.option.1.dream_complete`); await showEncounterText(scene, `${namespace}:option.1.dream_complete`);
await doNewTeamPostProcess(scene, transformations); await doNewTeamPostProcess(scene, transformations);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT ]}); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT ]});
@ -199,11 +199,11 @@ export const WeirdDreamEncounter: MysteryEncounter =
) )
.withSimpleOption( .withSimpleOption(
{ {
buttonLabel: `${namespace}.option.2.label`, buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`, buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [ selected: [
{ {
text: `${namespace}.option.2.selected`, text: `${namespace}:option.2.selected`,
}, },
], ],
}, },

View File

@ -5,16 +5,16 @@ import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { Nature } from "../nature"; import { Nature } from "#app/data/nature";
import { EvolutionItem, pokemonEvolutions } from "../pokemon-evolutions"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "../pokemon-forms"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
import { SpeciesFormKey } from "../pokemon-species"; import { StatusEffect } from "#app/data/status-effect";
import { StatusEffect } from "../status-effect"; import { Type } from "#app/data/type";
import { Type } from "../type"; import { WeatherType } from "#app/data/weather";
import { WeatherType } from "../weather";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { SpeciesFormKey } from "#enums/species-form-key";
export interface EncounterRequirement { export interface EncounterRequirement {
meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met
@ -1033,4 +1033,3 @@ export class WeightRequirement extends EncounterPokemonRequirement {
} }
} }

View File

@ -32,7 +32,7 @@ import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fu
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
import { getBiomeName } from "#app/data/biomes"; import { getBiomeName } from "#app/data/balance/biomes";
/** /**
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
@ -363,6 +363,7 @@ export function initMysteryEncounters() {
}); });
// Add ANY biome encounters to biome map // Add ANY biome encounters to biome map
// eslint-disable-next-line
let encounterBiomeTableLog = ""; let encounterBiomeTableLog = "";
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => { mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
anyBiomeEncounters.forEach(encounter => { anyBiomeEncounters.forEach(encounter => {
@ -374,5 +375,5 @@ export function initMysteryEncounters() {
encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`; encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`;
}); });
console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog); //console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog);
} }

View File

@ -1,5 +1,5 @@
import Battle, { BattlerIndex, BattleType } from "#app/battle"; import Battle, { BattlerIndex, BattleType } from "#app/battle";
import { biomeLinks, BiomePoolTier } from "#app/data/biomes"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import 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/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";

View File

@ -12,7 +12,8 @@ import { Mode } from "#app/ui/ui";
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
@ -206,8 +207,8 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers; let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers; let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters) let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarterCosts)
.map(s => [parseInt(s) as Species, speciesStarters[s] as number]) .map(s => [ parseInt(s) as Species, speciesStarterCosts[s] as number ])
.filter(s => { .filter(s => {
const pokemonSpecies = getPokemonSpecies(s[0]); const pokemonSpecies = getPokemonSpecies(s[0]);
return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0])) return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0]))
@ -224,7 +225,7 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are // If no filtered mons exist at specified starter tiers, will expand starter search range until there are
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10 // Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max)); let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) { while (tryFilterStarterTiers.length === 0 && !(min === 0 && max === 10)) {
if (min > 0) { if (min > 0) {
min--; min--;
} else { } else {

View File

@ -1,10 +1,9 @@
import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier"; import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier";
import Pokemon from "../field/pokemon"; import Pokemon from "../field/pokemon";
import { SpeciesFormKey } from "./pokemon-species";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import { MoveCategory, allMoves } from "./move"; import { MoveCategory, allMoves } from "./move";
import { Type } from "./type"; import { Type } from "./type";
import { Constructor } from "#app/utils"; import { Constructor, nil } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -13,6 +12,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next"; import i18next from "i18next";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { Challenges } from "#app/enums/challenges"; import { Challenges } from "#app/enums/challenges";
import { SpeciesFormKey } from "#enums/species-form-key";
export enum FormChangeItem { export enum FormChangeItem {
NONE, NONE,
@ -185,7 +185,7 @@ export class SpeciesFormChange {
return true; return true;
} }
findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | null { findTrigger(triggerType: Constructor<SpeciesFormChangeTrigger>): SpeciesFormChangeTrigger | nil {
if (!this.trigger.hasTriggerType(triggerType)) { if (!this.trigger.hasTriggerType(triggerType)) {
return null; return null;
} }
@ -193,7 +193,7 @@ export class SpeciesFormChange {
const trigger = this.trigger; const trigger = this.trigger;
if (trigger instanceof SpeciesFormChangeCompoundTrigger) { if (trigger instanceof SpeciesFormChangeCompoundTrigger) {
return trigger.triggers.find(t => t.hasTriggerType(triggerType))!; // TODO: is this bang correct? return trigger.triggers.find(t => t.hasTriggerType(triggerType));
} }
return trigger; return trigger;
@ -202,11 +202,11 @@ export class SpeciesFormChange {
export class SpeciesFormChangeCondition { export class SpeciesFormChangeCondition {
public predicate: SpeciesFormChangeConditionPredicate; public predicate: SpeciesFormChangeConditionPredicate;
public enforceFunc: SpeciesFormChangeConditionEnforceFunc | null; public enforceFunc: SpeciesFormChangeConditionEnforceFunc | nil;
constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) { constructor(predicate: SpeciesFormChangeConditionPredicate, enforceFunc?: SpeciesFormChangeConditionEnforceFunc) {
this.predicate = predicate; this.predicate = predicate;
this.enforceFunc = enforceFunc!; // TODO: is this bang correct? this.enforceFunc = enforceFunc;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,27 @@
import BattleScene, { startingWave } from "../battle-scene"; import BattleScene, { startingWave } from "#app/battle-scene";
import { ModifierTypeFunc, modifierTypes } from "../modifier/modifier-type"; import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type";
import { EnemyPokemon, PokemonMove } from "../field/pokemon"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import { PokeballType } from "./pokeball"; import { PokeballType } from "#app/data/pokeball";
import { pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import PokemonSpecies, { getPokemonSpecies, PokemonSpeciesFilter } from "./pokemon-species"; import PokemonSpecies, { getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
import { tmSpecies } from "./tms"; import { tmSpecies } from "#app/data/balance/tms";
import { Type } from "./type"; import { Type } from "#app/data/type";
import { doubleBattleDialogue } from "./dialogue"; import { doubleBattleDialogue } from "#app/data/dialogue";
import { PersistentModifier } from "../modifier/modifier"; import { PersistentModifier } from "#app/modifier/modifier";
import { TrainerVariant } from "../field/trainer"; import { TrainerVariant } from "#app/field/trainer";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import {Gender} from "./gender"; import { Gender } from "#app/data/gender";
/** Minimum BST for Pokemon generated onto the Elite Four's teams */
const ELITE_FOUR_MINIMUM_BST = 460;
/** Minimum BST for Pokemon generated onto the E4 Champion's team */
const CHAMPION_MINIMUM_BST = 508;
export enum TrainerPoolTier { export enum TrainerPoolTier {
COMMON, COMMON,
@ -860,10 +865,10 @@ export class TrainerConfig {
// Set species filter and specialty types if provided, otherwise filter by base total. // Set species filter and specialty types if provided, otherwise filter by base total.
if (specialtyTypes.length) { if (specialtyTypes.length) {
this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= 450); this.setSpeciesFilter(p => specialtyTypes.some(t => p.isOfType(t)) && p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
this.setSpecialtyTypes(...specialtyTypes); this.setSpecialtyTypes(...specialtyTypes);
} else { } else {
this.setSpeciesFilter(p => p.baseTotal >= 450); this.setSpeciesFilter(p => p.baseTotal >= ELITE_FOUR_MINIMUM_BST);
} }
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores. // Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
@ -913,8 +918,7 @@ export class TrainerConfig {
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
// Set species filter to only include species with a base total of 470 or higher. this.setSpeciesFilter(p => p.baseTotal >= CHAMPION_MINIMUM_BST);
this.setSpeciesFilter(p => p.baseTotal >= 470);
// Localize the trainer's name by converting it to lowercase and replacing spaces with underscores. // Localize the trainer's name by converting it to lowercase and replacing spaces with underscores.
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");

View File

@ -1,4 +1,3 @@
export enum ArenaTagType { export enum ArenaTagType {
NONE = "NONE", NONE = "NONE",
MUD_SPORT = "MUD_SPORT", MUD_SPORT = "MUD_SPORT",
@ -25,4 +24,5 @@ export enum ArenaTagType {
SAFEGUARD = "SAFEGUARD", SAFEGUARD = "SAFEGUARD",
NO_CRIT = "NO_CRIT", NO_CRIT = "NO_CRIT",
IMPRISON = "IMPRISON", IMPRISON = "IMPRISON",
PLASMA_FISTS = "PLASMA_FISTS",
} }

View File

@ -1,4 +1,3 @@
export enum BattlerTagType { export enum BattlerTagType {
NONE = "NONE", NONE = "NONE",
RECHARGING = "RECHARGING", RECHARGING = "RECHARGING",
@ -85,4 +84,5 @@ export enum BattlerTagType {
TORMENT = "TORMENT", TORMENT = "TORMENT",
TAUNT = "TAUNT", TAUNT = "TAUNT",
IMPRISON = "IMPRISON", IMPRISON = "IMPRISON",
SYRUP_BOMB = "SYRUP_BOMB",
} }

View File

@ -1,4 +1,3 @@
export enum BerryType { export enum BerryType {
SITRUS, SITRUS,
LUM, LUM,

View File

@ -1,4 +1,3 @@
export enum Biome { export enum Biome {
TOWN, TOWN,
PLAINS, PLAINS,

View File

@ -0,0 +1,13 @@
export enum SpeciesFormKey {
MEGA = "mega",
MEGA_X = "mega-x",
MEGA_Y = "mega-y",
PRIMAL = "primal",
ORIGIN = "origin",
INCARNATE = "incarnate",
THERIAN = "therian",
GIGANTAMAX = "gigantamax",
GIGANTAMAX_SINGLE = "gigantamax-single",
GIGANTAMAX_RAPID = "gigantamax-rapid",
ETERNAMAX = "eternamax",
}

View File

@ -1,4 +1,3 @@
export enum TimeOfDay { export enum TimeOfDay {
ALL = -1, ALL = -1,
DAWN, DAWN,

View File

@ -1,26 +1,26 @@
import BattleScene from "../battle-scene"; import BattleScene from "#app/battle-scene";
import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "../data/biomes"; import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "#app/data/balance/biomes";
import { Constructor } from "#app/utils"; import { Constructor } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "../data/weather"; import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "#app/data/weather";
import { CommonAnim } from "../data/battle-anims"; import { CommonAnim } from "#app/data/battle-anims";
import { Type } from "../data/type"; import { Type } from "#app/data/type";
import Move from "../data/move"; import Move from "#app/data/move";
import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "../data/arena-tag"; import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "#app/battle";
import { Terrain, TerrainType } from "../data/terrain"; import { Terrain, TerrainType } from "#app/data/terrain";
import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "../data/ability"; import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "#app/data/ability";
import Pokemon from "./pokemon"; import Pokemon from "#app/field/pokemon";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "../events/arena"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { Abilities } from "#app/enums/abilities"; import { Abilities } from "#enums/abilities";
import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms";
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase";

View File

@ -1,43 +1,45 @@
import Phaser from "phaser"; import Phaser from "phaser";
import BattleScene, { AnySound } from "../battle-scene"; import BattleScene, { AnySound } from "#app/battle-scene";
import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "../data/move"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget } from "#app/data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
import { getLevelTotalExp } from "../data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier } from "../modifier/modifier"; import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier } from "#app/modifier/modifier";
import { PokeballType } from "../data/pokeball"; import { PokeballType } from "#app/data/pokeball";
import { Gender } from "../data/gender"; import { Gender } from "#app/data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather"; import { WeatherType } from "#app/data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability";
import PokemonData from "../system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "#app/battle";
import { Mode } from "../ui/ui"; import { Mode } from "#app/ui/ui";
import PartyUiHandler, { PartyOption, PartyUiMode } from "../ui/party-ui-handler"; import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { LevelMoves } from "../data/pokemon-level-moves"; import { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { DamageAchv, achvs } from "../system/achv"; import { DamageAchv, achvs } from "#app/system/achv";
import { DexAttr, StarterDataEntry, StarterMoveset } from "../system/game-data"; import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { Nature, getNatureStatMultiplier } from "../data/nature"; import { Nature, getNatureStatMultiplier } from "#app/data/nature";
import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "../data/pokemon-forms"; import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
import { TerrainType } from "../data/terrain"; import { TerrainType } from "#app/data/terrain";
import { TrainerSlot } from "../data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import i18next from "i18next"; import i18next from "i18next";
import { speciesEggMoves } from "../data/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { ModifierTier } from "../modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { applyChallenges, ChallengeType } from "#app/data/challenge";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
@ -58,10 +60,12 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase"; import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { SpeciesFormKey } from "#enums/species-form-key";
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
export enum FieldPosition { export enum FieldPosition {
CENTER, CENTER,
@ -139,7 +143,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
throw `Cannot create a player Pokemon for species '${species.getName(formIndex)}'`; throw `Cannot create a player Pokemon for species '${species.getName(formIndex)}'`;
} }
const hiddenAbilityChance = new Utils.IntegerHolder(256); const hiddenAbilityChance = new Utils.IntegerHolder(BASE_HIDDEN_ABILITY_CHANCE);
if (!this.hasTrainer()) { if (!this.hasTrainer()) {
this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
} }
@ -1083,6 +1087,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !!this.fusionSpecies; return !!this.fusionSpecies;
} }
/**
* Checks if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}.
* @param species the pokemon {@linkcode Species} to check
* @returns `true` if the {@linkcode Pokemon} has a fusion with the specified {@linkcode Species}, `false` otherwise
*/
hasFusionSpecies(species: Species): boolean {
return this.fusionSpecies?.speciesId === species;
}
abstract isBoss(): boolean; abstract isBoss(): boolean;
getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] { getMoveset(ignoreOverride?: boolean): (PokemonMove | null)[] {
@ -1512,11 +1525,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
this.scene.arena.applyTags(ArenaTagType.PLASMA_FISTS, moveTypeHolder);
return moveTypeHolder.value as Type; return moveTypeHolder.value as Type;
} }
/** /**
* Calculates the effectiveness of a move against the Pokémon. * Calculates the effectiveness of a move against the Pokémon.
* This includes modifiers from move and ability attributes. * This includes modifiers from move and ability attributes.
@ -1529,6 +1543,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns The type damage multiplier, indicating the effectiveness of the move * @returns The type damage multiplier, indicating the effectiveness of the move
*/ */
getMoveEffectiveness(source: Pokemon, move: Move, ignoreAbility: boolean = false, simulated: boolean = true, cancelled?: Utils.BooleanHolder): TypeDamageMultiplier { getMoveEffectiveness(source: Pokemon, move: Move, ignoreAbility: boolean = false, simulated: boolean = true, cancelled?: Utils.BooleanHolder): TypeDamageMultiplier {
if (!Utils.isNullOrUndefined(this.turnData?.moveEffectiveness)) {
return this.turnData?.moveEffectiveness;
}
if (move.hasAttr(TypelessAttr)) { if (move.hasAttr(TypelessAttr)) {
return 1; return 1;
} }
@ -1822,7 +1840,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* The exact mechanic is that it calculates E as the XOR of the player's trainer ID and secret ID. * The exact mechanic is that it calculates E as the XOR of the player's trainer ID and secret ID.
* F is calculated as the XOR of the first 16 bits of the Pokemon's ID with the last 16 bits. * F is calculated as the XOR of the first 16 bits of the Pokemon's ID with the last 16 bits.
* The XOR of E and F are then compared to the {@linkcode shinyThreshold} (or {@linkcode thresholdOverride} if set) to see whether or not to generate a shiny. * The XOR of E and F are then compared to the {@linkcode shinyThreshold} (or {@linkcode thresholdOverride} if set) to see whether or not to generate a shiny.
* The base shiny odds are {@linkcode baseShinyChance} / 65536 * The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / 65536
* @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) * @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
* @returns true if the Pokemon has been set as a shiny, false otherwise * @returns true if the Pokemon has been set as a shiny, false otherwise
*/ */
@ -1838,9 +1856,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId; const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId;
const F = rand1 ^ rand2; const F = rand1 ^ rand2;
/** `64/65536 -> 1/1024` */ const shinyThreshold = new Utils.IntegerHolder(BASE_SHINY_CHANCE);
const baseShinyChance = 64;
const shinyThreshold = new Utils.IntegerHolder(baseShinyChance);
if (thresholdOverride === undefined) { if (thresholdOverride === undefined) {
if (this.scene.eventManager.isEventActive()) { if (this.scene.eventManager.isEventActive()) {
shinyThreshold.value *= this.scene.eventManager.getShinyMultiplier(); shinyThreshold.value *= this.scene.eventManager.getShinyMultiplier();
@ -1865,15 +1881,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Function that tries to set a Pokemon shiny based on seed. * Function that tries to set a Pokemon shiny based on seed.
* For manual use only, usually to roll a Pokemon's shiny chance a second time. * For manual use only, usually to roll a Pokemon's shiny chance a second time.
* *
* The base shiny odds are {@linkcode baseShinyChance} / 65536 * The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / 65536
* @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) * @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride} * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
* @returns true if the Pokemon has been set as a shiny, false otherwise * @returns true if the Pokemon has been set as a shiny, false otherwise
*/ */
trySetShinySeed(thresholdOverride?: integer, applyModifiersToOverride?: boolean): boolean { trySetShinySeed(thresholdOverride?: integer, applyModifiersToOverride?: boolean): boolean {
/** `64/65536 -> 1/1024` */ const shinyThreshold = new Utils.IntegerHolder(BASE_SHINY_CHANCE);
const baseShinyChance = 64;
const shinyThreshold = new Utils.IntegerHolder(baseShinyChance);
if (thresholdOverride === undefined || applyModifiersToOverride) { if (thresholdOverride === undefined || applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) { if (thresholdOverride !== undefined && applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride; shinyThreshold.value = thresholdOverride;
@ -1921,9 +1935,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.executeWithSeedOffset(() => { this.scene.executeWithSeedOffset(() => {
rand.value = Utils.randSeedInt(10); rand.value = Utils.randSeedInt(10);
}, this.id, this.scene.waveSeed); }, this.id, this.scene.waveSeed);
if (rand.value >= 4) { if (rand.value >= SHINY_VARIANT_CHANCE) {
return 0; // 6/10 return 0; // 6/10
} else if (rand.value >= 1) { } else if (rand.value >= SHINY_EPIC_CHANCE) {
return 1; // 3/10 return 1; // 3/10
} else { } else {
return 2; // 1/10 return 2; // 1/10
@ -1931,7 +1945,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
generateFusionSpecies(forStarter?: boolean): void { generateFusionSpecies(forStarter?: boolean): void {
const hiddenAbilityChance = new Utils.IntegerHolder(256); const hiddenAbilityChance = new Utils.IntegerHolder(BASE_HIDDEN_ABILITY_CHANCE);
if (!this.hasTrainer()) { if (!this.hasTrainer()) {
this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
} }
@ -3979,7 +3993,8 @@ export class PlayerPokemon extends Pokemon {
let compatible = false; let compatible = false;
for (const p of tmSpecies[tm]) { for (const p of tmSpecies[tm]) {
if (Array.isArray(p)) { if (Array.isArray(p)) {
if (p[0] === this.species.speciesId || (this.fusionSpecies && p[0] === this.fusionSpecies.speciesId) && p.slice(1).indexOf(this.species.forms[this.formIndex]) > -1) { const [ pkm, form ] = p;
if ((pkm === this.species.speciesId || this.fusionSpecies && pkm === this.fusionSpecies.speciesId) && form === this.getFormKey()) {
compatible = true; compatible = true;
break; break;
} }
@ -4047,7 +4062,7 @@ export class PlayerPokemon extends Pokemon {
starterData.forEach((sd: StarterDataEntry, i: integer) => { starterData.forEach((sd: StarterDataEntry, i: integer) => {
const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species; const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species;
sd.friendship = (sd.friendship || 0) + starterAmount.value; sd.friendship = (sd.friendship || 0) + starterAmount.value;
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarters[speciesId])) { if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) {
this.scene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1); this.scene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1);
sd.friendship = 0; sd.friendship = 0;
} }
@ -4995,6 +5010,8 @@ export class PokemonBattleData {
export class PokemonBattleSummonData { export class PokemonBattleSummonData {
/** The number of turns the pokemon has passed since entering the battle */ /** The number of turns the pokemon has passed since entering the battle */
public turnCount: number = 1; public turnCount: number = 1;
/** The number of turns the pokemon has passed since the start of the wave */
public waveTurnCount: number = 1;
/** The list of moves the pokemon has used since entering the battle */ /** The list of moves the pokemon has used since entering the battle */
public moveHistory: TurnMove[] = []; public moveHistory: TurnMove[] = [];
} }
@ -5011,6 +5028,7 @@ export class PokemonTurnData {
public order: number; public order: number;
public statStagesIncreased: boolean = false; public statStagesIncreased: boolean = false;
public statStagesDecreased: boolean = false; public statStagesDecreased: boolean = false;
public moveEffectiveness: TypeDamageMultiplier | null = null;
} }
export enum AiType { export enum AiType {

View File

@ -1,6 +1,6 @@
import BattleScene from "../battle-scene"; import BattleScene from "#app/battle-scene";
import {pokemonPrevolutions} from "../data/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import PokemonSpecies, {getPokemonSpecies} from "../data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
import { import {
TrainerConfig, TrainerConfig,
TrainerPartyCompoundTemplate, TrainerPartyCompoundTemplate,
@ -10,11 +10,11 @@ import {
trainerConfigs, trainerConfigs,
trainerPartyTemplates, trainerPartyTemplates,
signatureSpecies signatureSpecies
} from "../data/trainer-config"; } from "#app/data/trainer-config";
import {EnemyPokemon} from "./pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import * as Utils from "../utils"; import * as Utils from "#app/utils";
import {PersistentModifier} from "../modifier/modifier"; import { PersistentModifier } from "#app/modifier/modifier";
import {trainerNamePools} from "../data/trainer-names"; import { trainerNamePools } from "#app/data/trainer-names";
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";

View File

@ -1,24 +1,23 @@
import { GachaType } from "./enums/gacha-types"; import { GachaType } from "#enums/gacha-types";
import { getBiomeHasProps } from "./field/arena"; import { getBiomeHasProps } from "#app/field/arena";
import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin"; import CacheBustedLoaderPlugin from "#app/plugins/cache-busted-loader-plugin";
import { SceneBase } from "./scene-base"; import { SceneBase } from "#app/scene-base";
import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme"; import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme";
import { isMobile } from "./touch-controls"; import { isMobile } from "#app/touch-controls";
import * as Utils from "./utils"; import * as Utils from "#app/utils";
import { initI18n } from "./plugins/i18n"; import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { initPokemonPrevolutions } from "#app/data/pokemon-evolutions"; import { initBiomes } from "#app/data/balance/biomes";
import { initBiomes } from "#app/data/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves";
import { initEggMoves } from "#app/data/egg-moves";
import { initPokemonForms } from "#app/data/pokemon-forms"; import { initPokemonForms } from "#app/data/pokemon-forms";
import { initSpecies } from "#app/data/pokemon-species"; import { initSpecies } from "#app/data/pokemon-species";
import { initMoves } from "#app/data/move"; import { initMoves } from "#app/data/move";
import { initAbilities } from "#app/data/ability"; import { initAbilities } from "#app/data/ability";
import { initAchievements } from "#app/system/achv"; import { initAchievements } from "#app/system/achv";
import { initTrainerTypeDialogue } from "#app/data/dialogue"; import { initTrainerTypeDialogue } from "#app/data/dialogue";
import { initChallenges } from "./data/challenge"; import { initChallenges } from "#app/data/challenge";
import i18next from "i18next"; import i18next from "i18next";
import { initStatsKeys } from "./ui/game-stats-ui-handler"; import { initStatsKeys } from "#app/ui/game-stats-ui-handler";
import { initVouchers } from "./system/voucher"; import { initVouchers } from "#app/system/voucher";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters";
@ -31,7 +30,6 @@ export class LoadingScene extends SceneBase {
super(LoadingScene.KEY); super(LoadingScene.KEY);
Phaser.Plugins.PluginCache.register("Loader", CacheBustedLoaderPlugin, "load"); Phaser.Plugins.PluginCache.register("Loader", CacheBustedLoaderPlugin, "load");
initI18n();
} }
preload() { preload() {
@ -46,6 +44,8 @@ export class LoadingScene extends SceneBase {
this.loadAtlas("prompt", "ui"); this.loadAtlas("prompt", "ui");
this.loadImage("candy", "ui"); this.loadImage("candy", "ui");
this.loadImage("candy_overlay", "ui"); this.loadImage("candy_overlay", "ui");
this.loadImage("friendship", "ui");
this.loadImage("friendship_overlay", "ui");
this.loadImage("cursor", "ui"); this.loadImage("cursor", "ui");
this.loadImage("cursor_reverse", "ui"); this.loadImage("cursor_reverse", "ui");
for (const wv of Utils.getEnumValues(WindowVariant)) { for (const wv of Utils.getEnumValues(WindowVariant)) {

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1,40 +0,0 @@
{
"activeBattleEffects": "Efectes d'Arena Activa",
"player": "Jugador",
"neutral": "Neutre",
"enemy": "Enemic",
"sunny": "Assolellat",
"rain": "Plujós",
"sandstorm": "Tempesta Sorra",
"hail": "Calamarsa",
"snow": "Neu",
"fog": "Boira",
"heavyRain": "Diluvi",
"harshSun": "Sol Abrasador",
"strongWinds": "Vents Forts",
"misty": "Camp de Boira",
"electric": "Camp Elèctric",
"grassy": "Camp d'Herba",
"psychic": "Camp Psíquic",
"mudSport": "Xipollejo Fang",
"waterSport": "Hidrorraig",
"spikes": "Pues",
"toxicSpikes": "Pues Tòxiques",
"mist": "Boirina",
"futureSight": "Premonició",
"doomDesire": "Desig Ocult",
"wish": "Desig",
"stealthRock": "Trampa Roques",
"stickyWeb": "Xarxa Viscosa",
"trickRoom": "Espai Rar",
"gravity": "Gravetat",
"reflect": "Reflex",
"lightScreen": "Pantalla de Llum",
"auroraVeil": "Vel Aurora",
"quickGuard": "Anticipi",
"wideGuard": "Vasta Guàrdia",
"matBlock": "Escut Tatami",
"craftyShield": "Truc Defensa",
"tailwind": "Vent Afí",
"happyHour": "Paga Extra"
}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

View File

@ -1 +0,0 @@
{}

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