diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 3e6b8bf6d0d..a30cb642a46 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -30,7 +30,7 @@
- [ ] The PR is self-contained and cannot be split into smaller PRs?
- [ ] Have I provided a clear explanation of the changes?
- [ ] Have I considered writing automated tests for the issue?
-- [ ] If I have text, did I add make it translatable and added a key in the English language?
+- [ ] If I have text, did I make it translatable and add a key in the English locale file(s)?
- [ ] Have I tested the changes (manually)?
- [ ] Are all unit tests still passing? (`npm run test`)
- [ ] Are the changes visual?
diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml
index 9068f1ae9a2..2850418bc59 100644
--- a/.github/workflows/eslint.yml
+++ b/.github/workflows/eslint.yml
@@ -11,6 +11,8 @@ on:
branches:
- main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch
+ merge_group:
+ types: [checks_requested]
jobs:
run-linters: # Define a job named "run-linters"
diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml
index 3b7617c45f4..a092ccb425a 100644
--- a/.github/workflows/github-pages.yml
+++ b/.github/workflows/github-pages.yml
@@ -8,6 +8,8 @@ on:
branches:
- main
- beta
+ merge_group:
+ types: [checks_requested]
jobs:
pages:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9ce1d1c5038..adac45519ab 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -11,6 +11,8 @@ on:
branches:
- main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch
+ merge_group:
+ types: [checks_requested]
jobs:
run-tests: # Define a job named "run-tests"
diff --git a/create-test-boilerplate.js b/create-test-boilerplate.js
new file mode 100644
index 00000000000..bf68258f321
--- /dev/null
+++ b/create-test-boilerplate.js
@@ -0,0 +1,101 @@
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+/**
+ * This script creates a test boilerplate file for a move or ability.
+ * @param {string} type - The type of test to create. Either "move" or "ability".
+ * @param {string} fileName - The name of the file to create.
+ * @example npm run create-test move tackle
+ */
+
+// Get the directory name of the current module file
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Get the arguments from the command line
+const args = process.argv.slice(2);
+const type = args[0]; // "move" or "ability"
+let fileName = args[1]; // The file name
+
+if (!type || !fileName) {
+ console.error('Please provide both a type ("move" or "ability") and a file name.');
+ process.exit(1);
+}
+
+// Convert fileName from to snake_case if camelCase is given
+fileName = fileName.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
+
+// Format the description for the test case
+const formattedName = fileName
+ .replace(/_/g, ' ')
+ .replace(/\b\w/g, char => char.toUpperCase());
+
+// Determine the directory based on the type
+let dir;
+let description;
+if (type === 'move') {
+ dir = path.join(__dirname, 'src', 'test', 'moves');
+ description = `Moves - ${formattedName}`;
+} else if (type === 'ability') {
+ dir = path.join(__dirname, 'src', 'test', 'abilities');
+ description = `Abilities - ${formattedName}`;
+} else {
+ console.error('Invalid type. Please use "move" or "ability".');
+ process.exit(1);
+}
+
+// Ensure the directory exists
+if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+}
+
+// Create the file with the given name
+const filePath = path.join(dir, `${fileName}.test.ts`);
+
+if (fs.existsSync(filePath)) {
+ console.error(`File "${fileName}.test.ts" already exists.`);
+ process.exit(1);
+}
+
+// Define the content template
+const content = `import { Abilities } from "#enums/abilities";
+import GameManager from "#test/utils/gameManager";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, it } from "vitest";
+
+describe("${description}", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+ const TIMEOUT = 20 * 1000;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemyAbility(Abilities.BALL_FETCH)
+ .enemyMoveset(SPLASH_ONLY);
+ });
+
+ it("test case", async () => {
+ // await game.classicMode.startBattle();
+ // game.move.select();
+ }, TIMEOUT);
+});
+`;
+
+// Write the template content to the file
+fs.writeFileSync(filePath, content, 'utf8');
+
+console.log(`File created at: ${filePath}`);
\ No newline at end of file
diff --git a/docs/enemy-ai.md b/docs/enemy-ai.md
index f53a8511893..46482f56a90 100644
--- a/docs/enemy-ai.md
+++ b/docs/enemy-ai.md
@@ -191,15 +191,15 @@ Now that the enemy Pokémon with the best matchup score is on the field (assumin
We then need to apply a 2x multiplier for the move's type effectiveness and a 1.5x multiplier since STAB applies. After applying these multipliers, the final score for this move is **75**.
-- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to
+- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatStageChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to
$\text{TBS}=4\times \text{levels} + (-2\times \text{sign(levels)})$
where `levels` is the number of stat stages added by the attribute (in this case, +2). The final score for this move is **6** (Note: because this move is self-targeted, we don't flip the sign of TBS when computing the target score).
-- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score.
+- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatStageChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score.
- $\text{TBS}=\text{getTargetBenefitScore(StatChangeAttr)}-\text{attackScore}$
+ $\text{TBS}=\text{getTargetBenefitScore(StatStageChangeAttr)}-\text{attackScore}$
$\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$
@@ -221,4 +221,4 @@ When implementing a new move attribute, it's important to override `MoveAttr`'s
- A move's **user benefit score (UBS)** incentivizes (or discourages) the move's usage in general. A positive UBS gives the move more incentive to be used, while a negative UBS gives the move less incentive.
- A move's **target benefit score (TBS)** incentivizes (or discourages) the move's usage on a specific target. A positive TBS indicates the move is better used on the user or its allies, while a negative TBS indicates the move is better used on enemies.
- **The total benefit score (UBS + TBS) of a move should never be 0.** The move selection algorithm assumes the move's benefit score is unimplemented if the total score is 0 and penalizes the move's usage as a result. With status moves especially, it's important to have some form of implementation among the move's attributes to avoid this scenario.
-- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making.
\ No newline at end of file
+- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making.
diff --git a/index.css b/index.css
index abf4f9f708c..1274f2fcead 100644
--- a/index.css
+++ b/index.css
@@ -23,15 +23,6 @@ body {
}
}
-#links {
- width: 90%;
- text-align: center;
- position: fixed;
- bottom: 0;
- display: flex;
- justify-content: space-around;
-}
-
#app {
display: flex;
justify-content: center;
@@ -93,7 +84,7 @@ input:-internal-autofill-selected {
@media (orientation: landscape) {
#touchControls {
- --controls-size: 20vh;
+ --controls-size: 20vh;
--text-shadow-size: 1.3vh;
--small-button-offset: 4vh;
}
diff --git a/index.html b/index.html
index 29b4c0d1a6e..390a29fb365 100644
--- a/index.html
+++ b/index.html
@@ -39,7 +39,6 @@
-
diff --git a/package.json b/package.json
index b85ac639a1b..83e82585d1e 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,8 @@
"eslint-ci": "eslint .",
"docs": "typedoc",
"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"
},
"devDependencies": {
"@eslint/js": "^9.3.0",
diff --git a/public/images/events/september-update-de.png b/public/images/events/september-update-de.png
new file mode 100644
index 00000000000..1ecb46e408c
Binary files /dev/null and b/public/images/events/september-update-de.png differ
diff --git a/public/images/events/september-update-en.png b/public/images/events/september-update-en.png
new file mode 100644
index 00000000000..57dd130b98d
Binary files /dev/null and b/public/images/events/september-update-en.png differ
diff --git a/public/images/events/september-update-es.png b/public/images/events/september-update-es.png
new file mode 100644
index 00000000000..8c294d21403
Binary files /dev/null and b/public/images/events/september-update-es.png differ
diff --git a/public/images/events/september-update-fr.png b/public/images/events/september-update-fr.png
new file mode 100644
index 00000000000..4be33c85e9a
Binary files /dev/null and b/public/images/events/september-update-fr.png differ
diff --git a/public/images/events/september-update-it.png b/public/images/events/september-update-it.png
new file mode 100644
index 00000000000..62542f4eb9b
Binary files /dev/null and b/public/images/events/september-update-it.png differ
diff --git a/public/images/events/september-update-ja.png b/public/images/events/september-update-ja.png
new file mode 100644
index 00000000000..93e18c51223
Binary files /dev/null and b/public/images/events/september-update-ja.png differ
diff --git a/public/images/events/september-update-ko.png b/public/images/events/september-update-ko.png
new file mode 100644
index 00000000000..13585327fce
Binary files /dev/null and b/public/images/events/september-update-ko.png differ
diff --git a/public/images/events/september-update-pt-BR.png b/public/images/events/september-update-pt-BR.png
new file mode 100644
index 00000000000..8dd8b8759e9
Binary files /dev/null and b/public/images/events/september-update-pt-BR.png differ
diff --git a/public/images/events/september-update-zh-CN.png b/public/images/events/september-update-zh-CN.png
new file mode 100644
index 00000000000..ee56d644d24
Binary files /dev/null and b/public/images/events/september-update-zh-CN.png differ
diff --git a/public/images/inputs/keyboard.json b/public/images/inputs/keyboard.json
index b1902df10d6..c9b3c79fbfb 100644
--- a/public/images/inputs/keyboard.json
+++ b/public/images/inputs/keyboard.json
@@ -1,596 +1,529 @@
-{"frames": [
-
-{
- "filename": "0.png",
- "frame": {"x":0,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "1.png",
- "frame": {"x":12,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "2.png",
- "frame": {"x":24,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "3.png",
- "frame": {"x":36,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "4.png",
- "frame": {"x":48,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "5.png",
- "frame": {"x":60,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "6.png",
- "frame": {"x":72,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "7.png",
- "frame": {"x":84,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "8.png",
- "frame": {"x":96,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "9.png",
- "frame": {"x":108,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "A.png",
- "frame": {"x":120,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "ALT.png",
- "frame": {"x":132,"y":0,"w":16,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
- "sourceSize": {"w":16,"h":12}
-},
-{
- "filename": "B.png",
- "frame": {"x":148,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "BACK.png",
- "frame": {"x":160,"y":0,"w":24,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":24,"h":12},
- "sourceSize": {"w":24,"h":12}
-},
-{
- "filename": "C.png",
- "frame": {"x":184,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "CTRL.png",
- "frame": {"x":196,"y":0,"w":22,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":22,"h":12},
- "sourceSize": {"w":22,"h":12}
-},
-{
- "filename": "D.png",
- "frame": {"x":218,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "DEL.png",
- "frame": {"x":230,"y":0,"w":17,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":17,"h":12},
- "sourceSize": {"w":17,"h":12}
-},
-{
- "filename": "E.png",
- "frame": {"x":247,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "END.png",
- "frame": {"x":259,"y":0,"w":18,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":18,"h":12},
- "sourceSize": {"w":18,"h":12}
-},
-{
- "filename": "ENTER.png",
- "frame": {"x":277,"y":0,"w":27,"h":11},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":27,"h":11},
- "sourceSize": {"w":27,"h":11}
-},
-{
- "filename": "ESC.png",
- "frame": {"x":304,"y":0,"w":17,"h":11},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":17,"h":11},
- "sourceSize": {"w":17,"h":11}
-},
-{
- "filename": "F.png",
- "frame": {"x":321,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "F1.png",
- "frame": {"x":333,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F2.png",
- "frame": {"x":346,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F3.png",
- "frame": {"x":359,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F4.png",
- "frame": {"x":372,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F5.png",
- "frame": {"x":385,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F6.png",
- "frame": {"x":398,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F7.png",
- "frame": {"x":411,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F8.png",
- "frame": {"x":424,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F9.png",
- "frame": {"x":437,"y":0,"w":13,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":13,"h":12},
- "sourceSize": {"w":13,"h":12}
-},
-{
- "filename": "F10.png",
- "frame": {"x":450,"y":0,"w":16,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
- "sourceSize": {"w":16,"h":12}
-},
-{
- "filename": "F11.png",
- "frame": {"x":466,"y":0,"w":15,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":15,"h":12},
- "sourceSize": {"w":15,"h":12}
-},
-{
- "filename": "F12.png",
- "frame": {"x":481,"y":0,"w":16,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
- "sourceSize": {"w":16,"h":12}
-},
-{
- "filename": "G.png",
- "frame": {"x":497,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "H.png",
- "frame": {"x":509,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "HOME.png",
- "frame": {"x":521,"y":0,"w":23,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":23,"h":12},
- "sourceSize": {"w":23,"h":12}
-},
-{
- "filename": "I.png",
- "frame": {"x":544,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "INS.png",
- "frame": {"x":556,"y":0,"w":16,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":16,"h":12},
- "sourceSize": {"w":16,"h":12}
-},
-{
- "filename": "J.png",
- "frame": {"x":572,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "K.png",
- "frame": {"x":584,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "KEY_ARROW_DOWN.png",
- "frame": {"x":596,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "KEY_ARROW_LEFT.png",
- "frame": {"x":608,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "KEY_ARROW_RIGHT.png",
- "frame": {"x":620,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "KEY_ARROW_UP.png",
- "frame": {"x":632,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "L.png",
- "frame": {"x":644,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "LEFT_BRACKET.png",
- "frame": {"x":656,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "M.png",
- "frame": {"x":668,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "MINUS.png",
- "frame": {"x":680,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "N.png",
- "frame": {"x":692,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "O.png",
- "frame": {"x":704,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "P.png",
- "frame": {"x":716,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "PAGE_DOWN.png",
- "frame": {"x":728,"y":0,"w":20,"h":11},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":20,"h":11},
- "sourceSize": {"w":20,"h":11}
-},
-{
- "filename": "PAGE_UP.png",
- "frame": {"x":748,"y":0,"w":20,"h":11},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":20,"h":11},
- "sourceSize": {"w":20,"h":11}
-},
-{
- "filename": "PLUS.png",
- "frame": {"x":768,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "Q.png",
- "frame": {"x":780,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "QUOTE.png",
- "frame": {"x":792,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "R.png",
- "frame": {"x":804,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "RIGHT_BRACKET.png",
- "frame": {"x":816,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "S.png",
- "frame": {"x":828,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "SEMICOLON.png",
- "frame": {"x":840,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "SHIFT.png",
- "frame": {"x":852,"y":0,"w":23,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":23,"h":12},
- "sourceSize": {"w":23,"h":12}
-},
-{
- "filename": "SPACE.png",
- "frame": {"x":875,"y":0,"w":25,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":25,"h":12},
- "sourceSize": {"w":25,"h":12}
-},
-{
- "filename": "T.png",
- "frame": {"x":900,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "TAB.png",
- "frame": {"x":912,"y":0,"w":19,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":19,"h":12},
- "sourceSize": {"w":19,"h":12}
-},
-{
- "filename": "TILDE.png",
- "frame": {"x":931,"y":0,"w":15,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":15,"h":12},
- "sourceSize": {"w":15,"h":12}
-},
-{
- "filename": "U.png",
- "frame": {"x":946,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "V.png",
- "frame": {"x":958,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "W.png",
- "frame": {"x":970,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "X.png",
- "frame": {"x":982,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "Y.png",
- "frame": {"x":994,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-},
-{
- "filename": "Z.png",
- "frame": {"x":1006,"y":0,"w":12,"h":12},
- "rotated": false,
- "trimmed": false,
- "spriteSourceSize": {"x":0,"y":0,"w":12,"h":12},
- "sourceSize": {"w":12,"h":12}
-}],
-"meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "1.0",
- "image": "keyboard.png",
- "format": "RGBA8888",
- "size": {"w":1018,"h":12},
- "scale": "1",
- "smartupdate": "$TexturePacker:SmartUpdate:085d4353a5c4d18c90f82f8926710d72:45908b22b446cf7f4904d4e0b658b16a:bad03abb89ad027d879c383c13fd51bc$"
-}
-}
+{ "frames": {
+ "0.png": {
+ "frame": { "x": 12, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "1.png": {
+ "frame": { "x": 36, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "2.png": {
+ "frame": { "x": 0, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "3.png": {
+ "frame": { "x": 12, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "4.png": {
+ "frame": { "x": 24, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "5.png": {
+ "frame": { "x": 84, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "6.png": {
+ "frame": { "x": 96, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "7.png": {
+ "frame": { "x": 120, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "8.png": {
+ "frame": { "x": 132, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "9.png": {
+ "frame": { "x": 52, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "A.png": {
+ "frame": { "x": 64, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "ALT.png": {
+ "frame": { "x": 0, "y": 22, "w": 16, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
+ "sourceSize": { "w": 16, "h": 11 }
+ },
+ "B.png": {
+ "frame": { "x": 76, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "BACK.png": {
+ "frame": { "x": 80, "y": 0, "w": 24, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 24, "h": 11 },
+ "sourceSize": { "w": 24, "h": 11 }
+ },
+ "C.png": {
+ "frame": { "x": 88, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "CTRL.png": {
+ "frame": { "x": 0, "y": 11, "w": 22, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 22, "h": 11 },
+ "sourceSize": { "w": 22, "h": 11 }
+ },
+ "D.png": {
+ "frame": { "x": 100, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "DEL.png": {
+ "frame": { "x": 116, "y": 11, "w": 17, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 17, "h": 11 },
+ "sourceSize": { "w": 17, "h": 11 }
+ },
+ "E.png": {
+ "frame": { "x": 112, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "END.png": {
+ "frame": { "x": 81, "y": 11, "w": 18, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 11 },
+ "sourceSize": { "w": 18, "h": 11 }
+ },
+ "ENTER.png": {
+ "frame": { "x": 28, "y": 0, "w": 27, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 27, "h": 11 },
+ "sourceSize": { "w": 27, "h": 11 }
+ },
+ "ESC.png": {
+ "frame": { "x": 99, "y": 11, "w": 17, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 17, "h": 11 },
+ "sourceSize": { "w": 17, "h": 11 }
+ },
+ "F.png": {
+ "frame": { "x": 124, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "F1.png": {
+ "frame": { "x": 78, "y": 22, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F2.png": {
+ "frame": { "x": 91, "y": 22, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F3.png": {
+ "frame": { "x": 104, "y": 22, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F4.png": {
+ "frame": { "x": 117, "y": 22, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F5.png": {
+ "frame": { "x": 130, "y": 22, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F6.png": {
+ "frame": { "x": 0, "y": 33, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F7.png": {
+ "frame": { "x": 13, "y": 33, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F8.png": {
+ "frame": { "x": 26, "y": 33, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F9.png": {
+ "frame": { "x": 39, "y": 33, "w": 13, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
+ "sourceSize": { "w": 13, "h": 11 }
+ },
+ "F10.png": {
+ "frame": { "x": 16, "y": 22, "w": 16, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
+ "sourceSize": { "w": 16, "h": 11 }
+ },
+ "F11.png": {
+ "frame": { "x": 48, "y": 22, "w": 15, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 15, "h": 11 },
+ "sourceSize": { "w": 15, "h": 11 }
+ },
+ "F12.png": {
+ "frame": { "x": 133, "y": 11, "w": 16, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
+ "sourceSize": { "w": 16, "h": 11 }
+ },
+ "G.png": {
+ "frame": { "x": 136, "y": 33, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "H.png": {
+ "frame": { "x": 0, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "HOME.png": {
+ "frame": { "x": 104, "y": 0, "w": 23, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 23, "h": 11 },
+ "sourceSize": { "w": 23, "h": 11 }
+ },
+ "I.png": {
+ "frame": { "x": 24, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "INS.png": {
+ "frame": { "x": 32, "y": 22, "w": 16, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
+ "sourceSize": { "w": 16, "h": 11 }
+ },
+ "J.png": {
+ "frame": { "x": 48, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "K.png": {
+ "frame": { "x": 60, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "KEY_ARROW_DOWN.png": {
+ "frame": { "x": 72, "y": 66, "w": 11, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
+ "sourceSize": { "w": 11, "h": 11 }
+ },
+ "KEY_ARROW_LEFT.png": {
+ "frame": { "x": 72, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "KEY_ARROW_RIGHT.png": {
+ "frame": { "x": 84, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "KEY_ARROW_UP.png": {
+ "frame": { "x": 94, "y": 66, "w": 11, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
+ "sourceSize": { "w": 11, "h": 11 }
+ },
+ "L.png": {
+ "frame": { "x": 96, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "LEFT_BRACKET.png": {
+ "frame": { "x": 127, "y": 66, "w": 10, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 11 },
+ "sourceSize": { "w": 10, "h": 11 }
+ },
+ "M.png": {
+ "frame": { "x": 108, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "MINUS.png": {
+ "frame": { "x": 105, "y": 66, "w": 11, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
+ "sourceSize": { "w": 11, "h": 11 }
+ },
+ "N.png": {
+ "frame": { "x": 120, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "O.png": {
+ "frame": { "x": 12, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "P.png": {
+ "frame": { "x": 132, "y": 44, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "PAGE_DOWN.png": {
+ "frame": { "x": 22, "y": 11, "w": 20, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 11 },
+ "sourceSize": { "w": 20, "h": 11 }
+ },
+ "PAGE_UP.png": {
+ "frame": { "x": 42, "y": 11, "w": 20, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 11 },
+ "sourceSize": { "w": 20, "h": 11 }
+ },
+ "PLUS.png": {
+ "frame": { "x": 116, "y": 66, "w": 11, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
+ "sourceSize": { "w": 11, "h": 11 }
+ },
+ "Q.png": {
+ "frame": { "x": 36, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "QUOTE.png": {
+ "frame": { "x": 83, "y": 66, "w": 11, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
+ "sourceSize": { "w": 11, "h": 11 }
+ },
+ "R.png": {
+ "frame": { "x": 48, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "RIGHT_BRACKET.png": {
+ "frame": { "x": 137, "y": 66, "w": 10, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 11 },
+ "sourceSize": { "w": 10, "h": 11 }
+ },
+ "S.png": {
+ "frame": { "x": 60, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "SEMICOLON.png": {
+ "frame": { "x": 72, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "SHIFT.png": {
+ "frame": { "x": 127, "y": 0, "w": 23, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 23, "h": 11 },
+ "sourceSize": { "w": 23, "h": 11 }
+ },
+ "SPACE.png": {
+ "frame": { "x": 55, "y": 0, "w": 25, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 25, "h": 11 },
+ "sourceSize": { "w": 25, "h": 11 }
+ },
+ "T.png": {
+ "frame": { "x": 108, "y": 55, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "TAB.png": {
+ "frame": { "x": 62, "y": 11, "w": 19, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 11 },
+ "sourceSize": { "w": 19, "h": 11 }
+ },
+ "TILDE.png": {
+ "frame": { "x": 63, "y": 22, "w": 15, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 15, "h": 11 },
+ "sourceSize": { "w": 15, "h": 11 }
+ },
+ "U.png": {
+ "frame": { "x": 0, "y": 66, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "V.png": {
+ "frame": { "x": 12, "y": 66, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "W.png": {
+ "frame": { "x": 24, "y": 66, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "X.png": {
+ "frame": { "x": 36, "y": 66, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "Y.png": {
+ "frame": { "x": 48, "y": 66, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "Z.png": {
+ "frame": { "x": 60, "y": 66, "w": 12, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
+ "sourceSize": { "w": 12, "h": 11 }
+ },
+ "ACTION.png": {
+ "frame": { "x": 0, "y": 0, "w": 28, "h": 11 },
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": { "x": 0, "y": 0, "w": 28, "h": 11 },
+ "sourceSize": { "w": 28, "h": 11 }
+ }
+ },
+ "meta": {
+ "app": "https://www.aseprite.org/",
+ "version": "1.3.7-dev",
+ "image": "keyboard.png",
+ "format": "RGBA8888",
+ "size": { "w": 150, "h": 77 },
+ "scale": "1"
+ }
+}
diff --git a/public/images/inputs/keyboard.png b/public/images/inputs/keyboard.png
index 67b26af12de..e4d849be0fb 100644
Binary files a/public/images/inputs/keyboard.png and b/public/images/inputs/keyboard.png differ
diff --git a/public/images/items.json b/public/images/items.json
index bb86b46aa4d..442b93d657b 100644
--- a/public/images/items.json
+++ b/public/images/items.json
@@ -2971,7 +2971,7 @@
}
},
{
- "filename": "blank_memory",
+ "filename": "bug_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -2992,7 +2992,7 @@
}
},
{
- "filename": "bug_memory",
+ "filename": "charcoal",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -3013,7 +3013,7 @@
}
},
{
- "filename": "charcoal",
+ "filename": "dark_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -3034,7 +3034,7 @@
}
},
{
- "filename": "dark_memory",
+ "filename": "dire_hit",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -3433,7 +3433,7 @@
}
},
{
- "filename": "dire_hit",
+ "filename": "dna_splicers",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -3727,7 +3727,7 @@
}
},
{
- "filename": "dna_splicers",
+ "filename": "dragon_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -3979,7 +3979,7 @@
}
},
{
- "filename": "dragon_memory",
+ "filename": "electirizer",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4168,7 +4168,7 @@
}
},
{
- "filename": "electirizer",
+ "filename": "electric_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4189,7 +4189,7 @@
}
},
{
- "filename": "electric_memory",
+ "filename": "enigma_berry",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4231,7 +4231,7 @@
}
},
{
- "filename": "enigma_berry",
+ "filename": "fairy_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4252,7 +4252,7 @@
}
},
{
- "filename": "fairy_memory",
+ "filename": "fighting_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4272,27 +4272,6 @@
"h": 22
}
},
- {
- "filename": "fighting_memory",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 193,
- "y": 212,
- "w": 22,
- "h": 22
- }
- },
{
"filename": "fire_memory",
"rotated": false,
@@ -4309,7 +4288,7 @@
},
"frame": {
"x": 193,
- "y": 234,
+ "y": 212,
"w": 22,
"h": 22
}
@@ -4330,7 +4309,7 @@
},
"frame": {
"x": 193,
- "y": 256,
+ "y": 234,
"w": 22,
"h": 22
}
@@ -4350,8 +4329,8 @@
"h": 22
},
"frame": {
- "x": 215,
- "y": 212,
+ "x": 193,
+ "y": 256,
"w": 22,
"h": 22
}
@@ -4372,7 +4351,7 @@
},
"frame": {
"x": 215,
- "y": 234,
+ "y": 212,
"w": 22,
"h": 22
}
@@ -4393,7 +4372,7 @@
},
"frame": {
"x": 215,
- "y": 256,
+ "y": 234,
"w": 22,
"h": 22
}
@@ -4412,6 +4391,27 @@
"w": 22,
"h": 22
},
+ "frame": {
+ "x": 215,
+ "y": 256,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "guard_spec",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
"frame": {
"x": 195,
"y": 278,
@@ -4420,7 +4420,7 @@
}
},
{
- "filename": "guard_spec",
+ "filename": "hard_meteorite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4428,15 +4428,15 @@
"h": 32
},
"spriteSourceSize": {
- "x": 5,
+ "x": 7,
"y": 5,
- "w": 22,
+ "w": 20,
"h": 22
},
"frame": {
"x": 217,
"y": 278,
- "w": 22,
+ "w": 20,
"h": 22
}
},
@@ -4476,8 +4476,8 @@
"h": 22
},
"frame": {
- "x": 236,
- "y": 127,
+ "x": 258,
+ "y": 105,
"w": 22,
"h": 22
}
@@ -4497,8 +4497,8 @@
"h": 22
},
"frame": {
- "x": 258,
- "y": 105,
+ "x": 236,
+ "y": 127,
"w": 22,
"h": 22
}
@@ -4525,7 +4525,7 @@
}
},
{
- "filename": "poison_memory",
+ "filename": "normal_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4546,7 +4546,7 @@
}
},
{
- "filename": "protector",
+ "filename": "poison_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4588,7 +4588,7 @@
}
},
{
- "filename": "psychic_memory",
+ "filename": "protector",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4609,7 +4609,7 @@
}
},
{
- "filename": "rock_memory",
+ "filename": "psychic_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4650,48 +4650,6 @@
"h": 23
}
},
- {
- "filename": "hard_meteorite",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 5,
- "w": 20,
- "h": 22
- },
- "frame": {
- "x": 237,
- "y": 235,
- "w": 20,
- "h": 22
- }
- },
- {
- "filename": "liechi_berry",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 6,
- "w": 22,
- "h": 21
- },
- "frame": {
- "x": 237,
- "y": 257,
- "w": 22,
- "h": 21
- }
- },
{
"filename": "potion",
"rotated": false,
@@ -4734,6 +4692,27 @@
"h": 21
}
},
+ {
+ "filename": "liechi_berry",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 22,
+ "h": 21
+ },
+ "frame": {
+ "x": 302,
+ "y": 128,
+ "w": 22,
+ "h": 21
+ }
+ },
{
"filename": "reviver_seed",
"rotated": false,
@@ -4749,12 +4728,54 @@
"h": 20
},
"frame": {
- "x": 302,
- "y": 128,
+ "x": 342,
+ "y": 107,
"w": 23,
"h": 20
}
},
+ {
+ "filename": "shell_bell",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 7,
+ "w": 23,
+ "h": 20
+ },
+ "frame": {
+ "x": 365,
+ "y": 107,
+ "w": 23,
+ "h": 20
+ }
+ },
+ {
+ "filename": "rock_memory",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 237,
+ "y": 235,
+ "w": 22,
+ "h": 22
+ }
+ },
{
"filename": "scroll_of_darkness",
"rotated": false,
@@ -4770,8 +4791,8 @@
"h": 22
},
"frame": {
- "x": 342,
- "y": 107,
+ "x": 237,
+ "y": 257,
"w": 22,
"h": 22
}
@@ -4791,75 +4812,12 @@
"h": 22
},
"frame": {
- "x": 364,
- "y": 107,
+ "x": 237,
+ "y": 279,
"w": 22,
"h": 22
}
},
- {
- "filename": "shed_shell",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 386,
- "y": 107,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "oval_stone",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 7,
- "w": 18,
- "h": 19
- },
- "frame": {
- "x": 408,
- "y": 108,
- "w": 18,
- "h": 19
- }
- },
- {
- "filename": "sitrus_berry",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 5,
- "w": 20,
- "h": 22
- },
- "frame": {
- "x": 239,
- "y": 278,
- "w": 20,
- "h": 22
- }
- },
{
"filename": "upgrade",
"rotated": false,
@@ -4882,7 +4840,7 @@
}
},
{
- "filename": "starf_berry",
+ "filename": "shed_shell",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4903,7 +4861,7 @@
}
},
{
- "filename": "steel_memory",
+ "filename": "starf_berry",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4924,7 +4882,7 @@
}
},
{
- "filename": "thick_club",
+ "filename": "steel_memory",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4945,7 +4903,7 @@
}
},
{
- "filename": "thunder_stone",
+ "filename": "big_nugget",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -4953,16 +4911,37 @@
"h": 32
},
"spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
},
"frame": {
- "x": 257,
- "y": 234,
- "w": 22,
- "h": 22
+ "x": 388,
+ "y": 107,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "oval_stone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 19
+ },
+ "frame": {
+ "x": 408,
+ "y": 108,
+ "w": 18,
+ "h": 19
}
},
{
@@ -4987,7 +4966,28 @@
}
},
{
- "filename": "tm_bug",
+ "filename": "sitrus_berry",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 5,
+ "w": 20,
+ "h": 22
+ },
+ "frame": {
+ "x": 281,
+ "y": 168,
+ "w": 20,
+ "h": 22
+ }
+ },
+ {
+ "filename": "thick_club",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5001,14 +5001,14 @@
"h": 22
},
"frame": {
- "x": 281,
- "y": 168,
+ "x": 301,
+ "y": 149,
"w": 22,
"h": 22
}
},
{
- "filename": "tm_dark",
+ "filename": "thunder_stone",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5029,7 +5029,7 @@
}
},
{
- "filename": "tm_dragon",
+ "filename": "tm_bug",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5050,7 +5050,7 @@
}
},
{
- "filename": "tm_electric",
+ "filename": "tm_dark",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5064,14 +5064,14 @@
"h": 22
},
"frame": {
- "x": 279,
+ "x": 259,
"y": 234,
"w": 22,
"h": 22
}
},
{
- "filename": "tm_fairy",
+ "filename": "tm_dragon",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5092,7 +5092,7 @@
}
},
{
- "filename": "tm_fighting",
+ "filename": "tm_electric",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5112,6 +5112,48 @@
"h": 22
}
},
+ {
+ "filename": "tm_fairy",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 281,
+ "y": 234,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_fighting",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 281,
+ "y": 256,
+ "w": 22,
+ "h": 22
+ }
+ },
{
"filename": "tm_fire",
"rotated": false,
@@ -5128,11 +5170,53 @@
},
"frame": {
"x": 281,
- "y": 256,
+ "y": 278,
"w": 22,
"h": 22
}
},
+ {
+ "filename": "lum_berry",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 7,
+ "w": 20,
+ "h": 19
+ },
+ "frame": {
+ "x": 301,
+ "y": 171,
+ "w": 20,
+ "h": 19
+ }
+ },
+ {
+ "filename": "metal_coat",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 5,
+ "w": 19,
+ "h": 22
+ },
+ "frame": {
+ "x": 302,
+ "y": 190,
+ "w": 19,
+ "h": 22
+ }
+ },
{
"filename": "tm_flying",
"rotated": false,
@@ -5148,56 +5232,14 @@
"h": 22
},
"frame": {
- "x": 281,
- "y": 278,
+ "x": 301,
+ "y": 212,
"w": 22,
"h": 22
}
},
{
- "filename": "baton",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 7,
- "w": 18,
- "h": 18
- },
- "frame": {
- "x": 408,
- "y": 127,
- "w": 18,
- "h": 18
- }
- },
- {
- "filename": "golden_egg",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 6,
- "w": 17,
- "h": 20
- },
- "frame": {
- "x": 325,
- "y": 128,
- "w": 17,
- "h": 20
- }
- },
- {
- "filename": "shell_bell",
+ "filename": "tm_ghost",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5206,19 +5248,19 @@
},
"spriteSourceSize": {
"x": 5,
- "y": 7,
- "w": 23,
- "h": 20
+ "y": 5,
+ "w": 22,
+ "h": 22
},
"frame": {
- "x": 342,
- "y": 129,
- "w": 23,
- "h": 20
+ "x": 303,
+ "y": 234,
+ "w": 22,
+ "h": 22
}
},
{
- "filename": "deep_sea_scale",
+ "filename": "tm_grass",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5227,15 +5269,36 @@
},
"spriteSourceSize": {
"x": 5,
- "y": 6,
+ "y": 5,
"w": 22,
- "h": 20
+ "h": 22
},
"frame": {
- "x": 365,
- "y": 129,
+ "x": 303,
+ "y": 256,
"w": 22,
- "h": 20
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_ground",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 303,
+ "y": 278,
+ "w": 22,
+ "h": 22
}
},
{
@@ -5253,14 +5316,14 @@
"h": 21
},
"frame": {
- "x": 387,
- "y": 129,
+ "x": 324,
+ "y": 128,
"w": 21,
"h": 21
}
},
{
- "filename": "candy",
+ "filename": "tm_ice",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5268,15 +5331,498 @@
"h": 32
},
"spriteSourceSize": {
- "x": 7,
- "y": 11,
- "w": 18,
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 323,
+ "y": 149,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_normal",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 321,
+ "y": 171,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_poison",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 345,
+ "y": 127,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_psychic",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 345,
+ "y": 149,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_rock",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 343,
+ "y": 171,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_steel",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 367,
+ "y": 127,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "tm_water",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 367,
+ "y": 149,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "water_memory",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 365,
+ "y": 171,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "water_stone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 389,
+ "y": 127,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "full_heal",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 9,
+ "y": 4,
+ "w": 15,
+ "h": 23
+ },
+ "frame": {
+ "x": 411,
+ "y": 127,
+ "w": 15,
+ "h": 23
+ }
+ },
+ {
+ "filename": "x_accuracy",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 389,
+ "y": 149,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "leftovers",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 8,
+ "y": 5,
+ "w": 15,
+ "h": 22
+ },
+ "frame": {
+ "x": 411,
+ "y": 150,
+ "w": 15,
+ "h": 22
+ }
+ },
+ {
+ "filename": "x_attack",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 387,
+ "y": 171,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "super_potion",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 8,
+ "y": 5,
+ "w": 17,
+ "h": 23
+ },
+ "frame": {
+ "x": 409,
+ "y": 172,
+ "w": 17,
+ "h": 23
+ }
+ },
+ {
+ "filename": "power_herb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 7,
+ "w": 20,
+ "h": 19
+ },
+ "frame": {
+ "x": 321,
+ "y": 193,
+ "w": 20,
+ "h": 19
+ }
+ },
+ {
+ "filename": "x_defense",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 323,
+ "y": 212,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "razor_claw",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 7,
+ "w": 20,
+ "h": 19
+ },
+ "frame": {
+ "x": 341,
+ "y": 193,
+ "w": 20,
+ "h": 19
+ }
+ },
+ {
+ "filename": "x_sp_atk",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 325,
+ "y": 234,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "x_sp_def",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 325,
+ "y": 256,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "x_speed",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 5,
+ "w": 22,
+ "h": 22
+ },
+ "frame": {
+ "x": 325,
+ "y": 278,
+ "w": 22,
+ "h": 22
+ }
+ },
+ {
+ "filename": "deep_sea_scale",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 22,
+ "h": 20
+ },
+ "frame": {
+ "x": 361,
+ "y": 193,
+ "w": 22,
+ "h": 20
+ }
+ },
+ {
+ "filename": "fairy_feather",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 7,
+ "w": 22,
+ "h": 20
+ },
+ "frame": {
+ "x": 383,
+ "y": 193,
+ "w": 22,
+ "h": 20
+ }
+ },
+ {
+ "filename": "shiny_stone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 21,
+ "h": 21
+ },
+ "frame": {
+ "x": 405,
+ "y": 195,
+ "w": 21,
+ "h": 21
+ }
+ },
+ {
+ "filename": "mystery_egg",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 8,
+ "y": 8,
+ "w": 16,
"h": 18
},
"frame": {
- "x": 408,
- "y": 145,
- "w": 18,
+ "x": 345,
+ "y": 212,
+ "w": 16,
"h": 18
}
},
@@ -5295,35 +5841,14 @@
"h": 17
},
"frame": {
- "x": 195,
- "y": 300,
+ "x": 361,
+ "y": 213,
"w": 23,
"h": 17
}
},
{
- "filename": "relic_crown",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 4,
- "y": 7,
- "w": 23,
- "h": 18
- },
- "frame": {
- "x": 218,
- "y": 300,
- "w": 23,
- "h": 18
- }
- },
- {
- "filename": "fairy_feather",
+ "filename": "masterpiece_teacup",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5333,13 +5858,286 @@
"spriteSourceSize": {
"x": 5,
"y": 7,
+ "w": 21,
+ "h": 18
+ },
+ "frame": {
+ "x": 384,
+ "y": 213,
+ "w": 21,
+ "h": 18
+ }
+ },
+ {
+ "filename": "zoom_lens",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 21,
+ "h": 21
+ },
+ "frame": {
+ "x": 405,
+ "y": 216,
+ "w": 21,
+ "h": 21
+ }
+ },
+ {
+ "filename": "sweet_apple",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
"w": 22,
+ "h": 21
+ },
+ "frame": {
+ "x": 347,
+ "y": 230,
+ "w": 22,
+ "h": 21
+ }
+ },
+ {
+ "filename": "syrupy_apple",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 22,
+ "h": 21
+ },
+ "frame": {
+ "x": 347,
+ "y": 251,
+ "w": 22,
+ "h": 21
+ }
+ },
+ {
+ "filename": "tart_apple",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 22,
+ "h": 21
+ },
+ "frame": {
+ "x": 347,
+ "y": 272,
+ "w": 22,
+ "h": 21
+ }
+ },
+ {
+ "filename": "eviolite",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 8,
+ "y": 8,
+ "w": 15,
+ "h": 15
+ },
+ "frame": {
+ "x": 369,
+ "y": 230,
+ "w": 15,
+ "h": 15
+ }
+ },
+ {
+ "filename": "sharp_meteorite",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 8,
+ "w": 21,
+ "h": 18
+ },
+ "frame": {
+ "x": 384,
+ "y": 231,
+ "w": 21,
+ "h": 18
+ }
+ },
+ {
+ "filename": "unremarkable_teacup",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 7,
+ "w": 21,
+ "h": 18
+ },
+ "frame": {
+ "x": 405,
+ "y": 237,
+ "w": 21,
+ "h": 18
+ }
+ },
+ {
+ "filename": "prism_scale",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 9,
+ "y": 8,
+ "w": 15,
+ "h": 15
+ },
+ "frame": {
+ "x": 369,
+ "y": 245,
+ "w": 15,
+ "h": 15
+ }
+ },
+ {
+ "filename": "metronome",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 5,
+ "w": 17,
+ "h": 22
+ },
+ "frame": {
+ "x": 369,
+ "y": 260,
+ "w": 17,
+ "h": 22
+ }
+ },
+ {
+ "filename": "quick_claw",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 19,
+ "h": 21
+ },
+ "frame": {
+ "x": 386,
+ "y": 249,
+ "w": 19,
+ "h": 21
+ }
+ },
+ {
+ "filename": "blue_orb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 20,
"h": 20
},
"frame": {
- "x": 241,
- "y": 300,
- "w": 22,
+ "x": 405,
+ "y": 255,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "candy_jar",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 19,
+ "h": 20
+ },
+ "frame": {
+ "x": 386,
+ "y": 270,
+ "w": 19,
+ "h": 20
+ }
+ },
+ {
+ "filename": "golden_egg",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 6,
+ "w": 17,
+ "h": 20
+ },
+ "frame": {
+ "x": 369,
+ "y": 282,
+ "w": 17,
"h": 20
}
},
@@ -5358,14 +6156,56 @@
"h": 20
},
"frame": {
- "x": 263,
- "y": 300,
+ "x": 347,
+ "y": 293,
"w": 22,
"h": 20
}
},
{
- "filename": "big_nugget",
+ "filename": "everstone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 8,
+ "w": 20,
+ "h": 17
+ },
+ "frame": {
+ "x": 405,
+ "y": 275,
+ "w": 20,
+ "h": 17
+ }
+ },
+ {
+ "filename": "hard_stone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 19,
+ "h": 20
+ },
+ "frame": {
+ "x": 386,
+ "y": 290,
+ "w": 19,
+ "h": 20
+ }
+ },
+ {
+ "filename": "gb",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5379,7 +6219,217 @@
"h": 20
},
"frame": {
- "x": 285,
+ "x": 405,
+ "y": 292,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "lucky_egg",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 6,
+ "w": 17,
+ "h": 20
+ },
+ "frame": {
+ "x": 369,
+ "y": 302,
+ "w": 17,
+ "h": 20
+ }
+ },
+ {
+ "filename": "miracle_seed",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 7,
+ "w": 19,
+ "h": 19
+ },
+ "frame": {
+ "x": 386,
+ "y": 310,
+ "w": 19,
+ "h": 19
+ }
+ },
+ {
+ "filename": "magnet",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
+ },
+ "frame": {
+ "x": 405,
+ "y": 312,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "relic_crown",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 4,
+ "y": 7,
+ "w": 23,
+ "h": 18
+ },
+ "frame": {
+ "x": 195,
+ "y": 300,
+ "w": 23,
+ "h": 18
+ }
+ },
+ {
+ "filename": "spell_tag",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 6,
+ "w": 19,
+ "h": 21
+ },
+ "frame": {
+ "x": 218,
+ "y": 300,
+ "w": 19,
+ "h": 21
+ }
+ },
+ {
+ "filename": "tera_orb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 5,
+ "y": 6,
+ "w": 22,
+ "h": 20
+ },
+ "frame": {
+ "x": 237,
+ "y": 301,
+ "w": 22,
+ "h": 20
+ }
+ },
+ {
+ "filename": "mb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
+ },
+ "frame": {
+ "x": 259,
+ "y": 300,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "pb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
+ },
+ "frame": {
+ "x": 279,
+ "y": 300,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "pb_gold",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
+ },
+ "frame": {
+ "x": 299,
+ "y": 300,
+ "w": 20,
+ "h": 20
+ }
+ },
+ {
+ "filename": "rb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
+ },
+ "frame": {
+ "x": 319,
"y": 300,
"w": 20,
"h": 20
@@ -5407,7 +6457,7 @@
}
},
{
- "filename": "lucky_egg",
+ "filename": "soothe_bell",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5415,16 +6465,16 @@
"h": 32
},
"spriteSourceSize": {
- "x": 7,
- "y": 6,
+ "x": 8,
+ "y": 5,
"w": 17,
- "h": 20
+ "h": 22
},
"frame": {
"x": 178,
"y": 305,
"w": 17,
- "h": 20
+ "h": 22
}
},
{
@@ -5443,13 +6493,13 @@
},
"frame": {
"x": 195,
- "y": 317,
+ "y": 318,
"w": 23,
"h": 17
}
},
{
- "filename": "sweet_apple",
+ "filename": "smooth_meteorite",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5457,20 +6507,20 @@
"h": 32
},
"spriteSourceSize": {
- "x": 5,
+ "x": 7,
"y": 6,
- "w": 22,
- "h": 21
+ "w": 20,
+ "h": 20
},
"frame": {
"x": 218,
- "y": 318,
- "w": 22,
- "h": 21
+ "y": 321,
+ "w": 20,
+ "h": 20
}
},
{
- "filename": "syrupy_apple",
+ "filename": "strange_ball",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5478,58 +6528,16 @@
"h": 32
},
"spriteSourceSize": {
- "x": 5,
+ "x": 6,
"y": 6,
- "w": 22,
- "h": 21
+ "w": 20,
+ "h": 20
},
"frame": {
- "x": 240,
- "y": 320,
- "w": 22,
- "h": 21
- }
- },
- {
- "filename": "tart_apple",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 6,
- "w": 22,
- "h": 21
- },
- "frame": {
- "x": 262,
- "y": 320,
- "w": 22,
- "h": 21
- }
- },
- {
- "filename": "shiny_stone",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 6,
- "w": 21,
- "h": 21
- },
- "frame": {
- "x": 284,
- "y": 320,
- "w": 21,
- "h": 21
+ "x": 238,
+ "y": 321,
+ "w": 20,
+ "h": 20
}
},
{
@@ -5554,7 +6562,7 @@
}
},
{
- "filename": "tm_ghost",
+ "filename": "ub",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -5562,541 +6570,16 @@
"h": 32
},
"spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
+ "x": 6,
+ "y": 6,
+ "w": 20,
+ "h": 20
},
"frame": {
"x": 135,
"y": 325,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "zoom_lens",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 6,
- "w": 21,
- "h": 21
- },
- "frame": {
- "x": 157,
- "y": 322,
- "w": 21,
- "h": 21
- }
- },
- {
- "filename": "metronome",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 5,
- "w": 17,
- "h": 22
- },
- "frame": {
- "x": 178,
- "y": 325,
- "w": 17,
- "h": 22
- }
- },
- {
- "filename": "tm_grass",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 136,
- "y": 347,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "blue_orb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
"w": 20,
"h": 20
- },
- "frame": {
- "x": 158,
- "y": 343,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "super_potion",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 5,
- "w": 17,
- "h": 23
- },
- "frame": {
- "x": 141,
- "y": 369,
- "w": 17,
- "h": 23
- }
- },
- {
- "filename": "tm_ground",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 158,
- "y": 363,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "metal_coat",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 5,
- "w": 19,
- "h": 22
- },
- "frame": {
- "x": 141,
- "y": 392,
- "w": 19,
- "h": 22
- }
- },
- {
- "filename": "tera_orb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 6,
- "w": 22,
- "h": 20
- },
- "frame": {
- "x": 195,
- "y": 334,
- "w": 22,
- "h": 20
- }
- },
- {
- "filename": "tm_ice",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 217,
- "y": 339,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "tm_normal",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 239,
- "y": 341,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "tm_poison",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 261,
- "y": 341,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "tm_psychic",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 283,
- "y": 341,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "tm_rock",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 160,
- "y": 385,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "lum_berry",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 20,
- "h": 19
- },
- "frame": {
- "x": 160,
- "y": 407,
- "w": 20,
- "h": 19
- }
- },
- {
- "filename": "power_herb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 20,
- "h": 19
- },
- "frame": {
- "x": 180,
- "y": 407,
- "w": 20,
- "h": 19
- }
- },
- {
- "filename": "altarianite",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 8,
- "w": 16,
- "h": 16
- },
- "frame": {
- "x": 178,
- "y": 347,
- "w": 16,
- "h": 16
- }
- },
- {
- "filename": "leftovers",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 5,
- "w": 15,
- "h": 22
- },
- "frame": {
- "x": 180,
- "y": 363,
- "w": 15,
- "h": 22
- }
- },
- {
- "filename": "tm_steel",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 182,
- "y": 385,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "razor_claw",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 20,
- "h": 19
- },
- "frame": {
- "x": 200,
- "y": 407,
- "w": 20,
- "h": 19
- }
- },
- {
- "filename": "tm_water",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 195,
- "y": 354,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "water_memory",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 217,
- "y": 361,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "water_stone",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 239,
- "y": 363,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "x_accuracy",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 261,
- "y": 363,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "x_attack",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 283,
- "y": 363,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "soothe_bell",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 5,
- "w": 17,
- "h": 22
- },
- "frame": {
- "x": 204,
- "y": 385,
- "w": 17,
- "h": 22
}
},
{
@@ -6114,537 +6597,12 @@
"h": 19
},
"frame": {
- "x": 220,
- "y": 407,
+ "x": 155,
+ "y": 322,
"w": 20,
"h": 19
}
},
- {
- "filename": "x_defense",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 221,
- "y": 385,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "x_sp_atk",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 243,
- "y": 385,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "miracle_seed",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 19,
- "h": 19
- },
- "frame": {
- "x": 240,
- "y": 407,
- "w": 19,
- "h": 19
- }
- },
- {
- "filename": "x_sp_def",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 265,
- "y": 385,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "x_speed",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 5,
- "w": 22,
- "h": 22
- },
- "frame": {
- "x": 287,
- "y": 385,
- "w": 22,
- "h": 22
- }
- },
- {
- "filename": "masterpiece_teacup",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 7,
- "w": 21,
- "h": 18
- },
- "frame": {
- "x": 259,
- "y": 407,
- "w": 21,
- "h": 18
- }
- },
- {
- "filename": "sharp_meteorite",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 8,
- "w": 21,
- "h": 18
- },
- "frame": {
- "x": 280,
- "y": 407,
- "w": 21,
- "h": 18
- }
- },
- {
- "filename": "dark_stone",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 7,
- "w": 18,
- "h": 18
- },
- "frame": {
- "x": 301,
- "y": 407,
- "w": 18,
- "h": 18
- }
- },
- {
- "filename": "relic_gold",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 9,
- "y": 11,
- "w": 15,
- "h": 11
- },
- "frame": {
- "x": 0,
- "y": 414,
- "w": 15,
- "h": 11
- }
- },
- {
- "filename": "full_heal",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 9,
- "y": 4,
- "w": 15,
- "h": 23
- },
- "frame": {
- "x": 301,
- "y": 212,
- "w": 15,
- "h": 23
- }
- },
- {
- "filename": "quick_claw",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 19,
- "h": 21
- },
- "frame": {
- "x": 301,
- "y": 235,
- "w": 19,
- "h": 21
- }
- },
- {
- "filename": "spell_tag",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 6,
- "w": 19,
- "h": 21
- },
- "frame": {
- "x": 303,
- "y": 256,
- "w": 19,
- "h": 21
- }
- },
- {
- "filename": "candy_jar",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 19,
- "h": 20
- },
- "frame": {
- "x": 303,
- "y": 277,
- "w": 19,
- "h": 20
- }
- },
- {
- "filename": "gb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 305,
- "y": 297,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "magnet",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 305,
- "y": 317,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "mb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 305,
- "y": 337,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "pb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 305,
- "y": 357,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "hard_stone",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 19,
- "h": 20
- },
- "frame": {
- "x": 309,
- "y": 377,
- "w": 19,
- "h": 20
- }
- },
- {
- "filename": "pb_gold",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 302,
- "y": 148,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "rb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 322,
- "y": 148,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "smooth_meteorite",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 303,
- "y": 168,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "strange_ball",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 342,
- "y": 149,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "ub",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 6,
- "w": 20,
- "h": 20
- },
- "frame": {
- "x": 362,
- "y": 149,
- "w": 20,
- "h": 20
- }
- },
- {
- "filename": "flame_orb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 7,
- "w": 18,
- "h": 18
- },
- "frame": {
- "x": 323,
- "y": 168,
- "w": 18,
- "h": 18
- }
- },
- {
- "filename": "unremarkable_teacup",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 5,
- "y": 7,
- "w": 21,
- "h": 18
- },
- "frame": {
- "x": 341,
- "y": 169,
- "w": 21,
- "h": 18
- }
- },
{
"filename": "wl_ability_urge",
"rotated": false,
@@ -6660,8 +6618,8 @@
"h": 18
},
"frame": {
- "x": 362,
- "y": 169,
+ "x": 175,
+ "y": 327,
"w": 20,
"h": 18
}
@@ -6681,12 +6639,33 @@
"h": 18
},
"frame": {
- "x": 382,
- "y": 150,
+ "x": 136,
+ "y": 345,
"w": 20,
"h": 18
}
},
+ {
+ "filename": "baton",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 18
+ },
+ "frame": {
+ "x": 156,
+ "y": 341,
+ "w": 18,
+ "h": 18
+ }
+ },
{
"filename": "wl_awakening",
"rotated": false,
@@ -6702,8 +6681,8 @@
"h": 18
},
"frame": {
- "x": 382,
- "y": 168,
+ "x": 174,
+ "y": 345,
"w": 20,
"h": 18
}
@@ -6723,33 +6702,12 @@
"h": 18
},
"frame": {
- "x": 402,
- "y": 163,
+ "x": 195,
+ "y": 335,
"w": 20,
"h": 18
}
},
- {
- "filename": "everstone",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 8,
- "w": 20,
- "h": 17
- },
- "frame": {
- "x": 402,
- "y": 181,
- "w": 20,
- "h": 17
- }
- },
{
"filename": "wl_custom_spliced",
"rotated": false,
@@ -6765,8 +6723,8 @@
"h": 18
},
"frame": {
- "x": 382,
- "y": 186,
+ "x": 215,
+ "y": 341,
"w": 20,
"h": 18
}
@@ -6786,8 +6744,8 @@
"h": 18
},
"frame": {
- "x": 402,
- "y": 198,
+ "x": 235,
+ "y": 341,
"w": 20,
"h": 18
}
@@ -6807,33 +6765,12 @@
"h": 18
},
"frame": {
- "x": 303,
- "y": 188,
+ "x": 194,
+ "y": 353,
"w": 20,
"h": 18
}
},
- {
- "filename": "light_ball",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 7,
- "w": 18,
- "h": 18
- },
- "frame": {
- "x": 323,
- "y": 186,
- "w": 18,
- "h": 18
- }
- },
{
"filename": "wl_ether",
"rotated": false,
@@ -6849,8 +6786,8 @@
"h": 18
},
"frame": {
- "x": 341,
- "y": 187,
+ "x": 214,
+ "y": 359,
"w": 20,
"h": 18
}
@@ -6870,14 +6807,14 @@
"h": 18
},
"frame": {
- "x": 361,
- "y": 187,
+ "x": 234,
+ "y": 359,
"w": 20,
"h": 18
}
},
{
- "filename": "light_stone",
+ "filename": "candy",
"rotated": false,
"trimmed": true,
"sourceSize": {
@@ -6886,13 +6823,13 @@
},
"spriteSourceSize": {
"x": 7,
- "y": 7,
+ "y": 11,
"w": 18,
"h": 18
},
"frame": {
- "x": 323,
- "y": 204,
+ "x": 156,
+ "y": 359,
"w": 18,
"h": 18
}
@@ -6912,8 +6849,8 @@
"h": 18
},
"frame": {
- "x": 341,
- "y": 205,
+ "x": 174,
+ "y": 363,
"w": 20,
"h": 18
}
@@ -6933,8 +6870,8 @@
"h": 18
},
"frame": {
- "x": 361,
- "y": 205,
+ "x": 194,
+ "y": 371,
"w": 20,
"h": 18
}
@@ -6954,8 +6891,8 @@
"h": 18
},
"frame": {
- "x": 381,
- "y": 204,
+ "x": 214,
+ "y": 377,
"w": 20,
"h": 18
}
@@ -6975,12 +6912,33 @@
"h": 18
},
"frame": {
- "x": 401,
- "y": 216,
+ "x": 234,
+ "y": 377,
"w": 20,
"h": 18
}
},
+ {
+ "filename": "relic_gold",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 9,
+ "y": 11,
+ "w": 15,
+ "h": 11
+ },
+ "frame": {
+ "x": 141,
+ "y": 363,
+ "w": 15,
+ "h": 11
+ }
+ },
{
"filename": "wl_item_drop",
"rotated": false,
@@ -6996,8 +6954,8 @@
"h": 18
},
"frame": {
- "x": 381,
- "y": 222,
+ "x": 141,
+ "y": 377,
"w": 20,
"h": 18
}
@@ -7017,8 +6975,8 @@
"h": 18
},
"frame": {
- "x": 401,
- "y": 234,
+ "x": 141,
+ "y": 395,
"w": 20,
"h": 18
}
@@ -7038,8 +6996,8 @@
"h": 18
},
"frame": {
- "x": 320,
- "y": 222,
+ "x": 161,
+ "y": 381,
"w": 20,
"h": 18
}
@@ -7059,8 +7017,8 @@
"h": 18
},
"frame": {
- "x": 340,
- "y": 223,
+ "x": 161,
+ "y": 399,
"w": 20,
"h": 18
}
@@ -7080,96 +7038,12 @@
"h": 18
},
"frame": {
- "x": 360,
- "y": 223,
+ "x": 181,
+ "y": 389,
"w": 20,
"h": 18
}
},
- {
- "filename": "ampharosite",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 8,
- "w": 16,
- "h": 16
- },
- "frame": {
- "x": 320,
- "y": 240,
- "w": 16,
- "h": 16
- }
- },
- {
- "filename": "mystery_egg",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 8,
- "w": 16,
- "h": 18
- },
- "frame": {
- "x": 322,
- "y": 256,
- "w": 16,
- "h": 18
- }
- },
- {
- "filename": "toxic_orb",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 7,
- "y": 7,
- "w": 18,
- "h": 18
- },
- "frame": {
- "x": 322,
- "y": 274,
- "w": 18,
- "h": 18
- }
- },
- {
- "filename": "eviolite",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 8,
- "y": 8,
- "w": 15,
- "h": 15
- },
- "frame": {
- "x": 336,
- "y": 241,
- "w": 15,
- "h": 15
- }
- },
{
"filename": "wl_max_revive",
"rotated": false,
@@ -7185,8 +7059,8 @@
"h": 18
},
"frame": {
- "x": 351,
- "y": 241,
+ "x": 181,
+ "y": 407,
"w": 20,
"h": 18
}
@@ -7206,8 +7080,8 @@
"h": 18
},
"frame": {
- "x": 325,
- "y": 292,
+ "x": 201,
+ "y": 395,
"w": 20,
"h": 18
}
@@ -7227,12 +7101,54 @@
"h": 18
},
"frame": {
- "x": 325,
- "y": 310,
+ "x": 221,
+ "y": 395,
"w": 20,
"h": 18
}
},
+ {
+ "filename": "dark_stone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 18
+ },
+ "frame": {
+ "x": 241,
+ "y": 395,
+ "w": 18,
+ "h": 18
+ }
+ },
+ {
+ "filename": "flame_orb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 18
+ },
+ "frame": {
+ "x": 255,
+ "y": 341,
+ "w": 18,
+ "h": 18
+ }
+ },
{
"filename": "wl_reset_urge",
"rotated": false,
@@ -7248,8 +7164,8 @@
"h": 18
},
"frame": {
- "x": 325,
- "y": 328,
+ "x": 254,
+ "y": 359,
"w": 20,
"h": 18
}
@@ -7269,12 +7185,33 @@
"h": 18
},
"frame": {
- "x": 325,
- "y": 346,
+ "x": 254,
+ "y": 377,
"w": 20,
"h": 18
}
},
+ {
+ "filename": "light_ball",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 18
+ },
+ "frame": {
+ "x": 259,
+ "y": 395,
+ "w": 18,
+ "h": 18
+ }
+ },
{
"filename": "wl_super_potion",
"rotated": false,
@@ -7290,12 +7227,96 @@
"h": 18
},
"frame": {
- "x": 371,
- "y": 241,
+ "x": 259,
+ "y": 320,
"w": 20,
"h": 18
}
},
+ {
+ "filename": "light_stone",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 18
+ },
+ "frame": {
+ "x": 279,
+ "y": 320,
+ "w": 18,
+ "h": 18
+ }
+ },
+ {
+ "filename": "toxic_orb",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 7,
+ "y": 7,
+ "w": 18,
+ "h": 18
+ },
+ "frame": {
+ "x": 297,
+ "y": 320,
+ "w": 18,
+ "h": 18
+ }
+ },
+ {
+ "filename": "altarianite",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 8,
+ "y": 8,
+ "w": 16,
+ "h": 16
+ },
+ "frame": {
+ "x": 315,
+ "y": 320,
+ "w": 16,
+ "h": 16
+ }
+ },
+ {
+ "filename": "ampharosite",
+ "rotated": false,
+ "trimmed": true,
+ "sourceSize": {
+ "w": 32,
+ "h": 32
+ },
+ "spriteSourceSize": {
+ "x": 8,
+ "y": 8,
+ "w": 16,
+ "h": 16
+ },
+ "frame": {
+ "x": 273,
+ "y": 338,
+ "w": 16,
+ "h": 16
+ }
+ },
{
"filename": "audinite",
"rotated": false,
@@ -7311,8 +7332,8 @@
"h": 16
},
"frame": {
- "x": 328,
- "y": 364,
+ "x": 289,
+ "y": 338,
"w": 16,
"h": 16
}
@@ -7332,33 +7353,12 @@
"h": 16
},
"frame": {
- "x": 328,
- "y": 380,
+ "x": 274,
+ "y": 354,
"w": 16,
"h": 16
}
},
- {
- "filename": "prism_scale",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 32,
- "h": 32
- },
- "spriteSourceSize": {
- "x": 9,
- "y": 8,
- "w": 15,
- "h": 15
- },
- "frame": {
- "x": 338,
- "y": 259,
- "w": 15,
- "h": 15
- }
- },
{
"filename": "beedrillite",
"rotated": false,
@@ -7374,8 +7374,8 @@
"h": 16
},
"frame": {
- "x": 353,
- "y": 259,
+ "x": 274,
+ "y": 370,
"w": 16,
"h": 16
}
@@ -7395,8 +7395,8 @@
"h": 16
},
"frame": {
- "x": 369,
- "y": 259,
+ "x": 290,
+ "y": 354,
"w": 16,
"h": 16
}
@@ -7416,8 +7416,8 @@
"h": 16
},
"frame": {
- "x": 385,
- "y": 259,
+ "x": 290,
+ "y": 370,
"w": 16,
"h": 16
}
@@ -7437,8 +7437,8 @@
"h": 16
},
"frame": {
- "x": 401,
- "y": 252,
+ "x": 305,
+ "y": 338,
"w": 16,
"h": 16
}
@@ -7458,8 +7458,8 @@
"h": 16
},
"frame": {
- "x": 401,
- "y": 268,
+ "x": 306,
+ "y": 354,
"w": 16,
"h": 16
}
@@ -7479,8 +7479,8 @@
"h": 16
},
"frame": {
- "x": 340,
- "y": 275,
+ "x": 306,
+ "y": 370,
"w": 16,
"h": 16
}
@@ -7500,8 +7500,8 @@
"h": 16
},
"frame": {
- "x": 356,
- "y": 275,
+ "x": 331,
+ "y": 320,
"w": 16,
"h": 16
}
@@ -7521,8 +7521,8 @@
"h": 16
},
"frame": {
- "x": 372,
- "y": 275,
+ "x": 321,
+ "y": 336,
"w": 16,
"h": 16
}
@@ -7542,8 +7542,8 @@
"h": 16
},
"frame": {
- "x": 345,
- "y": 291,
+ "x": 322,
+ "y": 352,
"w": 16,
"h": 16
}
@@ -7563,8 +7563,8 @@
"h": 16
},
"frame": {
- "x": 345,
- "y": 307,
+ "x": 322,
+ "y": 368,
"w": 16,
"h": 16
}
@@ -7584,8 +7584,8 @@
"h": 16
},
"frame": {
- "x": 361,
- "y": 291,
+ "x": 337,
+ "y": 336,
"w": 16,
"h": 16
}
@@ -7605,8 +7605,8 @@
"h": 16
},
"frame": {
- "x": 345,
- "y": 323,
+ "x": 338,
+ "y": 352,
"w": 16,
"h": 16
}
@@ -7626,8 +7626,8 @@
"h": 16
},
"frame": {
- "x": 361,
- "y": 307,
+ "x": 338,
+ "y": 368,
"w": 16,
"h": 16
}
@@ -7647,8 +7647,8 @@
"h": 16
},
"frame": {
- "x": 345,
- "y": 339,
+ "x": 347,
+ "y": 313,
"w": 16,
"h": 16
}
@@ -7668,8 +7668,8 @@
"h": 16
},
"frame": {
- "x": 361,
- "y": 323,
+ "x": 277,
+ "y": 386,
"w": 16,
"h": 16
}
@@ -7689,8 +7689,8 @@
"h": 16
},
"frame": {
- "x": 361,
- "y": 339,
+ "x": 293,
+ "y": 386,
"w": 16,
"h": 16
}
@@ -7710,8 +7710,8 @@
"h": 16
},
"frame": {
- "x": 377,
- "y": 291,
+ "x": 309,
+ "y": 386,
"w": 16,
"h": 16
}
@@ -7731,8 +7731,8 @@
"h": 16
},
"frame": {
- "x": 377,
- "y": 307,
+ "x": 325,
+ "y": 384,
"w": 16,
"h": 16
}
@@ -7752,8 +7752,8 @@
"h": 16
},
"frame": {
- "x": 377,
- "y": 323,
+ "x": 341,
+ "y": 384,
"w": 16,
"h": 16
}
@@ -7773,8 +7773,8 @@
"h": 16
},
"frame": {
- "x": 377,
- "y": 339,
+ "x": 277,
+ "y": 402,
"w": 16,
"h": 16
}
@@ -7794,8 +7794,8 @@
"h": 16
},
"frame": {
- "x": 345,
- "y": 355,
+ "x": 293,
+ "y": 402,
"w": 16,
"h": 16
}
@@ -7815,8 +7815,8 @@
"h": 16
},
"frame": {
- "x": 361,
- "y": 355,
+ "x": 309,
+ "y": 402,
"w": 16,
"h": 16
}
@@ -7836,8 +7836,8 @@
"h": 16
},
"frame": {
- "x": 377,
- "y": 355,
+ "x": 325,
+ "y": 400,
"w": 16,
"h": 16
}
@@ -7857,8 +7857,8 @@
"h": 16
},
"frame": {
- "x": 344,
- "y": 371,
+ "x": 341,
+ "y": 400,
"w": 16,
"h": 16
}
@@ -7878,8 +7878,8 @@
"h": 16
},
"frame": {
- "x": 360,
- "y": 371,
+ "x": 353,
+ "y": 329,
"w": 16,
"h": 16
}
@@ -7899,8 +7899,8 @@
"h": 16
},
"frame": {
- "x": 376,
- "y": 371,
+ "x": 369,
+ "y": 322,
"w": 16,
"h": 16
}
@@ -7920,8 +7920,8 @@
"h": 16
},
"frame": {
- "x": 344,
- "y": 387,
+ "x": 354,
+ "y": 345,
"w": 16,
"h": 16
}
@@ -7941,8 +7941,8 @@
"h": 16
},
"frame": {
- "x": 360,
- "y": 387,
+ "x": 354,
+ "y": 361,
"w": 16,
"h": 16
}
@@ -7962,8 +7962,8 @@
"h": 16
},
"frame": {
- "x": 376,
- "y": 387,
+ "x": 385,
+ "y": 329,
"w": 16,
"h": 16
}
@@ -7983,8 +7983,8 @@
"h": 16
},
"frame": {
- "x": 328,
- "y": 396,
+ "x": 401,
+ "y": 332,
"w": 16,
"h": 16
}
@@ -8004,8 +8004,8 @@
"h": 16
},
"frame": {
- "x": 344,
- "y": 403,
+ "x": 357,
+ "y": 377,
"w": 16,
"h": 16
}
@@ -8025,8 +8025,8 @@
"h": 16
},
"frame": {
- "x": 360,
- "y": 403,
+ "x": 357,
+ "y": 393,
"w": 16,
"h": 16
}
@@ -8046,8 +8046,8 @@
"h": 16
},
"frame": {
- "x": 376,
- "y": 403,
+ "x": 357,
+ "y": 409,
"w": 16,
"h": 16
}
@@ -8067,8 +8067,8 @@
"h": 16
},
"frame": {
- "x": 393,
- "y": 284,
+ "x": 370,
+ "y": 345,
"w": 16,
"h": 16
}
@@ -8088,8 +8088,8 @@
"h": 16
},
"frame": {
- "x": 393,
- "y": 300,
+ "x": 370,
+ "y": 361,
"w": 16,
"h": 16
}
@@ -8109,8 +8109,8 @@
"h": 16
},
"frame": {
- "x": 393,
- "y": 316,
+ "x": 373,
+ "y": 377,
"w": 16,
"h": 16
}
@@ -8130,8 +8130,8 @@
"h": 16
},
"frame": {
- "x": 393,
- "y": 332,
+ "x": 373,
+ "y": 393,
"w": 16,
"h": 16
}
@@ -8151,8 +8151,8 @@
"h": 16
},
"frame": {
- "x": 393,
- "y": 348,
+ "x": 373,
+ "y": 409,
"w": 16,
"h": 16
}
@@ -8172,8 +8172,8 @@
"h": 16
},
"frame": {
- "x": 409,
- "y": 284,
+ "x": 386,
+ "y": 348,
"w": 16,
"h": 16
}
@@ -8193,8 +8193,8 @@
"h": 16
},
"frame": {
- "x": 409,
- "y": 300,
+ "x": 402,
+ "y": 348,
"w": 16,
"h": 16
}
@@ -8214,8 +8214,8 @@
"h": 16
},
"frame": {
- "x": 409,
- "y": 316,
+ "x": 389,
+ "y": 364,
"w": 16,
"h": 16
}
@@ -8235,8 +8235,8 @@
"h": 16
},
"frame": {
- "x": 409,
- "y": 332,
+ "x": 389,
+ "y": 380,
"w": 16,
"h": 16
}
@@ -8247,6 +8247,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
- "smartupdate": "$TexturePacker:SmartUpdate:6dd58685cd89890a4361a424b59bfe65:ff9159978f3a103ee1d656fb76e37457:110e074689c9edd2c54833ce2e4d9270$"
+ "smartupdate": "$TexturePacker:SmartUpdate:c004184e48566e1da6f13477a3348fd3:dc1a5489f7821641aade35ba290bbea7:110e074689c9edd2c54833ce2e4d9270$"
}
}
diff --git a/public/images/items.png b/public/images/items.png
index 23c87cf3204..4c366e4d72a 100644
Binary files a/public/images/items.png and b/public/images/items.png differ
diff --git a/public/images/items/blank_memory.png b/public/images/items/normal_memory.png
similarity index 100%
rename from public/images/items/blank_memory.png
rename to public/images/items/normal_memory.png
diff --git a/public/images/pokemon/279.png b/public/images/pokemon/279.png
index a50bf952433..e7243586515 100644
Binary files a/public/images/pokemon/279.png and b/public/images/pokemon/279.png differ
diff --git a/public/images/pokemon/exp/shiny/1002b.png b/public/images/pokemon/exp/shiny/1002b.png
deleted file mode 100644
index 85dfb1c4bd6..00000000000
Binary files a/public/images/pokemon/exp/shiny/1002b.png and /dev/null differ
diff --git a/public/images/pokemon/exp/shiny/1002s.png b/public/images/pokemon/exp/shiny/1002s.png
deleted file mode 100644
index 835b3dcd73b..00000000000
Binary files a/public/images/pokemon/exp/shiny/1002s.png and /dev/null differ
diff --git a/public/images/pokemon/exp/shiny/1002sb.png b/public/images/pokemon/exp/shiny/1002sb.png
deleted file mode 100644
index f87e2fc4239..00000000000
Binary files a/public/images/pokemon/exp/shiny/1002sb.png and /dev/null differ
diff --git a/public/images/pokemon/shiny/279.png b/public/images/pokemon/shiny/279.png
index c43634e8602..26dba547913 100644
Binary files a/public/images/pokemon/shiny/279.png and b/public/images/pokemon/shiny/279.png differ
diff --git a/public/images/pokemon/variant/279.json b/public/images/pokemon/variant/279.json
index a7b97ac3161..5c193f7939b 100644
--- a/public/images/pokemon/variant/279.json
+++ b/public/images/pokemon/variant/279.json
@@ -1,38 +1,49 @@
{
"0": {
+ "bc4524": "af5457",
"31638c": "324a26",
- "101010": "101010",
"5aa5ce": "40683c",
- "a5e6ff": "b6d9ac",
- "7bceef": "789c6e",
- "ced6ef": "c09e99",
- "737384": "774644",
+ "ce4252": "af2c4f",
"ffffff": "f1dcd8",
"8c4231": "420b0c",
- "ffde4a": "c66f68",
- "c57b31": "551917",
"ffffad": "f4bfb6",
+ "ffde4a": "c66f68",
+ "7bceef": "789c6e",
+ "a5e6ff": "b6d9ac",
+ "737384": "774644",
"f7b531": "af5457",
- "ce4252": "af2c4f",
- "bc4524": "af5457",
- "00e5e7": "00e5e7"
+ "c57b31": "551917",
+ "ced6ef": "c09e99"
},
"1": {
+ "bc4524": "3d325e",
"31638c": "143a72",
- "101010": "101010",
"5aa5ce": "4060bc",
- "a5e6ff": "b4b3ff",
- "7bceef": "657ddf",
- "ced6ef": "a8b5dd",
- "737384": "737384",
+ "ce4252": "b75558",
"ffffff": "e5ecff",
"8c4231": "17103f",
- "ffde4a": "534e72",
- "c57b31": "2a1f50",
"ffffad": "87879b",
+ "ffde4a": "534e72",
+ "7bceef": "657ddf",
+ "a5e6ff": "b4b3ff",
"f7b531": "3d325e",
- "ce4252": "b75558",
- "bc4524": "3d325e",
- "00e5e7": "00e5e7"
+ "c57b31": "2a1f50",
+ "ced6ef": "a8b5dd"
+ },
+ "2": {
+ "ce4252": "215991",
+ "ffde4a": "f16f40",
+ "ffffad": "ffb274",
+ "737384": "884c43",
+ "c57b31": "761c03",
+ "7bceef": "be3d2f",
+ "8c4231": "5a0700",
+ "5aa5ce": "892722",
+ "8c4232": "761c03",
+ "ffffff": "f5e1d1",
+ "a5e6ff": "dd533a",
+ "f7b531": "bc4524",
+ "ced6ef": "d19e92",
+ "31638c": "610f0e"
}
-}
\ No newline at end of file
+}
diff --git a/public/images/pokemon/variant/279_1.json b/public/images/pokemon/variant/279_1.json
deleted file mode 100644
index 3f884262e14..00000000000
--- a/public/images/pokemon/variant/279_1.json
+++ /dev/null
@@ -1,3611 +0,0 @@
-{
- "textures": [
- {
- "image": "279_1.png",
- "format": "RGBA8888",
- "size": {
- "w": 422,
- "h": 422
- },
- "scale": 1,
- "frames": [
- {
- "filename": "0003.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0046.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0131.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0040.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0081.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0124.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0168.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0033.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 96,
- "h": 52
- },
- "frame": {
- "x": 192,
- "y": 0,
- "w": 96,
- "h": 52
- }
- },
- {
- "filename": "0161.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 96,
- "h": 52
- },
- "frame": {
- "x": 192,
- "y": 0,
- "w": 96,
- "h": 52
- }
- },
- {
- "filename": "0045.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 0,
- "w": 94,
- "h": 53
- },
- "frame": {
- "x": 288,
- "y": 0,
- "w": 94,
- "h": 53
- }
- },
- {
- "filename": "0088.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 0,
- "w": 94,
- "h": 53
- },
- "frame": {
- "x": 288,
- "y": 0,
- "w": 94,
- "h": 53
- }
- },
- {
- "filename": "0004.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0005.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0006.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0047.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0048.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0089.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0090.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0091.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0132.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0133.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0134.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0007.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0008.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0049.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0050.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0051.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0092.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0093.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0094.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0135.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0136.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0009.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0010.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0011.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0052.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0053.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0054.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0095.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0096.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0137.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0138.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0139.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0012.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0013.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0014.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0055.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0056.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0097.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0098.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0099.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0140.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0141.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0142.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0015.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0016.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0057.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0058.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0059.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0100.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0101.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0102.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0143.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0144.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0038.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0123.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0166.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0039.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0082.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0125.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0167.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0083.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0119.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 9,
- "w": 82,
- "h": 60
- },
- "frame": {
- "x": 186,
- "y": 158,
- "w": 82,
- "h": 60
- }
- },
- {
- "filename": "0076.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 94,
- "h": 52
- },
- "frame": {
- "x": 268,
- "y": 159,
- "w": 94,
- "h": 52
- }
- },
- {
- "filename": "0085.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 57,
- "h": 68
- },
- "frame": {
- "x": 362,
- "y": 159,
- "w": 57,
- "h": 68
- }
- },
- {
- "filename": "0128.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 57,
- "h": 68
- },
- "frame": {
- "x": 362,
- "y": 159,
- "w": 57,
- "h": 68
- }
- },
- {
- "filename": "0037.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0080.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0165.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0034.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 349,
- "y": 227,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0162.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 349,
- "y": 227,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0126.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 5,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 0,
- "y": 159,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0002.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0087.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0130.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0017.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0018.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0019.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0060.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0061.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0062.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0103.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0104.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0145.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0146.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0147.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0041.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0084.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0169.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0020.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0021.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0022.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0063.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0064.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0105.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0106.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0107.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0148.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0149.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0150.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0023.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0024.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0065.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0066.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0067.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0108.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0109.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0110.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0151.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0152.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0079.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 67,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 282,
- "w": 67,
- "h": 63
- }
- },
- {
- "filename": "0122.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 67,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 282,
- "w": 67,
- "h": 63
- }
- },
- {
- "filename": "0025.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0026.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0027.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0068.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0069.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0070.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0111.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0112.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0153.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0154.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0155.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0028.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0029.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0030.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0071.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0072.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0113.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0114.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0115.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0156.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0157.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0158.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0042.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0127.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0170.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0031.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0032.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0073.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0074.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0075.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0116.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0117.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0118.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0159.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0160.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0043.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0086.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0171.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0077.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 160,
- "y": 322,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0120.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 160,
- "y": 322,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0001.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0044.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0129.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0035.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0078.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0163.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0036.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- },
- {
- "filename": "0121.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- },
- {
- "filename": "0164.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- }
- ]
- }
- ],
- "meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "3.0",
- "smartupdate": "$TexturePacker:SmartUpdate:37124082e206aa3cddb045440ab1e9cf:8d64288649efa3066ed9b372190e868f:b1d275d5bba320dd22d2f2c7e56d52ec$"
- }
-}
\ No newline at end of file
diff --git a/public/images/pokemon/variant/279_1.png b/public/images/pokemon/variant/279_1.png
deleted file mode 100644
index f2aa7b8ac3a..00000000000
Binary files a/public/images/pokemon/variant/279_1.png and /dev/null differ
diff --git a/public/images/pokemon/variant/279_2.json b/public/images/pokemon/variant/279_2.json
deleted file mode 100644
index 441f7841115..00000000000
--- a/public/images/pokemon/variant/279_2.json
+++ /dev/null
@@ -1,3611 +0,0 @@
-{
- "textures": [
- {
- "image": "279_2.png",
- "format": "RGBA8888",
- "size": {
- "w": 422,
- "h": 422
- },
- "scale": 1,
- "frames": [
- {
- "filename": "0003.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0046.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0131.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0040.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0081.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0124.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0168.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0033.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 96,
- "h": 52
- },
- "frame": {
- "x": 192,
- "y": 0,
- "w": 96,
- "h": 52
- }
- },
- {
- "filename": "0161.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 96,
- "h": 52
- },
- "frame": {
- "x": 192,
- "y": 0,
- "w": 96,
- "h": 52
- }
- },
- {
- "filename": "0045.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 0,
- "w": 94,
- "h": 53
- },
- "frame": {
- "x": 288,
- "y": 0,
- "w": 94,
- "h": 53
- }
- },
- {
- "filename": "0088.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 0,
- "w": 94,
- "h": 53
- },
- "frame": {
- "x": 288,
- "y": 0,
- "w": 94,
- "h": 53
- }
- },
- {
- "filename": "0004.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0005.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0006.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0047.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0048.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0089.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0090.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0091.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0132.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0133.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0134.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0007.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0008.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0049.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0050.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0051.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0092.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0093.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0094.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0135.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0136.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0009.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0010.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0011.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0052.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0053.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0054.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0095.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0096.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0137.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0138.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0139.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0012.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0013.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0014.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0055.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0056.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0097.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0098.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0099.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0140.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0141.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0142.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0015.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0016.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0057.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0058.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0059.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0100.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0101.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0102.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0143.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0144.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0038.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0123.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0166.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0039.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0082.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0125.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0167.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0083.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0119.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 9,
- "w": 82,
- "h": 60
- },
- "frame": {
- "x": 186,
- "y": 158,
- "w": 82,
- "h": 60
- }
- },
- {
- "filename": "0076.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 94,
- "h": 52
- },
- "frame": {
- "x": 268,
- "y": 159,
- "w": 94,
- "h": 52
- }
- },
- {
- "filename": "0085.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 57,
- "h": 68
- },
- "frame": {
- "x": 362,
- "y": 159,
- "w": 57,
- "h": 68
- }
- },
- {
- "filename": "0128.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 57,
- "h": 68
- },
- "frame": {
- "x": 362,
- "y": 159,
- "w": 57,
- "h": 68
- }
- },
- {
- "filename": "0037.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0080.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0165.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0034.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 349,
- "y": 227,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0162.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 349,
- "y": 227,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0126.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 5,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 0,
- "y": 159,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0002.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0087.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0130.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0017.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0018.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0019.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0060.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0061.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0062.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0103.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0104.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0145.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0146.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0147.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0041.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0084.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0169.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0020.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0021.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0022.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0063.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0064.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0105.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0106.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0107.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0148.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0149.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0150.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0023.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0024.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0065.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0066.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0067.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0108.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0109.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0110.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0151.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0152.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0079.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 67,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 282,
- "w": 67,
- "h": 63
- }
- },
- {
- "filename": "0122.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 67,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 282,
- "w": 67,
- "h": 63
- }
- },
- {
- "filename": "0025.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0026.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0027.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0068.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0069.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0070.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0111.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0112.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0153.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0154.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0155.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0028.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0029.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0030.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0071.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0072.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0113.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0114.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0115.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0156.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0157.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0158.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0042.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0127.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0170.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0031.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0032.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0073.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0074.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0075.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0116.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0117.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0118.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0159.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0160.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0043.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0086.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0171.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0077.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 160,
- "y": 322,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0120.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 160,
- "y": 322,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0001.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0044.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0129.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0035.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0078.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0163.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0036.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- },
- {
- "filename": "0121.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- },
- {
- "filename": "0164.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- }
- ]
- }
- ],
- "meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "3.0",
- "smartupdate": "$TexturePacker:SmartUpdate:37124082e206aa3cddb045440ab1e9cf:8d64288649efa3066ed9b372190e868f:b1d275d5bba320dd22d2f2c7e56d52ec$"
- }
-}
\ No newline at end of file
diff --git a/public/images/pokemon/variant/279_2.png b/public/images/pokemon/variant/279_2.png
deleted file mode 100644
index cde1727d123..00000000000
Binary files a/public/images/pokemon/variant/279_2.png and /dev/null differ
diff --git a/public/images/pokemon/variant/279_3.json b/public/images/pokemon/variant/279_3.json
deleted file mode 100644
index e2415bee4cd..00000000000
--- a/public/images/pokemon/variant/279_3.json
+++ /dev/null
@@ -1,3611 +0,0 @@
-{
- "textures": [
- {
- "image": "279_3.png",
- "format": "RGBA8888",
- "size": {
- "w": 422,
- "h": 422
- },
- "scale": 1,
- "frames": [
- {
- "filename": "0003.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0046.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0131.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 1,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0040.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0081.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0124.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0168.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 96,
- "h": 53
- },
- "frame": {
- "x": 96,
- "y": 0,
- "w": 96,
- "h": 53
- }
- },
- {
- "filename": "0033.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 96,
- "h": 52
- },
- "frame": {
- "x": 192,
- "y": 0,
- "w": 96,
- "h": 52
- }
- },
- {
- "filename": "0161.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 96,
- "h": 52
- },
- "frame": {
- "x": 192,
- "y": 0,
- "w": 96,
- "h": 52
- }
- },
- {
- "filename": "0045.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 0,
- "w": 94,
- "h": 53
- },
- "frame": {
- "x": 288,
- "y": 0,
- "w": 94,
- "h": 53
- }
- },
- {
- "filename": "0088.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 0,
- "w": 94,
- "h": 53
- },
- "frame": {
- "x": 288,
- "y": 0,
- "w": 94,
- "h": 53
- }
- },
- {
- "filename": "0004.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0005.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0006.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0047.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0048.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0089.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0090.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0091.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0132.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0133.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0134.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 1,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 192,
- "y": 52,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0007.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0008.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0049.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0050.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0051.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0092.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0093.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0094.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0135.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0136.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 2,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 285,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0009.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0010.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0011.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0052.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0053.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0054.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0095.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0096.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0137.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0138.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0139.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 3,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0012.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0013.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0014.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0055.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0056.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0097.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0098.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0099.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0140.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0141.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0142.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 4,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 53,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0015.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0016.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0057.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0058.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0059.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0100.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0101.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0102.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0143.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0144.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 186,
- "y": 105,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0038.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0123.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0166.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 7,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 279,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0039.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0082.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0125.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0167.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 0,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0083.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 5,
- "w": 93,
- "h": 53
- },
- "frame": {
- "x": 93,
- "y": 106,
- "w": 93,
- "h": 53
- }
- },
- {
- "filename": "0119.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 9,
- "w": 82,
- "h": 60
- },
- "frame": {
- "x": 186,
- "y": 158,
- "w": 82,
- "h": 60
- }
- },
- {
- "filename": "0076.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 0,
- "y": 9,
- "w": 94,
- "h": 52
- },
- "frame": {
- "x": 268,
- "y": 159,
- "w": 94,
- "h": 52
- }
- },
- {
- "filename": "0085.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 57,
- "h": 68
- },
- "frame": {
- "x": 362,
- "y": 159,
- "w": 57,
- "h": 68
- }
- },
- {
- "filename": "0128.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 57,
- "h": 68
- },
- "frame": {
- "x": 362,
- "y": 159,
- "w": 57,
- "h": 68
- }
- },
- {
- "filename": "0037.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0080.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0165.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 7,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 268,
- "y": 211,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0034.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 349,
- "y": 227,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0162.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 349,
- "y": 227,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0126.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 5,
- "w": 81,
- "h": 60
- },
- "frame": {
- "x": 0,
- "y": 159,
- "w": 81,
- "h": 60
- }
- },
- {
- "filename": "0002.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0087.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0130.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 6,
- "y": 0,
- "w": 82,
- "h": 59
- },
- "frame": {
- "x": 81,
- "y": 159,
- "w": 82,
- "h": 59
- }
- },
- {
- "filename": "0017.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0018.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0019.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0060.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0061.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0062.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0103.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0104.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0145.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0146.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0147.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 6,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 81,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0041.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0084.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0169.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 68,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 219,
- "w": 68,
- "h": 63
- }
- },
- {
- "filename": "0020.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0021.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0022.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0063.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0064.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0105.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0106.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0107.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0148.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0149.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0150.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 7,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 174,
- "y": 218,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0023.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0024.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0065.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0066.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0067.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0108.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0109.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0110.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0151.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0152.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 8,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 68,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0079.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 67,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 282,
- "w": 67,
- "h": 63
- }
- },
- {
- "filename": "0122.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 67,
- "h": 63
- },
- "frame": {
- "x": 0,
- "y": 282,
- "w": 67,
- "h": 63
- }
- },
- {
- "filename": "0025.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0026.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0027.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0068.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0069.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0070.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0111.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0112.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0153.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0154.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0155.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 9,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 161,
- "y": 270,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0028.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0029.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0030.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0071.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0072.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0113.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0114.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0115.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0156.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0157.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0158.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 10,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 254,
- "y": 271,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0042.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0127.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0170.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 3,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 290,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0031.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0032.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0073.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0074.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0075.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0116.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0117.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0118.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0159.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0160.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 2,
- "y": 11,
- "w": 93,
- "h": 52
- },
- "frame": {
- "x": 67,
- "y": 322,
- "w": 93,
- "h": 52
- }
- },
- {
- "filename": "0043.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0086.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0171.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 1,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 0,
- "y": 345,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0077.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 160,
- "y": 322,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0120.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 63,
- "h": 66
- },
- "frame": {
- "x": 160,
- "y": 322,
- "w": 63,
- "h": 66
- }
- },
- {
- "filename": "0001.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0044.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0129.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 0,
- "w": 67,
- "h": 62
- },
- "frame": {
- "x": 223,
- "y": 323,
- "w": 67,
- "h": 62
- }
- },
- {
- "filename": "0035.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0078.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0163.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 9,
- "w": 57,
- "h": 67
- },
- "frame": {
- "x": 290,
- "y": 323,
- "w": 57,
- "h": 67
- }
- },
- {
- "filename": "0036.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- },
- {
- "filename": "0121.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- },
- {
- "filename": "0164.png",
- "rotated": false,
- "trimmed": true,
- "sourceSize": {
- "w": 96,
- "h": 76
- },
- "spriteSourceSize": {
- "x": 14,
- "y": 7,
- "w": 62,
- "h": 66
- },
- "frame": {
- "x": 347,
- "y": 356,
- "w": 62,
- "h": 66
- }
- }
- ]
- }
- ],
- "meta": {
- "app": "https://www.codeandweb.com/texturepacker",
- "version": "3.0",
- "smartupdate": "$TexturePacker:SmartUpdate:37124082e206aa3cddb045440ab1e9cf:8d64288649efa3066ed9b372190e868f:b1d275d5bba320dd22d2f2c7e56d52ec$"
- }
-}
\ No newline at end of file
diff --git a/public/images/pokemon/variant/279_3.png b/public/images/pokemon/variant/279_3.png
deleted file mode 100644
index a3b93047551..00000000000
Binary files a/public/images/pokemon/variant/279_3.png and /dev/null differ
diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json
index 87f80fd17c4..9efbf5bc150 100644
--- a/public/images/pokemon/variant/_masterlist.json
+++ b/public/images/pokemon/variant/_masterlist.json
@@ -1017,7 +1017,7 @@
"279": [
1,
1,
- 2
+ 1
],
"280": [
0,
diff --git a/public/images/ui/egg_summary_bg.png b/public/images/ui/egg_summary_bg.png
new file mode 100644
index 00000000000..658f5df0e96
Binary files /dev/null and b/public/images/ui/egg_summary_bg.png differ
diff --git a/public/images/ui/egg_summary_bg_blank.png b/public/images/ui/egg_summary_bg_blank.png
new file mode 100644
index 00000000000..09bcb63cfa3
Binary files /dev/null and b/public/images/ui/egg_summary_bg_blank.png differ
diff --git a/public/images/ui/icon_egg_move.png b/public/images/ui/icon_egg_move.png
new file mode 100644
index 00000000000..a5b0bff4ace
Binary files /dev/null and b/public/images/ui/icon_egg_move.png differ
diff --git a/public/images/ui/icon_lock.png b/public/images/ui/icon_lock.png
new file mode 100644
index 00000000000..6a12efa15e8
Binary files /dev/null and b/public/images/ui/icon_lock.png differ
diff --git a/public/images/ui/icon_stop.png b/public/images/ui/icon_stop.png
new file mode 100644
index 00000000000..6d9c201695a
Binary files /dev/null and b/public/images/ui/icon_stop.png differ
diff --git a/public/images/ui/legacy/egg_summary_bg.png b/public/images/ui/legacy/egg_summary_bg.png
new file mode 100644
index 00000000000..658f5df0e96
Binary files /dev/null and b/public/images/ui/legacy/egg_summary_bg.png differ
diff --git a/public/images/ui/legacy/icon_egg_move.png b/public/images/ui/legacy/icon_egg_move.png
new file mode 100644
index 00000000000..a5b0bff4ace
Binary files /dev/null and b/public/images/ui/legacy/icon_egg_move.png differ
diff --git a/public/images/ui/legacy/icon_lock.png b/public/images/ui/legacy/icon_lock.png
new file mode 100644
index 00000000000..6a12efa15e8
Binary files /dev/null and b/public/images/ui/legacy/icon_lock.png differ
diff --git a/public/images/ui/legacy/icon_stop.png b/public/images/ui/legacy/icon_stop.png
new file mode 100644
index 00000000000..6d9c201695a
Binary files /dev/null and b/public/images/ui/legacy/icon_stop.png differ
diff --git a/src/battle-scene.ts b/src/battle-scene.ts
index 2a920864850..d4c33663c14 100644
--- a/src/battle-scene.ts
+++ b/src/battle-scene.ts
@@ -130,7 +130,7 @@ export default class BattleScene extends SceneBase {
public gameSpeed: integer = 1;
public damageNumbersMode: integer = 0;
public reroll: boolean = false;
- public shopCursorTarget: number = ShopCursorTarget.CHECK_TEAM;
+ public shopCursorTarget: number = ShopCursorTarget.REWARDS;
public showMovesetFlyout: boolean = true;
public showArenaFlyout: boolean = true;
public showTimeOfDayWidget: boolean = true;
@@ -841,12 +841,13 @@ export default class BattleScene extends SceneBase {
}
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
+ if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
+ level = Overrides.OPP_LEVEL_OVERRIDE;
+ }
if (Overrides.OPP_SPECIES_OVERRIDE) {
species = getPokemonSpecies(Overrides.OPP_SPECIES_OVERRIDE);
- }
-
- if (Overrides.OPP_LEVEL_OVERRIDE !== 0) {
- level = Overrides.OPP_LEVEL_OVERRIDE;
+ // The fact that a Pokemon is a boss or not can change based on its Species and level
+ boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
}
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource);
@@ -973,6 +974,7 @@ export default class BattleScene extends SceneBase {
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;
@@ -1327,6 +1329,13 @@ export default class BattleScene extends SceneBase {
}
getEncounterBossSegments(waveIndex: integer, level: integer, species?: PokemonSpecies, forceBoss: boolean = false): integer {
+ if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1) {
+ return Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE;
+ } else if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE === 1) {
+ // The rest of the code expects to be returned 0 and not 1 if the enemy is not a boss
+ return 0;
+ }
+
if (this.gameMode.isDaily && this.gameMode.isWaveFinal(waveIndex)) {
return 5;
}
@@ -1791,6 +1800,7 @@ export default class BattleScene extends SceneBase {
config = config ?? {};
try {
const keyDetails = key.split("/");
+ config["volume"] = config["volume"] ?? 1;
switch (keyDetails[0]) {
case "level_up_fanfare":
case "item_fanfare":
@@ -1800,11 +1810,11 @@ export default class BattleScene extends SceneBase {
case "evolution_fanfare":
// These sounds are loaded in as BGM, but played as sound effects
// When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM()
- config["volume"] = this.masterVolume * this.bgmVolume;
+ config["volume"] *= (this.masterVolume * this.bgmVolume);
break;
case "battle_anims":
case "cry":
- config["volume"] = this.masterVolume * this.fieldVolume;
+ config["volume"] *= (this.masterVolume * this.fieldVolume);
//PRSFX sound files are unusually loud
if (keyDetails[1].startsWith("PRSFX- ")) {
config["volume"] *= 0.5;
@@ -1812,10 +1822,10 @@ export default class BattleScene extends SceneBase {
break;
case "ui":
//As of, right now this applies to the "select", "menu_open", "error" sound effects
- config["volume"] = this.masterVolume * this.uiVolume;
+ config["volume"] *= (this.masterVolume * this.uiVolume);
break;
case "se":
- config["volume"] = this.masterVolume * this.seVolume;
+ config["volume"] *= (this.masterVolume * this.seVolume);
break;
}
this.sound.play(key, config);
@@ -2732,6 +2742,35 @@ export default class BattleScene extends SceneBase {
(window as any).gameInfo = gameInfo;
}
+ /**
+ * This function retrieves the sprite and audio keys for active Pokemon.
+ * Active Pokemon include both enemy and player Pokemon of the current wave.
+ * Note: Questions on garbage collection go to @frutescens
+ * @returns a string array of active sprite and audio keys that should not be deleted
+ */
+ getActiveKeys(): string[] {
+ const keys: string[] = [];
+ const playerParty = this.getParty();
+ playerParty.forEach(p => {
+ keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant));
+ keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant, true));
+ keys.push("cry/" + p.species.getCryKey(p.species.formIndex));
+ if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) {
+ keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex));
+ }
+ });
+ // enemyParty has to be operated on separately from playerParty because playerPokemon =/= enemyPokemon
+ const enemyParty = this.getEnemyParty();
+ enemyParty.forEach(p => {
+ keys.push(p.species.getSpriteKey(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant));
+ keys.push("cry/" + p.species.getCryKey(p.species.formIndex));
+ if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) {
+ keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex));
+ }
+ });
+ return keys;
+ }
+
/**
* Initialized the 2nd phase of the final boss (e.g. form-change for Eternatus)
* @param pokemon The (enemy) pokemon
diff --git a/src/data/ability.ts b/src/data/ability.ts
index 8b7a7772efe..925a7efb79b 100644
--- a/src/data/ability.ts
+++ b/src/data/ability.ts
@@ -2,20 +2,18 @@ import Pokemon, { HitResult, PlayerPokemon, PokemonMove } from "../field/pokemon
import { Type } from "./type";
import { Constructor } from "#app/utils";
import * as Utils from "../utils";
-import { BattleStat, getBattleStatName } from "./battle-stat";
import { getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather";
import { BattlerTag, GroundedTag, GulpMissileTag, SemiInvulnerableTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
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 } 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";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
-import { Stat, getStatName } from "./pokemon-stat";
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { TerrainType } from "./terrain";
import { SpeciesFormChangeManualTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "./pokemon-forms";
import i18next from "i18next";
-import { Localizable } from "#app/interfaces/locales.js";
+import { Localizable } from "#app/interfaces/locales";
import { Command } from "../ui/command-ui-handler";
import { BerryModifierType } from "#app/modifier/modifier-type";
import { getPokeballName } from "./pokeball";
@@ -25,10 +23,11 @@ import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
+import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
import { MovePhase } from "#app/phases/move-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
-import { StatChangePhase } from "#app/phases/stat-change-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import BattleScene from "#app/battle-scene";
export class Ability implements Localizable {
@@ -126,7 +125,7 @@ type AbAttrCondition = (pokemon: Pokemon) => boolean;
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
-type PokemonStatChangeCondition = (target: Pokemon, statsChanged: BattleStat[], levels: integer) => boolean;
+type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
export abstract class AbAttr {
public showAbility: boolean;
@@ -203,38 +202,36 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr {
}
}
-export class PostBattleInitStatChangeAbAttr extends PostBattleInitAbAttr {
+export class PostBattleInitStatStageChangeAbAttr extends PostBattleInitAbAttr {
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
private selfTarget: boolean;
- constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean) {
+ constructor(stats: BattleStat[], stages: number, selfTarget?: boolean) {
super();
- this.stats = typeof(stats) === "number"
- ? [ stats as BattleStat ]
- : stats as BattleStat[];
- this.levels = levels;
+ this.stats = stats;
+ this.stages = stages;
this.selfTarget = !!selfTarget;
}
applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
- const statChangePhases: StatChangePhase[] = [];
+ const statStageChangePhases: StatStageChangePhase[] = [];
if (!simulated) {
if (this.selfTarget) {
- statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
+ statStageChangePhases.push(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
} else {
for (const opponent of pokemon.getOpponents()) {
- statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels));
+ statStageChangePhases.push(new StatStageChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.stages));
}
}
- for (const statChangePhase of statChangePhases) {
- if (!this.selfTarget && !statChangePhase.getPokemon()?.summonData) {
- pokemon.scene.pushPhase(statChangePhase);
+ for (const statStageChangePhase of statStageChangePhases) {
+ if (!this.selfTarget && !statStageChangePhase.getPokemon()?.summonData) {
+ pokemon.scene.pushPhase(statStageChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time
- pokemon.scene.unshiftPhase(statChangePhase);
+ pokemon.scene.unshiftPhase(statStageChangePhase);
}
}
}
@@ -313,7 +310,7 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(moveType: Type, damageMultiplier: number) {
- super((user, target, move) => move.type === moveType, damageMultiplier);
+ super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier);
}
}
@@ -402,15 +399,15 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
}
}
-class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
+class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr {
private stat: BattleStat;
- private levels: integer;
+ private stages: number;
- constructor(immuneType: Type, stat: BattleStat, levels: integer, condition?: AbAttrCondition) {
+ constructor(immuneType: Type, stat: BattleStat, stages: number, condition?: AbAttrCondition) {
super(immuneType, condition);
this.stat = stat;
- this.levels = levels;
+ this.stages = stages;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
@@ -419,7 +416,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
if (ret) {
cancelled.value = true; // Suppresses "No Effect" message
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
}
}
@@ -458,7 +455,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
- if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(pokemon.getMoveType(move), attacker) < 2) {
+ if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker) < 2) {
cancelled.value = true; // Suppresses "No Effect" message
(args[0] as Utils.NumberHolder).value = 0;
return true;
@@ -475,6 +472,47 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
}
}
+/**
+ * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(Ability) | Tera Shell}
+ * When the source is at full HP, incoming attacks will have a maximum 0.5x type effectiveness multiplier.
+ * @extends PreDefendAbAttr
+ */
+export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
+ /**
+ * Reduces a type multiplier to 0.5 if the source is at full HP.
+ * @param pokemon {@linkcode Pokemon} the Pokemon with this ability
+ * @param passive n/a
+ * @param simulated n/a (this doesn't change game state)
+ * @param attacker n/a
+ * @param move {@linkcode Move} the move being used on the source
+ * @param cancelled n/a
+ * @param args `[0]` a container for the move's current type effectiveness multiplier
+ * @returns `true` if the move's effectiveness is reduced; `false` otherwise
+ */
+ applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move | null, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise {
+ const typeMultiplier = args[0];
+ if (!(typeMultiplier && typeMultiplier instanceof Utils.NumberHolder)) {
+ return false;
+ }
+
+ if (move && move.hasAttr(FixedDamageAttr)) {
+ return false;
+ }
+
+ if (pokemon.isFullHp() && typeMultiplier.value > 0.5) {
+ typeMultiplier.value = 0.5;
+ return true;
+ }
+ return false;
+ }
+
+ getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
+ return i18next.t("abilityTriggers:fullHpResistType", {
+ pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)
+ });
+ }
+}
+
export class PostDefendAbAttr extends AbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean | Promise {
return false;
@@ -518,7 +556,7 @@ export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
}
if (battlerTag.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ BattleStat.DEF ], -1));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ Stat.DEF ], -1));
} else {
attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon);
}
@@ -547,8 +585,8 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
}
}
-export class PostStatChangeAbAttr extends AbAttr {
- applyPostStatChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], levelChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise {
+export class PostStatStageChangeAbAttr extends AbAttr {
+ applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], stagesChanged: integer, selfTarget: boolean, args: any[]): boolean | Promise {
return false;
}
}
@@ -594,20 +632,20 @@ export class WonderSkinAbAttr extends PreDefendAbAttr {
}
}
-export class MoveImmunityStatChangeAbAttr extends MoveImmunityAbAttr {
+export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
private stat: BattleStat;
- private levels: integer;
+ private stages: number;
- constructor(immuneCondition: PreDefendAbAttrCondition, stat: BattleStat, levels: integer) {
+ constructor(immuneCondition: PreDefendAbAttrCondition, stat: BattleStat, stages: number) {
super(immuneCondition);
this.stat = stat;
- this.levels = levels;
+ this.stages = stages;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args);
if (ret && !simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
}
return ret;
@@ -642,19 +680,19 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
}
}
-export class PostDefendStatChangeAbAttr extends PostDefendAbAttr {
+export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition;
private stat: BattleStat;
- private levels: integer;
+ private stages: number;
private selfTarget: boolean;
private allOthers: boolean;
- constructor(condition: PokemonDefendCondition, stat: BattleStat, levels: integer, selfTarget: boolean = true, allOthers: boolean = false) {
+ constructor(condition: PokemonDefendCondition, stat: BattleStat, stages: number, selfTarget: boolean = true, allOthers: boolean = false) {
super(true);
this.condition = condition;
this.stat = stat;
- this.levels = levels;
+ this.stages = stages;
this.selfTarget = selfTarget;
this.allOthers = allOthers;
}
@@ -668,11 +706,11 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr {
if (this.allOthers) {
const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents();
for (const other of otherPokemon) {
- other.scene.unshiftPhase(new StatChangePhase(other.scene, (other).getBattlerIndex(), false, [ this.stat ], this.levels));
+ other.scene.unshiftPhase(new StatStageChangePhase(other.scene, (other).getBattlerIndex(), false, [ this.stat ], this.stages));
}
return true;
}
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages));
return true;
}
@@ -680,20 +718,20 @@ export class PostDefendStatChangeAbAttr extends PostDefendAbAttr {
}
}
-export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr {
+export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition;
private hpGate: number;
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
private selfTarget: boolean;
- constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], levels: integer, selfTarget: boolean = true) {
+ constructor(condition: PokemonDefendCondition, hpGate: number, stats: BattleStat[], stages: number, selfTarget: boolean = true) {
super(true);
this.condition = condition;
this.hpGate = hpGate;
this.stats = stats;
- this.levels = levels;
+ this.stages = stages;
this.selfTarget = selfTarget;
}
@@ -703,8 +741,8 @@ export class PostDefendHpGatedStatChangeAbAttr extends PostDefendAbAttr {
const damageReceived = lastAttackReceived?.damage || 0;
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) {
- if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.levels));
+ if (!simulated ) {
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages));
}
return true;
}
@@ -809,7 +847,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
}
export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
- private chance: integer;
+ public chance: integer;
private effects: StatusEffect[];
constructor(chance: integer, ...effects: StatusEffect[]) {
@@ -872,20 +910,20 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
}
}
-export class PostDefendCritStatChangeAbAttr extends PostDefendAbAttr {
+export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
private stat: BattleStat;
- private levels: integer;
+ private stages: number;
- constructor(stat: BattleStat, levels: integer) {
+ constructor(stat: BattleStat, stages: number) {
super();
this.stat = stat;
- this.levels = levels;
+ this.stages = stages;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
}
return true;
@@ -1072,23 +1110,23 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
}
}
-export class PostStatChangeStatChangeAbAttr extends PostStatChangeAbAttr {
- private condition: PokemonStatChangeCondition;
+export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChangeAbAttr {
+ private condition: PokemonStatStageChangeCondition;
private statsToChange: BattleStat[];
- private levels: integer;
+ private stages: number;
- constructor(condition: PokemonStatChangeCondition, statsToChange: BattleStat[], levels: integer) {
+ constructor(condition: PokemonStatStageChangeCondition, statsToChange: BattleStat[], stages: number) {
super(true);
this.condition = condition;
this.statsToChange = statsToChange;
- this.levels = levels;
+ this.stages = stages;
}
- applyPostStatChange(pokemon: Pokemon, simulated: boolean, statsChanged: BattleStat[], levelsChanged: integer, selfTarget: boolean, args: any[]): boolean {
- if (this.condition(pokemon, statsChanged, levelsChanged) && !selfTarget) {
+ applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): boolean {
+ if (this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget) {
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, (pokemon).getBattlerIndex(), true, this.statsToChange, this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (pokemon).getBattlerIndex(), true, this.statsToChange, this.stages));
}
return true;
}
@@ -1169,13 +1207,13 @@ export class FieldPreventExplosiveMovesAbAttr extends AbAttr {
}
/**
- * Multiplies a BattleStat if the checked Pokemon lacks this ability.
+ * Multiplies a Stat if the checked Pokemon lacks this ability.
* If this ability cannot stack, a BooleanHolder can be used to prevent this from stacking.
- * @see {@link applyFieldBattleStatMultiplierAbAttrs}
- * @see {@link applyFieldBattleStat}
+ * @see {@link applyFieldStatMultiplierAbAttrs}
+ * @see {@link applyFieldStat}
* @see {@link Utils.BooleanHolder}
*/
-export class FieldMultiplyBattleStatAbAttr extends AbAttr {
+export class FieldMultiplyStatAbAttr extends AbAttr {
private stat: Stat;
private multiplier: number;
private canStack: boolean;
@@ -1189,7 +1227,7 @@ export class FieldMultiplyBattleStatAbAttr extends AbAttr {
}
/**
- * applyFieldBattleStat: Tries to multiply a Pokemon's BattleStat
+ * applyFieldStat: Tries to multiply a Pokemon's Stat
* @param pokemon {@linkcode Pokemon} the Pokemon using this ability
* @param passive {@linkcode boolean} unused
* @param stat {@linkcode Stat} the type of the checked stat
@@ -1199,12 +1237,12 @@ export class FieldMultiplyBattleStatAbAttr extends AbAttr {
* @param args {any[]} unused
* @returns true if this changed the checked stat, false otherwise.
*/
- applyFieldBattleStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean {
+ applyFieldStat(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, args: any[]): boolean {
if (!this.canStack && hasApplied.value) {
return false;
}
- if (this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyBattleStatAbAttr).every(attr => (attr as FieldMultiplyBattleStatAbAttr).stat !== stat)) {
+ if (this.stat === stat && checkedPokemon.getAbilityAttrs(FieldMultiplyStatAbAttr).every(attr => (attr as FieldMultiplyStatAbAttr).stat !== stat)) {
statValue.value *= this.multiplier;
hasApplied.value = true;
return true;
@@ -1424,7 +1462,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr {
constructor(boostedType: Type, powerMultiplier?: number) {
- super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5);
+ super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
}
}
@@ -1508,7 +1546,7 @@ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostA
* @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided.
*/
constructor(boostedType: Type, powerMultiplier?: number) {
- super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5);
+ super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
}
}
@@ -1538,22 +1576,22 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr
}
}
-export class BattleStatMultiplierAbAttr extends AbAttr {
- private battleStat: BattleStat;
+export class StatMultiplierAbAttr extends AbAttr {
+ private stat: BattleStat;
private multiplier: number;
private condition: PokemonAttackCondition | null;
- constructor(battleStat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
+ constructor(stat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) {
super(false);
- this.battleStat = battleStat;
+ this.stat = stat;
this.multiplier = multiplier;
this.condition = condition ?? null;
}
- applyBattleStat(pokemon: Pokemon, passive: boolean, simulated: boolean, battleStat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise {
+ applyStatStage(pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, statValue: Utils.NumberHolder, args: any[]): boolean | Promise {
const move = (args[0] as Move);
- if (battleStat === this.battleStat && (!this.condition || this.condition(pokemon, null, move))) {
+ if (stat === this.stat && (!this.condition || this.condition(pokemon, null, move))) {
statValue.value *= this.multiplier;
return true;
}
@@ -1724,15 +1762,15 @@ export class PostVictoryAbAttr extends AbAttr {
}
}
-class PostVictoryStatChangeAbAttr extends PostVictoryAbAttr {
+class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr {
private stat: BattleStat | ((p: Pokemon) => BattleStat);
- private levels: integer;
+ private stages: number;
- constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), levels: integer) {
+ constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), stages: number) {
super();
this.stat = stat;
- this.levels = levels;
+ this.stages = stages;
}
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise {
@@ -1740,7 +1778,7 @@ class PostVictoryStatChangeAbAttr extends PostVictoryAbAttr {
? this.stat(pokemon)
: this.stat;
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.stages));
}
return true;
}
@@ -1774,15 +1812,15 @@ export class PostKnockOutAbAttr extends AbAttr {
}
}
-export class PostKnockOutStatChangeAbAttr extends PostKnockOutAbAttr {
+export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr {
private stat: BattleStat | ((p: Pokemon) => BattleStat);
- private levels: integer;
+ private stages: number;
- constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), levels: integer) {
+ constructor(stat: BattleStat | ((p: Pokemon) => BattleStat), stages: number) {
super();
this.stat = stat;
- this.levels = levels;
+ this.stages = stages;
}
applyPostKnockOut(pokemon: Pokemon, passive: boolean, simulated: boolean, knockedOut: Pokemon, args: any[]): boolean | Promise {
@@ -1790,7 +1828,7 @@ export class PostKnockOutStatChangeAbAttr extends PostKnockOutAbAttr {
? this.stat(pokemon)
: this.stat;
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.stages));
}
return true;
}
@@ -1814,37 +1852,21 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr {
}
}
-export class IgnoreOpponentStatChangesAbAttr extends AbAttr {
- constructor() {
+export class IgnoreOpponentStatStagesAbAttr extends AbAttr {
+ private stats: readonly BattleStat[];
+
+ constructor(stats?: BattleStat[]) {
super(false);
+
+ this.stats = stats ?? BATTLE_STATS;
}
- apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]) {
- (args[0] as Utils.IntegerHolder).value = 0;
-
- return true;
- }
-}
-/**
- * Ignores opponent's evasion stat changes when determining if a move hits or not
- * @extends AbAttr
- * @see {@linkcode apply}
- */
-export class IgnoreOpponentEvasionAbAttr extends AbAttr {
- constructor() {
- super(false);
- }
- /**
- * Checks if enemy Pokemon is trapped by an Arena Trap-esque ability
- * @param pokemon N/A
- * @param passive N/A
- * @param cancelled N/A
- * @param args [0] {@linkcode Utils.IntegerHolder} of BattleStat.EVA
- * @returns if evasion level was successfully considered as 0
- */
- apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]) {
- (args[0] as Utils.IntegerHolder).value = 0;
- return true;
+ apply(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: Utils.BooleanHolder, args: any[]) {
+ if (this.stats.includes(args[0])) {
+ (args[1] as Utils.BooleanHolder).value = true;
+ return true;
+ }
+ return false;
}
}
@@ -1866,21 +1888,21 @@ export class IntimidateImmunityAbAttr extends AbAttr {
}
}
-export class PostIntimidateStatChangeAbAttr extends AbAttr {
+export class PostIntimidateStatStageChangeAbAttr extends AbAttr {
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
private overwrites: boolean;
- constructor(stats: BattleStat[], levels: integer, overwrites?: boolean) {
+ constructor(stats: BattleStat[], stages: number, overwrites?: boolean) {
super(true);
this.stats = stats;
- this.levels = levels;
+ this.stages = stages;
this.overwrites = !!overwrites;
}
- apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
+ apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (!simulated) {
- pokemon.scene.pushPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, this.levels));
+ pokemon.scene.pushPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, this.stages));
}
cancelled.value = this.overwrites;
return true;
@@ -1985,19 +2007,17 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr {
}
}
-export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
+export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr {
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
private selfTarget: boolean;
private intimidate: boolean;
- constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, intimidate?: boolean) {
+ constructor(stats: BattleStat[], stages: number, selfTarget?: boolean, intimidate?: boolean) {
super(false);
- this.stats = typeof(stats) === "number"
- ? [ stats as BattleStat ]
- : stats as BattleStat[];
- this.levels = levels;
+ this.stats = stats;
+ this.stages = stages;
this.selfTarget = !!selfTarget;
this.intimidate = !!intimidate;
}
@@ -2009,20 +2029,19 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
queueShowAbility(pokemon, passive); // TODO: Better solution than manually showing the ability here
if (this.selfTarget) {
- // we unshift the StatChangePhase to put it right after the showAbility and not at the end of the
+ // we unshift the StatStageChangePhase to put it right after the showAbility and not at the end of the
// phase list (which could be after CommandPhase for example)
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
return true;
}
for (const opponent of pokemon.getOpponents()) {
const cancelled = new Utils.BooleanHolder(false);
if (this.intimidate) {
applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated);
- applyAbAttrs(PostIntimidateStatChangeAbAttr, opponent, cancelled, simulated);
+ applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated);
}
if (!cancelled.value) {
- const statChangePhase = new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels);
- pokemon.scene.unshiftPhase(statChangePhase);
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.stages));
}
}
return true;
@@ -2063,7 +2082,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr {
* @param args N/A
* @returns if the move was successful
*/
-export class PostSummonClearAllyStatsAbAttr extends PostSummonAbAttr {
+export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr {
constructor() {
super();
}
@@ -2072,8 +2091,8 @@ export class PostSummonClearAllyStatsAbAttr extends PostSummonAbAttr {
const target = pokemon.getAlly();
if (target?.isActive(true)) {
if (!simulated) {
- for (let s = 0; s < target.summonData.battleStats.length; s++) {
- target.summonData.battleStats[s] = 0;
+ for (const s of BATTLE_STATS) {
+ target.setStatStage(s, 0);
}
target.scene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
@@ -2102,7 +2121,7 @@ export class DownloadAbAttr extends PostSummonAbAttr {
// TODO: Implement the Substitute feature(s) once move is implemented.
/**
* Checks to see if it is the opening turn (starting a new game), if so, Download won't work. This is because Download takes into account
- * vitamins and items, so it needs to use the BattleStat and the stat alone.
+ * vitamins and items, so it needs to use the Stat and the stat alone.
* @param {Pokemon} pokemon Pokemon that is using the move, as well as seeing the opposing pokemon.
* @param {boolean} passive N/A
* @param {any[]} args N/A
@@ -2115,21 +2134,21 @@ export class DownloadAbAttr extends PostSummonAbAttr {
for (const opponent of pokemon.getOpponents()) {
this.enemyCountTally++;
- this.enemyDef += opponent.getBattleStat(Stat.DEF);
- this.enemySpDef += opponent.getBattleStat(Stat.SPDEF);
+ this.enemyDef += opponent.getEffectiveStat(Stat.DEF);
+ this.enemySpDef += opponent.getEffectiveStat(Stat.SPDEF);
}
this.enemyDef = Math.round(this.enemyDef / this.enemyCountTally);
this.enemySpDef = Math.round(this.enemySpDef / this.enemyCountTally);
if (this.enemyDef < this.enemySpDef) {
- this.stats = [BattleStat.ATK];
+ this.stats = [ Stat.ATK ];
} else {
- this.stats = [BattleStat.SPATK];
+ this.stats = [ Stat.SPATK ];
}
if (this.enemyDef > 0 && this.enemySpDef > 0) { // only activate if there's actually an enemy to download from
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, 1));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, 1));
}
return true;
}
@@ -2298,12 +2317,14 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr {
}
const ally = pokemon.getAlly();
- if (!ally || ally.summonData.battleStats.every((change) => change === 0)) {
+ if (!ally || ally.getStatStages().every(s => s === 0)) {
return false;
}
if (!simulated) {
- pokemon.summonData.battleStats = ally.summonData.battleStats;
+ for (const s of BATTLE_STATS) {
+ pokemon.setStatStage(s, ally.getStatStage(s));
+ }
pokemon.updateInfo();
}
@@ -2342,14 +2363,27 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
pokemon.summonData.ability = target.getAbility().id;
pokemon.summonData.gender = target.getGender();
pokemon.summonData.fusionGender = target.getFusionGender();
- pokemon.summonData.stats = [ pokemon.stats[Stat.HP] ].concat(target.stats.slice(1));
- pokemon.summonData.battleStats = target.summonData.battleStats.slice(0);
+
+ // Copy all stats (except HP)
+ for (const s of EFFECTIVE_STATS) {
+ pokemon.setStat(s, target.getStat(s, false), false);
+ }
+
+ // Copy all stat stages
+ for (const s of BATTLE_STATS) {
+ 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.types = target.getTypes();
+
pokemon.scene.playSound("battle_anims/PRSFX- Transform");
- pokemon.loadAssets(false).then(() => pokemon.playAnim());
+ pokemon.loadAssets(false).then(() => {
+ pokemon.playAnim();
+ pokemon.updateInfo();
+ });
pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, }));
@@ -2387,7 +2421,7 @@ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAtt
/**
* Triggers weather-based form change when summoned into an active weather.
- * Used by Forecast.
+ * Used by Forecast and Flower Gift.
* @extends PostSummonAbAttr
*/
export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
@@ -2410,7 +2444,10 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
* @returns whether the form change was triggered
*/
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
- if (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST) {
+ const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
+ const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
+
+ if (isCastformWithForecast || isCherrimWithFlowerGift) {
if (simulated) {
return simulated;
}
@@ -2550,13 +2587,13 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
}
-export class PreStatChangeAbAttr extends AbAttr {
- applyPreStatChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise {
+export class PreStatStageChangeAbAttr extends AbAttr {
+ applyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise {
return false;
}
}
-export class ProtectStatAbAttr extends PreStatChangeAbAttr {
+export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
private protectedStat?: BattleStat;
constructor(protectedStat?: BattleStat) {
@@ -2565,7 +2602,7 @@ export class ProtectStatAbAttr extends PreStatChangeAbAttr {
this.protectedStat = protectedStat;
}
- applyPreStatChange(pokemon: Pokemon, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean {
+ applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
cancelled.value = true;
return true;
@@ -2574,11 +2611,11 @@ export class ProtectStatAbAttr extends PreStatChangeAbAttr {
return false;
}
- getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
+ getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:protectStat", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName,
- statName: this.protectedStat ? getBattleStatName(this.protectedStat) : i18next.t("battle:stats")
+ statName: this.protectedStat ? i18next.t(getStatKey(this.protectedStat)) : i18next.t("battle:stats")
});
}
}
@@ -3083,37 +3120,41 @@ export class PostWeatherChangeAbAttr extends AbAttr {
/**
* Triggers weather-based form change when weather changes.
- * Used by Forecast.
+ * Used by Forecast and Flower Gift.
* @extends PostWeatherChangeAbAttr
*/
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
private ability: Abilities;
+ private formRevertingWeathers: WeatherType[];
- constructor(ability: Abilities) {
+ constructor(ability: Abilities, formRevertingWeathers: WeatherType[]) {
super(false);
this.ability = ability;
+ this.formRevertingWeathers = formRevertingWeathers;
}
/**
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
- * @param {Pokemon} pokemon the Pokemon that changed the weather
+ * @param {Pokemon} pokemon the Pokemon with this ability
* @param passive n/a
* @param weather n/a
* @param args n/a
* @returns whether the form change was triggered
*/
applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean {
- if (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST) {
+ const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
+ const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
+
+ if (isCastformWithForecast || isCherrimWithFlowerGift) {
if (simulated) {
return simulated;
}
- const formRevertingWeathers: WeatherType[] = [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ];
const weatherType = pokemon.scene.arena.weather?.weatherType;
- if (weatherType && formRevertingWeathers.includes(weatherType)) {
+ if (weatherType && this.formRevertingWeathers.includes(weatherType)) {
pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal();
} else {
pokemon.scene.arena.triggerWeatherBasedFormChanges();
@@ -3417,51 +3458,53 @@ export class MoodyAbAttr extends PostTurnAbAttr {
super(true);
}
/**
- * Randomly increases one BattleStat by 2 stages and decreases a different BattleStat by 1 stage
+ * Randomly increases one stat stage by 2 and decreases a different stat stage by 1
* @param {Pokemon} pokemon Pokemon that has this ability
* @param passive N/A
* @param simulated true if applying in a simulated call.
* @param args N/A
* @returns true
*
- * Any BattleStats at +6 or -6 are excluded from being increased or decreased, respectively
- * If the pokemon already has all BattleStats raised to stage 6, it will only decrease one BattleStat by 1 stage
- * If the pokemon already has all BattleStats lowered to stage -6, it will only increase one BattleStat by 2 stages
+ * Any stat stages at +6 or -6 are excluded from being increased or decreased, respectively
+ * If the pokemon already has all stat stages raised to 6, it will only decrease one stat stage by 1
+ * If the pokemon already has all stat stages lowered to -6, it will only increase one stat stage by 2
*/
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
- const selectableStats = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD];
- const increaseStatArray = selectableStats.filter(s => pokemon.summonData.battleStats[s] < 6);
- let decreaseStatArray = selectableStats.filter(s => pokemon.summonData.battleStats[s] > -6);
+ const canRaise = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) < 6);
+ let canLower = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) > -6);
- if (!simulated && increaseStatArray.length > 0) {
- const increaseStat = increaseStatArray[Utils.randInt(increaseStatArray.length)];
- decreaseStatArray = decreaseStatArray.filter(s => s !== increaseStat);
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [increaseStat], 2));
- }
- if (!simulated && decreaseStatArray.length > 0) {
- const decreaseStat = decreaseStatArray[Utils.randInt(decreaseStatArray.length)];
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [decreaseStat], -1));
+ if (!simulated) {
+ if (canRaise.length > 0) {
+ const raisedStat = canRaise[pokemon.randSeedInt(canRaise.length)];
+ canLower = canRaise.filter(s => s !== raisedStat);
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ raisedStat ], 2));
+ }
+ if (canLower.length > 0) {
+ const loweredStat = canLower[pokemon.randSeedInt(canLower.length)];
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ loweredStat ], -1));
+ }
}
+
return true;
}
}
-export class PostTurnStatChangeAbAttr extends PostTurnAbAttr {
+export class PostTurnStatStageChangeAbAttr extends PostTurnAbAttr {
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
- constructor(stats: BattleStat | BattleStat[], levels: integer) {
+ constructor(stats: BattleStat[], stages: number) {
super(true);
this.stats = Array.isArray(stats)
? stats
: [ stats ];
- this.levels = levels;
+ this.stages = stages;
}
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
}
return true;
}
@@ -3647,10 +3690,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) {
const target = this.getTarget(dancer, source, targets);
- dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, target, move, true));
+ dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, target, move, true, true));
} else if (move.getMove() instanceof SelfStatusMove) {
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
- dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true));
+ dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true, true));
}
}
return true;
@@ -3673,7 +3716,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
}
}
-export class StatChangeMultiplierAbAttr extends AbAttr {
+export class StatStageChangeMultiplierAbAttr extends AbAttr {
private multiplier: integer;
constructor(multiplier: integer) {
@@ -3689,10 +3732,10 @@ export class StatChangeMultiplierAbAttr extends AbAttr {
}
}
-export class StatChangeCopyAbAttr extends AbAttr {
+export class StatStageChangeCopyAbAttr extends AbAttr {
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise {
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as integer), true, false, false));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false));
}
return true;
}
@@ -4092,22 +4135,22 @@ export class FlinchEffectAbAttr extends AbAttr {
}
}
-export class FlinchStatChangeAbAttr extends FlinchEffectAbAttr {
+export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr {
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
- constructor(stats: BattleStat | BattleStat[], levels: integer) {
+ constructor(stats: BattleStat[], stages: number) {
super();
this.stats = Array.isArray(stats)
? stats
: [ stats ];
- this.levels = levels;
+ this.stages = stages;
}
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (!simulated) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
}
return true;
}
@@ -4307,9 +4350,9 @@ export class MoneyAbAttr extends PostBattleAbAttr {
* Applies a stat change after a Pokémon is summoned,
* conditioned on the presence of a specific arena tag.
*
- * @extends {PostSummonStatChangeAbAttr}
+ * @extends {PostSummonStatStageChangeAbAttr}
*/
-export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAttr {
+export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr {
/**
* The type of arena tag that conditions the stat change.
* @private
@@ -4318,13 +4361,13 @@ export class PostSummonStatChangeOnArenaAbAttr extends PostSummonStatChangeAbAtt
private tagType: ArenaTagType;
/**
- * Creates an instance of PostSummonStatChangeOnArenaAbAttr.
+ * Creates an instance of PostSummonStatStageChangeOnArenaAbAttr.
* Initializes the stat change to increase Attack by 1 stage if the specified arena tag is present.
*
* @param {ArenaTagType} tagType - The type of arena tag to check for.
*/
constructor(tagType: ArenaTagType) {
- super([BattleStat.ATK], 1, true, false);
+ super([ Stat.ATK ], 1, true, false);
this.tagType = tagType;
}
@@ -4571,14 +4614,14 @@ export function applyPostMoveUsedAbAttrs(attrType: Constructor(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, simulated, args), args, false, simulated);
}
-export function applyBattleStatMultiplierAbAttrs(attrType: Constructor,
- pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise {
- return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, simulated, battleStat, statValue, args), args, false, simulated);
+export function applyStatMultiplierAbAttrs(attrType: Constructor,
+ pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise {
+ return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args);
}
/**
- * Applies a field Battle Stat multiplier attribute
- * @param attrType {@linkcode FieldMultiplyBattleStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
+ * Applies a field Stat multiplier attribute
+ * @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
* @param pokemon {@linkcode Pokemon} the Pokemon applying this ability
* @param stat {@linkcode Stat} the type of the checked stat
* @param statValue {@linkcode Utils.NumberHolder} the value of the checked stat
@@ -4586,9 +4629,9 @@ export function applyBattleStatMultiplierAbAttrs(attrType: Constructor,
+export function applyFieldStatMultiplierAbAttrs(attrType: Constructor,
pokemon: Pokemon, stat: Stat, statValue: Utils.NumberHolder, checkedPokemon: Pokemon, hasApplied: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise {
- return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyFieldBattleStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), args, false, simulated);
+ return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyFieldStat(pokemon, passive, simulated, stat, statValue, checkedPokemon, hasApplied, args), args);
}
export function applyPreAttackAbAttrs(attrType: Constructor,
@@ -4621,14 +4664,14 @@ export function applyPreSwitchOutAbAttrs(attrType: Constructor(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), args, true, simulated);
}
-export function applyPreStatChangeAbAttrs(attrType: Constructor,
+export function applyPreStatStageChangeAbAttrs(attrType: Constructor,
pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise {
- return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatChange(pokemon, passive, simulated, stat, cancelled, args), args, false, simulated);
+ return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), args, false, simulated);
}
-export function applyPostStatChangeAbAttrs(attrType: Constructor,
- pokemon: Pokemon, stats: BattleStat[], levels: integer, selfTarget: boolean, simulated: boolean = false, ...args: any[]): Promise {
- return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostStatChange(pokemon, simulated, stats, levels, selfTarget, args), args, false, simulated);
+export function applyPostStatStageChangeAbAttrs(attrType: Constructor,
+ pokemon: Pokemon, stats: BattleStat[], stages: integer, selfTarget: boolean, simulated: boolean = false, ...args: any[]): Promise {
+ return applyAbAttrsInternal(attrType, pokemon, (attr, _passive) => attr.applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), args, false, simulated);
}
export function applyPreSetStatusAbAttrs(attrType: Constructor,
@@ -4703,7 +4746,8 @@ function setAbilityRevealed(pokemon: Pokemon): void {
*/
function getPokemonWithWeatherBasedForms(scene: BattleScene) {
return scene.getField(true).filter(p =>
- p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM
+ (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM)
+ || (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM)
);
}
@@ -4717,7 +4761,7 @@ export function initAbilities() {
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
new Ability(Abilities.SPEED_BOOST, 3)
- .attr(PostTurnStatChangeAbAttr, BattleStat.SPD, 1),
+ .attr(PostTurnStatStageChangeAbAttr, [ Stat.SPD ], 1),
new Ability(Abilities.BATTLE_ARMOR, 3)
.attr(BlockCritAbAttr)
.ignorable(),
@@ -4732,7 +4776,7 @@ export function initAbilities() {
.attr(StatusEffectImmunityAbAttr, StatusEffect.PARALYSIS)
.ignorable(),
new Ability(Abilities.SAND_VEIL, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.EVA, 1.2)
+ .attr(StatMultiplierAbAttr, Stat.EVA, 1.2)
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
.condition(getWeatherCondition(WeatherType.SANDSTORM))
.ignorable(),
@@ -4758,7 +4802,7 @@ export function initAbilities() {
.attr(PostFaintUnsuppressedWeatherFormChangeAbAttr)
.bypassFaint(),
new Ability(Abilities.COMPOUND_EYES, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.3),
+ .attr(StatMultiplierAbAttr, Stat.ACC, 1.3),
new Ability(Abilities.INSOMNIA, 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.SLEEP)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
@@ -4783,7 +4827,7 @@ export function initAbilities() {
.attr(ForceSwitchOutImmunityAbAttr)
.ignorable(),
new Ability(Abilities.INTIMIDATE, 3)
- .attr(PostSummonStatChangeAbAttr, BattleStat.ATK, -1, false, true),
+ .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], -1, false, true),
new Ability(Abilities.SHADOW_TAG, 3)
.attr(ArenaTrapAbAttr, (user, target) => {
if (target.hasAbility(Abilities.SHADOW_TAG)) {
@@ -4814,26 +4858,26 @@ export function initAbilities() {
.attr(PreSwitchOutResetStatusAbAttr),
new Ability(Abilities.LIGHTNING_ROD, 3)
.attr(RedirectTypeMoveAbAttr, Type.ELECTRIC)
- .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1)
+ .attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
.ignorable(),
new Ability(Abilities.SERENE_GRACE, 3)
.attr(MoveEffectChanceMultiplierAbAttr, 2)
.partial(),
new Ability(Abilities.SWIFT_SWIM, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
+ .attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
new Ability(Abilities.CHLOROPHYLL, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
+ .attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)),
new Ability(Abilities.ILLUMINATE, 3)
- .attr(ProtectStatAbAttr, BattleStat.ACC)
+ .attr(ProtectStatAbAttr, Stat.ACC)
.attr(DoubleBattleChanceAbAttr)
.ignorable(),
new Ability(Abilities.TRACE, 3)
.attr(PostSummonCopyAbilityAbAttr)
.attr(UncopiableAbilityAbAttr),
new Ability(Abilities.HUGE_POWER, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ATK, 2),
+ .attr(StatMultiplierAbAttr, Stat.ATK, 2),
new Ability(Abilities.POISON_POINT, 3)
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON)
.bypassFaint(),
@@ -4878,31 +4922,31 @@ export function initAbilities() {
new Ability(Abilities.RUN_AWAY, 3)
.attr(RunSuccessAbAttr),
new Ability(Abilities.KEEN_EYE, 3)
- .attr(ProtectStatAbAttr, BattleStat.ACC)
+ .attr(ProtectStatAbAttr, Stat.ACC)
.ignorable(),
new Ability(Abilities.HYPER_CUTTER, 3)
- .attr(ProtectStatAbAttr, BattleStat.ATK)
+ .attr(ProtectStatAbAttr, Stat.ATK)
.ignorable(),
new Ability(Abilities.PICKUP, 3)
.attr(PostBattleLootAbAttr),
new Ability(Abilities.TRUANT, 3)
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.TRUANT, 1, false),
new Ability(Abilities.HUSTLE, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ATK, 1.5)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 0.8, (user, target, move) => move.category === MoveCategory.PHYSICAL),
+ .attr(StatMultiplierAbAttr, Stat.ATK, 1.5)
+ .attr(StatMultiplierAbAttr, Stat.ACC, 0.8, (_user, _target, move) => move.category === MoveCategory.PHYSICAL),
new Ability(Abilities.CUTE_CHARM, 3)
.attr(PostDefendContactApplyTagChanceAbAttr, 30, BattlerTagType.INFATUATED),
new Ability(Abilities.PLUS, 3)
- .conditionalAttr(p => p.scene.currentBattle.double && [Abilities.PLUS, Abilities.MINUS].some(a => p.getAlly().hasAbility(a)), BattleStatMultiplierAbAttr, BattleStat.SPATK, 1.5)
+ .conditionalAttr(p => p.scene.currentBattle.double && [Abilities.PLUS, Abilities.MINUS].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5)
.ignorable(),
new Ability(Abilities.MINUS, 3)
- .conditionalAttr(p => p.scene.currentBattle.double && [Abilities.PLUS, Abilities.MINUS].some(a => p.getAlly().hasAbility(a)), BattleStatMultiplierAbAttr, BattleStat.SPATK, 1.5)
+ .conditionalAttr(p => p.scene.currentBattle.double && [Abilities.PLUS, Abilities.MINUS].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5)
.ignorable(),
new Ability(Abilities.FORECAST, 3)
.attr(UncopiableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FORECAST)
- .attr(PostWeatherChangeFormChangeAbAttr, Abilities.FORECAST),
+ .attr(PostWeatherChangeFormChangeAbAttr, Abilities.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]),
new Ability(Abilities.STICKY_HOLD, 3)
.attr(BlockItemTheftAbAttr)
.bypassFaint()
@@ -4911,9 +4955,9 @@ export function initAbilities() {
.conditionalAttr(pokemon => !Utils.randSeedInt(3), PostTurnResetStatusAbAttr),
new Ability(Abilities.GUTS, 3)
.attr(BypassBurnDamageReductionAbAttr)
- .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.ATK, 1.5),
+ .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.ATK, 1.5),
new Ability(Abilities.MARVEL_SCALE, 3)
- .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.DEF, 1.5)
+ .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.DEF, 1.5)
.ignorable(),
new Ability(Abilities.LIQUID_OOZE, 3)
.attr(ReverseDrainAbAttr),
@@ -4946,7 +4990,7 @@ export function initAbilities() {
.attr(ProtectStatAbAttr)
.ignorable(),
new Ability(Abilities.PURE_POWER, 3)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ATK, 2),
+ .attr(StatMultiplierAbAttr, Stat.ATK, 2),
new Ability(Abilities.SHELL_ARMOR, 3)
.attr(BlockCritAbAttr)
.ignorable(),
@@ -4957,25 +5001,25 @@ export function initAbilities() {
.attr(PostFaintUnsuppressedWeatherFormChangeAbAttr)
.bypassFaint(),
new Ability(Abilities.TANGLED_FEET, 4)
- .conditionalAttr(pokemon => !!pokemon.getTag(BattlerTagType.CONFUSED), BattleStatMultiplierAbAttr, BattleStat.EVA, 2)
+ .conditionalAttr(pokemon => !!pokemon.getTag(BattlerTagType.CONFUSED), StatMultiplierAbAttr, Stat.EVA, 2)
.ignorable(),
new Ability(Abilities.MOTOR_DRIVE, 4)
- .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPD, 1)
+ .attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPD, 1)
.ignorable(),
new Ability(Abilities.RIVALRY, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender === target?.gender, 1.25, true)
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.gender !== Gender.GENDERLESS && target?.gender !== Gender.GENDERLESS && user?.gender !== target?.gender, 0.75),
new Ability(Abilities.STEADFAST, 4)
- .attr(FlinchStatChangeAbAttr, BattleStat.SPD, 1),
+ .attr(FlinchStatStageChangeAbAttr, [ Stat.SPD ], 1),
new Ability(Abilities.SNOW_CLOAK, 4)
- .attr(BattleStatMultiplierAbAttr, BattleStat.EVA, 1.2)
+ .attr(StatMultiplierAbAttr, Stat.EVA, 1.2)
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW))
.ignorable(),
new Ability(Abilities.GLUTTONY, 4)
.attr(ReduceBerryUseThresholdAbAttr),
new Ability(Abilities.ANGER_POINT, 4)
- .attr(PostDefendCritStatChangeAbAttr, BattleStat.ATK, 6),
+ .attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
new Ability(Abilities.UNBURDEN, 4)
.unimplemented(),
new Ability(Abilities.HEATPROOF, 4)
@@ -4983,7 +5027,7 @@ export function initAbilities() {
.attr(ReduceBurnDamageAbAttr, 0.5)
.ignorable(),
new Ability(Abilities.SIMPLE, 4)
- .attr(StatChangeMultiplierAbAttr, 2)
+ .attr(StatStageChangeMultiplierAbAttr, 2)
.ignorable(),
new Ability(Abilities.DRY_SKIN, 4)
.attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN)
@@ -5008,11 +5052,11 @@ export function initAbilities() {
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
new Ability(Abilities.SOLAR_POWER, 4)
.attr(PostWeatherLapseDamageAbAttr, 2, WeatherType.SUNNY, WeatherType.HARSH_SUN)
- .attr(BattleStatMultiplierAbAttr, BattleStat.SPATK, 1.5)
+ .attr(StatMultiplierAbAttr, Stat.SPATK, 1.5)
.condition(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)),
new Ability(Abilities.QUICK_FEET, 4)
- .conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
- .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5),
+ .conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, StatMultiplierAbAttr, Stat.SPD, 2)
+ .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5),
new Ability(Abilities.NORMALIZE, 4)
.attr(MoveTypeChangeAbAttr, Type.NORMAL, 1.2, (user, target, move) => {
return ![Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST].includes(move.id);
@@ -5025,7 +5069,7 @@ export function initAbilities() {
.attr(AlwaysHitAbAttr)
.attr(DoubleBattleChanceAbAttr),
new Ability(Abilities.STALL, 4)
- .attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => true, -0.5),
+ .attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => true, -0.2),
new Ability(Abilities.TECHNICIAN, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => {
const power = new Utils.NumberHolder(move.power);
@@ -5052,13 +5096,13 @@ export function initAbilities() {
new Ability(Abilities.FOREWARN, 4)
.attr(ForewarnAbAttr),
new Ability(Abilities.UNAWARE, 4)
- .attr(IgnoreOpponentStatChangesAbAttr)
+ .attr(IgnoreOpponentStatStagesAbAttr)
.ignorable(),
new Ability(Abilities.TINTED_LENS, 4)
//@ts-ignore
- .attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), // TODO: fix TS issues
+ .attr(DamageBoostAbAttr, 2, (user, target, move) => target?.getMoveEffectiveness(user, move) <= 0.5), // TODO: fix TS issues
new Ability(Abilities.FILTER, 4)
- .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
+ .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
.ignorable(),
new Ability(Abilities.SLOW_START, 4)
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
@@ -5067,14 +5111,14 @@ export function initAbilities() {
.attr(IntimidateImmunityAbAttr),
new Ability(Abilities.STORM_DRAIN, 4)
.attr(RedirectTypeMoveAbAttr, Type.WATER)
- .attr(TypeImmunityStatChangeAbAttr, Type.WATER, BattleStat.SPATK, 1)
+ .attr(TypeImmunityStatStageChangeAbAttr, Type.WATER, Stat.SPATK, 1)
.ignorable(),
new Ability(Abilities.ICE_BODY, 4)
.attr(BlockWeatherDamageAttr, WeatherType.HAIL)
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.SOLID_ROCK, 4)
- .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
+ .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
.ignorable(),
new Ability(Abilities.SNOW_WARNING, 4)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SNOW)
@@ -5091,12 +5135,14 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
new Ability(Abilities.FLOWER_GIFT, 4)
- .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.ATK, 1.5)
- .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.SPDEF, 1.5)
+ .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 1.5)
+ .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.SPDEF, 1.5)
.attr(UncopiableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
- .ignorable()
- .partial(),
+ .attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT)
+ .attr(PostWeatherChangeFormChangeAbAttr, Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
+ .partial() // Should also boosts stats of ally
+ .ignorable(),
new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5)
@@ -5107,15 +5153,15 @@ export function initAbilities() {
.attr(MoveEffectChanceMultiplierAbAttr, 0)
.partial(),
new Ability(Abilities.CONTRARY, 5)
- .attr(StatChangeMultiplierAbAttr, -1)
+ .attr(StatStageChangeMultiplierAbAttr, -1)
.ignorable(),
new Ability(Abilities.UNNERVE, 5)
.attr(PreventBerryUseAbAttr),
new Ability(Abilities.DEFIANT, 5)
- .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.ATK], 2),
+ .attr(PostStatStageChangeStatStageChangeAbAttr, (target, statsChanged, stages) => stages < 0, [Stat.ATK], 2),
new Ability(Abilities.DEFEATIST, 5)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ATK, 0.5)
- .attr(BattleStatMultiplierAbAttr, BattleStat.SPATK, 0.5)
+ .attr(StatMultiplierAbAttr, Stat.ATK, 0.5)
+ .attr(StatMultiplierAbAttr, Stat.SPATK, 0.5)
.condition((pokemon) => pokemon.getHpRatio() <= 0.5),
new Ability(Abilities.CURSED_BODY, 5)
.attr(PostDefendMoveDisableAbAttr, 30)
@@ -5126,8 +5172,8 @@ export function initAbilities() {
.ignorable()
.unimplemented(),
new Ability(Abilities.WEAK_ARMOR, 5)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, BattleStat.DEF, -1)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, BattleStat.SPD, 2),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1)
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2),
new Ability(Abilities.HEAVY_METAL, 5)
.attr(WeightMultiplierAbAttr, 2)
.ignorable(),
@@ -5163,10 +5209,10 @@ export function initAbilities() {
new Ability(Abilities.REGENERATOR, 5)
.attr(PreSwitchOutHealAbAttr),
new Ability(Abilities.BIG_PECKS, 5)
- .attr(ProtectStatAbAttr, BattleStat.DEF)
+ .attr(ProtectStatAbAttr, Stat.DEF)
.ignorable(),
new Ability(Abilities.SAND_RUSH, 5)
- .attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
+ .attr(StatMultiplierAbAttr, Stat.SPD, 2)
.attr(BlockWeatherDamageAttr, WeatherType.SANDSTORM)
.condition(getWeatherCondition(WeatherType.SANDSTORM)),
new Ability(Abilities.WONDER_SKIN, 5)
@@ -5188,18 +5234,21 @@ export function initAbilities() {
.attr(PostDefendAbilityGiveAbAttr, Abilities.MUMMY)
.bypassFaint(),
new Ability(Abilities.MOXIE, 5)
- .attr(PostVictoryStatChangeAbAttr, BattleStat.ATK, 1),
+ .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
new Ability(Abilities.JUSTIFIED, 5)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.DARK && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1),
new Ability(Abilities.RATTLED, 5)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS && (move.type === Type.DARK || move.type === Type.BUG ||
- move.type === Type.GHOST), BattleStat.SPD, 1)
- .attr(PostIntimidateStatChangeAbAttr, [BattleStat.SPD], 1),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => {
+ const moveType = user.getMoveType(move);
+ return move.category !== MoveCategory.STATUS
+ && (moveType === Type.DARK || moveType === Type.BUG || moveType === Type.GHOST);
+ }, Stat.SPD, 1)
+ .attr(PostIntimidateStatStageChangeAbAttr, [Stat.SPD], 1),
new Ability(Abilities.MAGIC_BOUNCE, 5)
.ignorable()
.unimplemented(),
new Ability(Abilities.SAP_SIPPER, 5)
- .attr(TypeImmunityStatChangeAbAttr, Type.GRASS, BattleStat.ATK, 1)
+ .attr(TypeImmunityStatStageChangeAbAttr, Type.GRASS, Stat.ATK, 1)
.ignorable(),
new Ability(Abilities.PRANKSTER, 5)
.attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS, 1),
@@ -5222,7 +5271,7 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.VICTORY_STAR, 5)
- .attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.1)
+ .attr(StatMultiplierAbAttr, Stat.ACC, 1.1)
.partial(),
new Ability(Abilities.TURBOBLAZE, 5)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
@@ -5251,7 +5300,7 @@ export function initAbilities() {
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.BALLBOMB_MOVE))
.ignorable(),
new Ability(Abilities.COMPETITIVE, 6)
- .attr(PostStatChangeStatChangeAbAttr, (target, statsChanged, levels) => levels < 0, [BattleStat.SPATK], 2),
+ .attr(PostStatStageChangeStatStageChangeAbAttr, (target, statsChanged, stages) => stages < 0, [Stat.SPATK], 2),
new Ability(Abilities.STRONG_JAW, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
new Ability(Abilities.REFRIGERATE, 6)
@@ -5267,11 +5316,11 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
new Ability(Abilities.GALE_WINGS, 6)
- .attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && move.type === Type.FLYING, 1),
+ .attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === Type.FLYING, 1),
new Ability(Abilities.MEGA_LAUNCHER, 6)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5),
new Ability(Abilities.GRASS_PELT, 6)
- .conditionalAttr(getTerrainCondition(TerrainType.GRASSY), BattleStatMultiplierAbAttr, BattleStat.DEF, 1.5)
+ .conditionalAttr(getTerrainCondition(TerrainType.GRASSY), StatMultiplierAbAttr, Stat.DEF, 1.5)
.ignorable(),
new Ability(Abilities.SYMBIOSIS, 6)
.unimplemented(),
@@ -5280,7 +5329,7 @@ export function initAbilities() {
new Ability(Abilities.PIXILATE, 6)
.attr(MoveTypeChangeAbAttr, Type.FAIRY, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.GOOEY, 6)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
new Ability(Abilities.AERILATE, 6)
.attr(MoveTypeChangeAbAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.PARENTAL_BOND, 6)
@@ -5314,7 +5363,7 @@ export function initAbilities() {
.attr(PostFaintClearWeatherAbAttr)
.bypassFaint(),
new Ability(Abilities.STAMINA, 7)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.DEF, 1),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
new Ability(Abilities.WIMP_OUT, 7)
.condition(getSheerForceHitDisableAbCondition())
.unimplemented(),
@@ -5322,7 +5371,7 @@ export function initAbilities() {
.condition(getSheerForceHitDisableAbCondition())
.unimplemented(),
new Ability(Abilities.WATER_COMPACTION, 7)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
new Ability(Abilities.MERCILESS, 7)
.attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
new Ability(Abilities.SHIELDS_DOWN, 7)
@@ -5346,10 +5395,10 @@ export function initAbilities() {
new Ability(Abilities.STEELWORKER, 7)
.attr(MoveTypePowerBoostAbAttr, Type.STEEL),
new Ability(Abilities.BERSERK, 7)
- .attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [BattleStat.SPATK], 1)
+ .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [Stat.SPATK], 1)
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SLUSH_RUSH, 7)
- .attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
+ .attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW)),
new Ability(Abilities.LONG_REACH, 7)
.attr(IgnoreContactAbAttr),
@@ -5360,7 +5409,7 @@ export function initAbilities() {
new Ability(Abilities.GALVANIZE, 7)
.attr(MoveTypeChangeAbAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
new Ability(Abilities.SURGE_SURFER, 7)
- .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2),
+ .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPD, 2),
new Ability(Abilities.SCHOOLING, 7)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostSummonFormChangeAbAttr, p => p.level < 20 || p.getHpRatio() <= 0.25 ? 0 : 1)
@@ -5378,7 +5427,7 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
- .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getAttackTypeEffectiveness(move.type, user) > 0, 0, BattlerTagType.DISGUISE,
+ .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
@@ -5423,15 +5472,15 @@ export function initAbilities() {
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL], 1.3),
new Ability(Abilities.FLUFFY, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
- .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.type === Type.FIRE, 2)
+ .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE, 2)
.ignorable(),
new Ability(Abilities.DAZZLING, 7)
.attr(FieldPriorityMoveImmunityAbAttr)
.ignorable(),
new Ability(Abilities.SOUL_HEART, 7)
- .attr(PostKnockOutStatChangeAbAttr, BattleStat.SPATK, 1),
+ .attr(PostKnockOutStatStageChangeAbAttr, Stat.SPATK, 1),
new Ability(Abilities.TANGLING_HAIR, 7)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
new Ability(Abilities.RECEIVER, 7)
.attr(CopyFaintedAllyAbilityAbAttr)
.attr(UncopiableAbilityAbAttr),
@@ -5439,18 +5488,17 @@ export function initAbilities() {
.attr(CopyFaintedAllyAbilityAbAttr)
.attr(UncopiableAbilityAbAttr),
new Ability(Abilities.BEAST_BOOST, 7)
- .attr(PostVictoryStatChangeAbAttr, p => {
- const battleStats = Utils.getEnumValues(BattleStat).slice(0, -3).map(s => s as BattleStat);
- let highestBattleStat = 0;
- let highestBattleStatIndex = 0;
- battleStats.map((bs: BattleStat, i: integer) => {
- const stat = p.getStat(bs + 1);
- if (stat > highestBattleStat) {
- highestBattleStatIndex = i;
- highestBattleStat = stat;
+ .attr(PostVictoryStatStageChangeAbAttr, p => {
+ let highestStat: EffectiveStat;
+ let highestValue = 0;
+ for (const s of EFFECTIVE_STATS) {
+ const value = p.getStat(s, false);
+ if (value > highestValue) {
+ highestStat = s;
+ highestValue = value;
}
- });
- return highestBattleStatIndex;
+ }
+ return highestStat!;
}, 1),
new Ability(Abilities.RKS_SYSTEM, 7)
.attr(UncopiableAbilityAbAttr)
@@ -5474,15 +5522,15 @@ export function initAbilities() {
new Ability(Abilities.SHADOW_SHIELD, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.isFullHp(), 0.5),
new Ability(Abilities.PRISM_ARMOR, 7)
- .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75),
+ .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75),
new Ability(Abilities.NEUROFORCE, 7)
//@ts-ignore
- .attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), // TODO: fix TS issues
+ .attr(MovePowerBoostAbAttr, (user, target, move) => target?.getMoveEffectiveness(user, move) >= 2, 1.25), // TODO: fix TS issues
new Ability(Abilities.INTREPID_SWORD, 8)
- .attr(PostSummonStatChangeAbAttr, BattleStat.ATK, 1, true)
+ .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
new Ability(Abilities.DAUNTLESS_SHIELD, 8)
- .attr(PostSummonStatChangeAbAttr, BattleStat.DEF, 1, true)
+ .attr(PostSummonStatStageChangeAbAttr, [ Stat.DEF ], 1, true)
.condition(getOncePerBattleCondition(Abilities.DAUNTLESS_SHIELD)),
new Ability(Abilities.LIBERO, 8)
.attr(PokemonTypeChangeAbAttr),
@@ -5491,7 +5539,7 @@ export function initAbilities() {
.attr(FetchBallAbAttr)
.condition(getOncePerBattleCondition(Abilities.BALL_FETCH)),
new Ability(Abilities.COTTON_DOWN, 8)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.SPD, -1, false, true)
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.SPD, -1, false, true)
.bypassFaint(),
new Ability(Abilities.PROPELLER_TAIL, 8)
.attr(BlockRedirectAbAttr),
@@ -5508,7 +5556,11 @@ export function initAbilities() {
new Ability(Abilities.STALWART, 8)
.attr(BlockRedirectAbAttr),
new Ability(Abilities.STEAM_ENGINE, 8)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => (move.type === Type.FIRE || move.type === Type.WATER) && move.category !== MoveCategory.STATUS, BattleStat.SPD, 6),
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => {
+ const moveType = user.getMoveType(move);
+ return move.category !== MoveCategory.STATUS
+ && (moveType === Type.FIRE || moveType === Type.WATER);
+ }, Stat.SPD, 6),
new Ability(Abilities.PUNK_ROCK, 8)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5)
@@ -5579,26 +5631,26 @@ export function initAbilities() {
new Ability(Abilities.UNSEEN_FIST, 8)
.attr(IgnoreProtectOnContactAbAttr),
new Ability(Abilities.CURIOUS_MEDICINE, 8)
- .attr(PostSummonClearAllyStatsAbAttr),
+ .attr(PostSummonClearAllyStatStagesAbAttr),
new Ability(Abilities.TRANSISTOR, 8)
.attr(MoveTypePowerBoostAbAttr, Type.ELECTRIC),
new Ability(Abilities.DRAGONS_MAW, 8)
.attr(MoveTypePowerBoostAbAttr, Type.DRAGON),
new Ability(Abilities.CHILLING_NEIGH, 8)
- .attr(PostVictoryStatChangeAbAttr, BattleStat.ATK, 1),
+ .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
new Ability(Abilities.GRIM_NEIGH, 8)
- .attr(PostVictoryStatChangeAbAttr, BattleStat.SPATK, 1),
+ .attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1),
new Ability(Abilities.AS_ONE_GLASTRIER, 8)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneGlastrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(PreventBerryUseAbAttr)
- .attr(PostVictoryStatChangeAbAttr, BattleStat.ATK, 1)
+ .attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr),
new Ability(Abilities.AS_ONE_SPECTRIER, 8)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAsOneSpectrier", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(PreventBerryUseAbAttr)
- .attr(PostVictoryStatChangeAbAttr, BattleStat.SPATK, 1)
+ .attr(PostVictoryStatStageChangeAbAttr, Stat.SPATK, 1)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr),
@@ -5608,26 +5660,26 @@ export function initAbilities() {
new Ability(Abilities.SEED_SOWER, 9)
.attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY),
new Ability(Abilities.THERMAL_EXCHANGE, 9)
- .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.FIRE && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1)
+ .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
.ignorable(),
new Ability(Abilities.ANGER_SHELL, 9)
- .attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 1)
- .attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.DEF, BattleStat.SPDEF ], -1)
+ .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 1)
+ .attr(PostDefendHpGatedStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ Stat.DEF, Stat.SPDEF ], -1)
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.PURIFYING_SALT, 9)
.attr(StatusEffectImmunityAbAttr)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.GHOST, 0.5)
.ignorable(),
new Ability(Abilities.WELL_BAKED_BODY, 9)
- .attr(TypeImmunityStatChangeAbAttr, Type.FIRE, BattleStat.DEF, 2)
+ .attr(TypeImmunityStatStageChangeAbAttr, Type.FIRE, Stat.DEF, 2)
.ignorable(),
new Ability(Abilities.WIND_RIDER, 9)
- .attr(MoveImmunityStatChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, BattleStat.ATK, 1)
- .attr(PostSummonStatChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
+ .attr(MoveImmunityStatStageChangeAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.hasFlag(MoveFlags.WIND_MOVE) && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
+ .attr(PostSummonStatStageChangeOnArenaAbAttr, ArenaTagType.TAILWIND)
.ignorable(),
new Ability(Abilities.GUARD_DOG, 9)
- .attr(PostIntimidateStatChangeAbAttr, [BattleStat.ATK], 1, true)
+ .attr(PostIntimidateStatStageChangeAbAttr, [Stat.ATK], 1, true)
.attr(ForceSwitchOutImmunityAbAttr)
.ignorable(),
new Ability(Abilities.ROCKY_PAYLOAD, 9)
@@ -5668,31 +5720,31 @@ export function initAbilities() {
.ignorable()
.partial(),
new Ability(Abilities.VESSEL_OF_RUIN, 9)
- .attr(FieldMultiplyBattleStatAbAttr, Stat.SPATK, 0.75)
- .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: getStatName(Stat.SPATK) }))
+ .attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75)
+ .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPATK)) }))
.ignorable(),
new Ability(Abilities.SWORD_OF_RUIN, 9)
- .attr(FieldMultiplyBattleStatAbAttr, Stat.DEF, 0.75)
- .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonSwordOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: getStatName(Stat.DEF) }))
+ .attr(FieldMultiplyStatAbAttr, Stat.DEF, 0.75)
+ .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonSwordOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.DEF)) }))
.ignorable(),
new Ability(Abilities.TABLETS_OF_RUIN, 9)
- .attr(FieldMultiplyBattleStatAbAttr, Stat.ATK, 0.75)
- .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonTabletsOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: getStatName(Stat.ATK) }))
+ .attr(FieldMultiplyStatAbAttr, Stat.ATK, 0.75)
+ .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonTabletsOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) }))
.ignorable(),
new Ability(Abilities.BEADS_OF_RUIN, 9)
- .attr(FieldMultiplyBattleStatAbAttr, Stat.SPDEF, 0.75)
- .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonBeadsOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: getStatName(Stat.SPDEF) }))
+ .attr(FieldMultiplyStatAbAttr, Stat.SPDEF, 0.75)
+ .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonBeadsOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPDEF)) }))
.ignorable(),
new Ability(Abilities.ORICHALCUM_PULSE, 9)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SUNNY)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.SUNNY)
- .conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.ATK, 4 / 3),
+ .conditionalAttr(getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN), StatMultiplierAbAttr, Stat.ATK, 4 / 3),
new Ability(Abilities.HADRON_ENGINE, 9)
.attr(PostSummonTerrainChangeAbAttr, TerrainType.ELECTRIC)
.attr(PostBiomeChangeTerrainChangeAbAttr, TerrainType.ELECTRIC)
- .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPATK, 4 / 3),
+ .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPATK, 4 / 3),
new Ability(Abilities.OPPORTUNIST, 9)
- .attr(StatChangeCopyAbAttr),
+ .attr(StatStageChangeCopyAbAttr),
new Ability(Abilities.CUD_CHEW, 9)
.unimplemented(),
new Ability(Abilities.SHARPNESS, 9)
@@ -5713,16 +5765,16 @@ export function initAbilities() {
.partial() // Healing not blocked by Heal Block
.ignorable(),
new Ability(Abilities.MYCELIUM_MIGHT, 9)
- .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.5)
+ .attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
.attr(PreventBypassSpeedChanceAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS)
.attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS),
new Ability(Abilities.MINDS_EYE, 9)
.attr(IgnoreTypeImmunityAbAttr, Type.GHOST, [Type.NORMAL, Type.FIGHTING])
- .attr(ProtectStatAbAttr, BattleStat.ACC)
- .attr(IgnoreOpponentEvasionAbAttr)
+ .attr(ProtectStatAbAttr, Stat.ACC)
+ .attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ])
.ignorable(),
new Ability(Abilities.SUPERSWEET_SYRUP, 9)
- .attr(PostSummonStatChangeAbAttr, BattleStat.EVA, -1)
+ .attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1)
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
new Ability(Abilities.HOSPITALITY, 9)
.attr(PostSummonAllyHealAbAttr, 4, true)
@@ -5730,25 +5782,25 @@ export function initAbilities() {
new Ability(Abilities.TOXIC_CHAIN, 9)
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)
- .attr(PostBattleInitStatChangeAbAttr, BattleStat.SPD, 1, true)
+ .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPD ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9)
- .attr(PostBattleInitStatChangeAbAttr, BattleStat.SPDEF, 1, true)
+ .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9)
- .attr(PostBattleInitStatChangeAbAttr, BattleStat.ATK, 1, true)
+ .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9)
- .attr(PostBattleInitStatChangeAbAttr, BattleStat.DEF, 1, true)
+ .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
@@ -5761,10 +5813,10 @@ export function initAbilities() {
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
new Ability(Abilities.TERA_SHELL, 9)
+ .attr(FullHpResistTypeAbAttr)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
- .ignorable()
- .unimplemented(),
+ .ignorable(),
new Ability(Abilities.TERAFORM_ZERO, 9)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts
index acb5bc74424..5570550c1b1 100644
--- a/src/data/arena-tag.ts
+++ b/src/data/arena-tag.ts
@@ -7,17 +7,17 @@ import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
-import { BattleStat } from "./battle-stat";
+import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import i18next from "i18next";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
-import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js";
-import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js";
-import { StatChangePhase } from "#app/phases/stat-change-phase.js";
+import { MoveEffectPhase } from "#app/phases/move-effect-phase";
+import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
+import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
export enum ArenaTagSide {
BOTH,
@@ -804,8 +804,8 @@ class StickyWebTag extends ArenaTrapTag {
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
- const statLevels = new Utils.NumberHolder(-1);
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [BattleStat.SPD], statLevels.value));
+ const stages = new Utils.NumberHolder(-1);
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
}
}
@@ -893,7 +893,7 @@ class TailwindTag extends ArenaTag {
// Raise attack by one stage if party member has WIND_RIDER ability
if (pokemon.hasAbility(Abilities.WIND_RIDER)) {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK], 1, true));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true));
}
}
}
@@ -923,6 +923,21 @@ class HappyHourTag extends ArenaTag {
}
}
+class SafeguardTag extends ArenaTag {
+ constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
+ super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
+ }
+
+ onAdd(arena: Arena): void {
+ arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
+ }
+
+ onRemove(arena: Arena): void {
+ arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
+ }
+}
+
+
class NoneTag extends ArenaTag {
constructor() {
super(ArenaTagType.NONE, 0, undefined, undefined);
@@ -974,6 +989,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new TailwindTag(turnCount, sourceId, side);
case ArenaTagType.HAPPY_HOUR:
return new HappyHourTag(turnCount, sourceId, side);
+ case ArenaTagType.SAFEGUARD:
+ return new SafeguardTag(turnCount, sourceId, side);
default:
return null;
}
diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts
index a2f6e41f4ae..da4e7f6a33b 100644
--- a/src/data/battle-anims.ts
+++ b/src/data/battle-anims.ts
@@ -788,10 +788,10 @@ export abstract class BattleAnim {
targetSprite.pipelineData["tone"] = [ 0.0, 0.0, 0.0, 0.0 ];
targetSprite.setAngle(0);
if (!this.isHideUser() && userSprite) {
- userSprite.setVisible(true);
+ this.user?.getSprite().setVisible(true); // using this.user to fix context loss due to isOppAnim swap (#481)
}
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) {
- targetSprite.setVisible(true);
+ this.target?.getSprite().setVisible(true); // using this.target to fix context loss due to isOppAnim swap (#481)
}
for (const ms of Object.values(spriteCache).flat()) {
if (ms) {
diff --git a/src/data/battle-stat.ts b/src/data/battle-stat.ts
deleted file mode 100644
index a0cb7ca88e1..00000000000
--- a/src/data/battle-stat.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import i18next, { ParseKeys } from "i18next";
-
-export enum BattleStat {
- ATK,
- DEF,
- SPATK,
- SPDEF,
- SPD,
- ACC,
- EVA,
- RAND,
- HP
-}
-
-export function getBattleStatName(stat: BattleStat) {
- switch (stat) {
- case BattleStat.ATK:
- return i18next.t("pokemonInfo:Stat.ATK");
- case BattleStat.DEF:
- return i18next.t("pokemonInfo:Stat.DEF");
- case BattleStat.SPATK:
- return i18next.t("pokemonInfo:Stat.SPATK");
- case BattleStat.SPDEF:
- return i18next.t("pokemonInfo:Stat.SPDEF");
- case BattleStat.SPD:
- return i18next.t("pokemonInfo:Stat.SPD");
- case BattleStat.ACC:
- return i18next.t("pokemonInfo:Stat.ACC");
- case BattleStat.EVA:
- return i18next.t("pokemonInfo:Stat.EVA");
- case BattleStat.HP:
- return i18next.t("pokemonInfo:Stat.HPStat");
- default:
- return "???";
- }
-}
-
-export function getBattleStatLevelChangeDescription(pokemonNameWithAffix: string, stats: string, levels: integer, up: boolean, count: number = 1) {
- const stringKey = (() => {
- if (up) {
- switch (levels) {
- case 1:
- return "battle:statRose";
- case 2:
- return "battle:statSharplyRose";
- case 3:
- case 4:
- case 5:
- case 6:
- return "battle:statRoseDrastically";
- default:
- return "battle:statWontGoAnyHigher";
- }
- } else {
- switch (levels) {
- case 1:
- return "battle:statFell";
- case 2:
- return "battle:statHarshlyFell";
- case 3:
- case 4:
- case 5:
- case 6:
- return "battle:statSeverelyFell";
- default:
- return "battle:statWontGoAnyLower";
- }
- }
- })();
- return i18next.t(stringKey as ParseKeys, { pokemonNameWithAffix, stats, count });
-}
diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts
index 8c05d296e76..66bcc7b9c3c 100644
--- a/src/data/battler-tags.ts
+++ b/src/data/battler-tags.ts
@@ -1,7 +1,6 @@
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
-import { Stat, getStatName } from "./pokemon-stat";
import { StatusEffect } from "./status-effect";
import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves } from "./move";
@@ -9,20 +8,20 @@ import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
import { TerrainType } from "./terrain";
import { WeatherType } from "./weather";
-import { BattleStat } from "./battle-stat";
import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import i18next from "#app/plugins/i18n.js";
-import { CommonAnimPhase } from "#app/phases/common-anim-phase.js";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
-import { MovePhase } from "#app/phases/move-phase.js";
-import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js";
-import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js";
-import { StatChangePhase, StatChangeCallback } from "#app/phases/stat-change-phase.js";
+import i18next from "#app/plugins/i18n";
+import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
+import { CommonAnimPhase } from "#app/phases/common-anim-phase";
+import { MoveEffectPhase } from "#app/phases/move-effect-phase";
+import { MovePhase } from "#app/phases/move-phase";
+import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
+import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
+import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
export enum BattlerTagLapseType {
FAINT,
@@ -40,13 +39,15 @@ export class BattlerTag {
public turnCount: number;
public sourceMove: Moves;
public sourceId?: number;
+ public isBatonPassable: boolean;
- constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) {
+ constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number, isBatonPassable: boolean = false) {
this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId;
+ this.isBatonPassable = isBatonPassable;
}
canAdd(pokemon: Pokemon): boolean {
@@ -207,12 +208,12 @@ export class ShellTrapTag extends BattlerTag {
export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
- super(tagType, lapseType, turnCount, sourceMove, sourceId);
+ super(tagType, lapseType, turnCount, sourceMove, sourceId, true);
}
canAdd(pokemon: Pokemon): boolean {
const isGhost = pokemon.isOfType(Type.GHOST);
- const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED);
+ const isTrapped = pokemon.getTag(TrappedTag);
return !isTrapped && !isGhost;
}
@@ -245,6 +246,23 @@ export class TrappedTag extends BattlerTag {
}
}
+/**
+ * BattlerTag implementing No Retreat's trapping effect.
+ * This is treated separately from other trapping effects to prevent
+ * Ghost-type Pokemon from being able to reuse the move.
+ * @extends TrappedTag
+ */
+class NoRetreatTag extends TrappedTag {
+ constructor(sourceId: number) {
+ super(BattlerTagType.NO_RETREAT, BattlerTagLapseType.CUSTOM, 0, Moves.NO_RETREAT, sourceId);
+ }
+
+ /** overrides {@linkcode TrappedTag.apply}, removing the Ghost-type condition */
+ canAdd(pokemon: Pokemon): boolean {
+ return !pokemon.getTag(TrappedTag);
+ }
+}
+
/**
* BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Flinch Flinch} status condition
*/
@@ -310,7 +328,7 @@ export class InterruptedTag extends BattlerTag {
*/
export class ConfusedTag extends BattlerTag {
constructor(turnCount: number, sourceMove: Moves) {
- super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove);
+ super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove, undefined, true);
}
canAdd(pokemon: Pokemon): boolean {
@@ -345,8 +363,8 @@ export class ConfusedTag extends BattlerTag {
// 1/3 chance of hitting self with a 40 base power move
if (pokemon.randSeedInt(3) === 0) {
- const atk = pokemon.getBattleStat(Stat.ATK);
- const def = pokemon.getBattleStat(Stat.DEF);
+ const atk = pokemon.getEffectiveStat(Stat.ATK);
+ const def = pokemon.getEffectiveStat(Stat.DEF);
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage);
@@ -370,7 +388,7 @@ export class ConfusedTag extends BattlerTag {
*/
export class DestinyBondTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: number) {
- super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId);
+ super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId, true);
}
/**
@@ -489,7 +507,7 @@ export class SeedTag extends BattlerTag {
private sourceIndex: number;
constructor(sourceId: number) {
- super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, Moves.LEECH_SEED, sourceId);
+ super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, Moves.LEECH_SEED, sourceId, true);
}
/**
@@ -750,7 +768,7 @@ export class OctolockTag extends TrappedTag {
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (shouldLapse) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], -1));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.DEF, Stat.SPDEF ], -1));
return true;
}
@@ -760,7 +778,7 @@ export class OctolockTag extends TrappedTag {
export class AquaRingTag extends BattlerTag {
constructor() {
- super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined);
+ super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined, true);
}
onAdd(pokemon: Pokemon): void {
@@ -792,7 +810,7 @@ export class AquaRingTag extends BattlerTag {
/** Tag used to allow moves that interact with {@link Moves.MINIMIZE} to function */
export class MinimizeTag extends BattlerTag {
constructor() {
- super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE, undefined);
+ super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE);
}
canAdd(pokemon: Pokemon): boolean {
@@ -864,7 +882,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
}
canAdd(pokemon: Pokemon): boolean {
- return !pokemon.isOfType(Type.GHOST) && !pokemon.findTag(t => t instanceof DamagingTrapTag);
+ return !pokemon.getTag(TrappedTag);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@@ -1076,7 +1094,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
}
}
-export class ContactStatChangeProtectedTag extends ProtectedTag {
+export class ContactStatStageChangeProtectedTag extends ProtectedTag {
private stat: BattleStat;
private levels: number;
@@ -1093,7 +1111,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
*/
loadTag(source: BattlerTag | any): void {
super.loadTag(source);
- this.stat = source.stat as BattleStat;
+ this.stat = source.stat;
this.levels = source.levels;
}
@@ -1104,7 +1122,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
}
}
@@ -1190,7 +1208,7 @@ export class SturdyTag extends BattlerTag {
export class PerishSongTag extends BattlerTag {
constructor(turnCount: number) {
- super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG);
+ super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG, undefined, true);
}
canAdd(pokemon: Pokemon): boolean {
@@ -1246,7 +1264,7 @@ export class AbilityBattlerTag extends BattlerTag {
public ability: Abilities;
constructor(tagType: BattlerTagType, ability: Abilities, lapseType: BattlerTagLapseType, turnCount: number) {
- super(tagType, lapseType, turnCount, undefined);
+ super(tagType, lapseType, turnCount);
this.ability = ability;
}
@@ -1331,11 +1349,10 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
- const stats = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
- let highestStat: Stat;
- stats.map(s => pokemon.getBattleStat(s)).reduce((highestValue: number, value: number, i: number) => {
+ let highestStat: EffectiveStat;
+ EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s)).reduce((highestValue: number, value: number, i: number) => {
if (value > highestValue) {
- highestStat = stats[i];
+ highestStat = EFFECTIVE_STATS[i];
return value;
}
return highestValue;
@@ -1353,7 +1370,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break;
}
- pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatName(highestStat) }), null, false, null, true);
+ pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true);
}
onRemove(pokemon: Pokemon): void {
@@ -1423,7 +1440,7 @@ export class TypeImmuneTag extends BattlerTag {
public immuneType: Type;
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) {
- super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove);
+ super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove, undefined, true);
this.immuneType = immuneType;
}
@@ -1487,7 +1504,7 @@ export class TypeBoostTag extends BattlerTag {
export class CritBoostTag extends BattlerTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) {
- super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove);
+ super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove, undefined, true);
}
onAdd(pokemon: Pokemon): void {
@@ -1507,6 +1524,25 @@ export class CritBoostTag extends BattlerTag {
}
}
+/**
+ * Tag for the effects of Dragon Cheer, which boosts the critical hit ratio of the user's allies.
+ * @extends {CritBoostTag}
+ */
+export class DragonCheerTag extends CritBoostTag {
+ /** The types of the user's ally when the tag is added */
+ public typesOnAdd: Type[];
+
+ constructor() {
+ super(BattlerTagType.CRIT_BOOST, Moves.DRAGON_CHEER);
+ }
+
+ onAdd(pokemon: Pokemon): void {
+ super.onAdd(pokemon);
+
+ this.typesOnAdd = pokemon.getTypes(true);
+ }
+}
+
export class SaltCuredTag extends BattlerTag {
private sourceIndex: number;
@@ -1560,7 +1596,7 @@ export class CursedTag extends BattlerTag {
private sourceIndex: number;
constructor(sourceId: number) {
- super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, Moves.CURSE, sourceId);
+ super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, Moves.CURSE, sourceId, true);
}
/**
@@ -1678,25 +1714,25 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag {
*/
export class StockpilingTag extends BattlerTag {
public stockpiledCount: number = 0;
- public statChangeCounts: { [BattleStat.DEF]: number; [BattleStat.SPDEF]: number } = {
- [BattleStat.DEF]: 0,
- [BattleStat.SPDEF]: 0
+ public statChangeCounts: { [Stat.DEF]: number; [Stat.SPDEF]: number } = {
+ [Stat.DEF]: 0,
+ [Stat.SPDEF]: 0
};
constructor(sourceMove: Moves = Moves.NONE) {
super(BattlerTagType.STOCKPILING, BattlerTagLapseType.CUSTOM, 1, sourceMove);
}
- private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => {
- const defChange = statChanges[statsChanged.indexOf(BattleStat.DEF)] ?? 0;
- const spDefChange = statChanges[statsChanged.indexOf(BattleStat.SPDEF)] ?? 0;
+ private onStatStagesChanged: StatStageChangeCallback = (_, statsChanged, statChanges) => {
+ const defChange = statChanges[statsChanged.indexOf(Stat.DEF)] ?? 0;
+ const spDefChange = statChanges[statsChanged.indexOf(Stat.SPDEF)] ?? 0;
if (defChange) {
- this.statChangeCounts[BattleStat.DEF]++;
+ this.statChangeCounts[Stat.DEF]++;
}
if (spDefChange) {
- this.statChangeCounts[BattleStat.SPDEF]++;
+ this.statChangeCounts[Stat.SPDEF]++;
}
};
@@ -1704,8 +1740,8 @@ export class StockpilingTag extends BattlerTag {
super.loadTag(source);
this.stockpiledCount = source.stockpiledCount || 0;
this.statChangeCounts = {
- [ BattleStat.DEF ]: source.statChangeCounts?.[ BattleStat.DEF ] ?? 0,
- [ BattleStat.SPDEF ]: source.statChangeCounts?.[ BattleStat.SPDEF ] ?? 0,
+ [ Stat.DEF ]: source.statChangeCounts?.[ Stat.DEF ] ?? 0,
+ [ Stat.SPDEF ]: source.statChangeCounts?.[ Stat.SPDEF ] ?? 0,
};
}
@@ -1725,9 +1761,9 @@ export class StockpilingTag extends BattlerTag {
}));
// Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes.
- pokemon.scene.unshiftPhase(new StatChangePhase(
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(
pokemon.scene, pokemon.getBattlerIndex(), true,
- [BattleStat.SPDEF, BattleStat.DEF], 1, true, false, true, this.onStatsChanged
+ [Stat.SPDEF, Stat.DEF], 1, true, false, true, this.onStatStagesChanged
));
}
}
@@ -1741,15 +1777,15 @@ export class StockpilingTag extends BattlerTag {
* one stage for each stack which had successfully changed that particular stat during onAdd.
*/
onRemove(pokemon: Pokemon): void {
- const defChange = this.statChangeCounts[BattleStat.DEF];
- const spDefChange = this.statChangeCounts[BattleStat.SPDEF];
+ const defChange = this.statChangeCounts[Stat.DEF];
+ const spDefChange = this.statChangeCounts[Stat.SPDEF];
if (defChange) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF], -defChange, true, false, true));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF ], -defChange, true, false, true));
}
if (spDefChange) {
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.SPDEF], -spDefChange, true, false, true));
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF ], -spDefChange, true, false, true));
}
}
}
@@ -1864,6 +1900,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new DrowsyTag();
case BattlerTagType.TRAPPED:
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
+ case BattlerTagType.NO_RETREAT:
+ return new NoRetreatTag(sourceId);
case BattlerTagType.BIND:
return new BindTag(turnCount, sourceId);
case BattlerTagType.WRAP:
@@ -1889,11 +1927,11 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD:
- return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.ATK, -1);
+ return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT:
- return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.DEF, -2);
+ return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP:
- return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.SPD, -1);
+ return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK:
@@ -1923,6 +1961,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
case BattlerTagType.CRIT_BOOST:
return new CritBoostTag(tagType, sourceMove);
+ case BattlerTagType.DRAGON_CHEER:
+ return new DragonCheerTag();
case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
diff --git a/src/data/berry.ts b/src/data/berry.ts
index d0c9c311e16..01325ee39dd 100644
--- a/src/data/berry.ts
+++ b/src/data/berry.ts
@@ -1,14 +1,14 @@
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { HitResult } from "../field/pokemon";
-import { BattleStat } from "./battle-stat";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
-import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js";
-import { StatChangePhase } from "#app/phases/stat-change-phase.js";
+import { Stat, type BattleStat } from "#app/enums/stat";
+import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
export function getBerryName(berryType: BerryType): string {
return i18next.t(`berry:${BerryType[berryType]}.name`);
@@ -35,9 +35,10 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.SALAC:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
- const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
+ // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
+ const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
- return pokemon.getHpRatio() < threshold.value && pokemon.summonData.battleStats[battleStat] < 6;
+ return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
@@ -95,10 +96,11 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
- const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
- const statLevels = new Utils.NumberHolder(1);
- applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels);
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value));
+ // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
+ const stat: BattleStat = berryType - BerryType.ENIGMA;
+ const statStages = new Utils.NumberHolder(1);
+ applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
@@ -112,9 +114,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
- const statLevels = new Utils.NumberHolder(2);
- applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels);
- pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], statLevels.value));
+ const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
+ const stages = new Utils.NumberHolder(2);
+ applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
+ pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
diff --git a/src/data/biomes.ts b/src/data/biomes.ts
index d9ea22f50f2..0e37cc94ff5 100644
--- a/src/data/biomes.ts
+++ b/src/data/biomes.ts
@@ -1,6 +1,6 @@
import { Type } from "./type";
import * as Utils from "../utils";
-import {pokemonEvolutions, SpeciesFormEvolution} from "./pokemon-evolutions";
+import { pokemonEvolutions, SpeciesFormEvolution } from "./pokemon-evolutions";
import i18next from "i18next";
import { Biome } from "#enums/biome";
import { Species } from "#enums/species";
@@ -46,7 +46,7 @@ export const biomeLinks: BiomeLinks = {
[Biome.SEABED]: [ Biome.CAVE, [ Biome.VOLCANO, 3 ] ],
[Biome.MOUNTAIN]: [ Biome.VOLCANO, [ Biome.WASTELAND, 2 ], [ Biome.SPACE, 3 ] ],
[Biome.BADLANDS]: [ Biome.DESERT, Biome.MOUNTAIN ],
- [Biome.CAVE]: [ Biome.BADLANDS, Biome.LAKE [ Biome.LABORATORY, 2 ] ],
+ [Biome.CAVE]: [ Biome.BADLANDS, Biome.LAKE, [ Biome.LABORATORY, 2 ] ],
[Biome.DESERT]: [ Biome.RUINS, [ Biome.CONSTRUCTION_SITE, 2 ] ],
[Biome.ICE_CAVE]: Biome.SNOWY_FOREST,
[Biome.MEADOW]: [ Biome.PLAINS, Biome.FAIRY_CAVE ],
@@ -7666,7 +7666,7 @@ export function initBiomes() {
if (biome === Biome.END) {
const biomeList = Object.keys(Biome).filter(key => !isNaN(Number(key)));
biomeList.pop(); // Removes Biome.END from the list
- const randIndex = Utils.randInt(biomeList.length, 2); // Will never be Biome.TOWN or Biome.PLAINS
+ const randIndex = Utils.randInt(biomeList.length, 1); // Will never be Biome.TOWN
biome = Biome[biomeList[randIndex]];
}
const linkedBiomes: (Biome | [ Biome, integer ])[] = Array.isArray(biomeLinks[biome])
diff --git a/src/data/challenge.ts b/src/data/challenge.ts
index 36f696e63c1..62751b92f9c 100644
--- a/src/data/challenge.ts
+++ b/src/data/challenge.ts
@@ -13,7 +13,6 @@ import { TrainerType } from "#enums/trainer-type";
import { Nature } from "./nature";
import { Moves } from "#app/enums/moves.js";
import { TypeColor, TypeShadow } from "#app/enums/color.js";
-import { Gender } from "./gender";
import { pokemonEvolutions } from "./pokemon-evolutions";
import { pokemonFormChanges } from "./pokemon-forms";
@@ -659,7 +658,6 @@ export class FreshStartChallenge extends Challenge {
pokemon.luck = 0; // No luck
pokemon.shiny = false; // Not shiny
pokemon.variant = 0; // Not shiny
- pokemon.gender = Gender.MALE; // Starters default to male
pokemon.formIndex = 0; // Froakie should be base form
pokemon.ivs = [10, 10, 10, 10, 10, 10]; // Default IVs of 10 for all stats
return true;
diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts
index 3ef6d30643c..a2ba06b657f 100644
--- a/src/data/dialogue.ts
+++ b/src/data/dialogue.ts
@@ -1,6 +1,6 @@
import { BattleSpec } from "#enums/battle-spec";
import { TrainerType } from "#enums/trainer-type";
-import {trainerConfigs} from "./trainer-config";
+import { trainerConfigs } from "./trainer-config";
export interface TrainerTypeMessages {
encounter?: string | string[],
@@ -707,6 +707,20 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
]
}
],
+ [TrainerType.ROOD]: [
+ {
+ encounter: [
+ "dialogue:rood.encounter.1",
+ "dialogue:rood.encounter.2",
+ "dialogue:rood.encounter.3",
+ ],
+ victory: [
+ "dialogue:rood.victory.1",
+ "dialogue:rood.victory.2",
+ "dialogue:rood.victory.3",
+ ]
+ }
+ ],
[TrainerType.FLARE_GRUNT]: [
{
encounter: [
diff --git a/src/data/egg-hatch-data.ts b/src/data/egg-hatch-data.ts
new file mode 100644
index 00000000000..e754a9205c4
--- /dev/null
+++ b/src/data/egg-hatch-data.ts
@@ -0,0 +1,98 @@
+import BattleScene from "#app/battle-scene";
+import { PlayerPokemon } from "#app/field/pokemon";
+import { DexEntry, StarterDataEntry } from "#app/system/game-data";
+
+/**
+ * Stores data associated with a specific egg and the hatched pokemon
+ * Allows hatch info to be stored at hatch then retrieved for display during egg summary
+ */
+export class EggHatchData {
+ /** the pokemon that hatched from the file (including shiny, IVs, ability) */
+ public pokemon: PlayerPokemon;
+ /** index of the egg move from the hatched pokemon (not stored in PlayerPokemon) */
+ public eggMoveIndex: number;
+ /** boolean indicating if the egg move for the hatch is new */
+ public eggMoveUnlocked: boolean;
+ /** stored copy of the hatched pokemon's dex entry before it was updated due to hatch */
+ public dexEntryBeforeUpdate: DexEntry;
+ /** stored copy of the hatched pokemon's starter entry before it was updated due to hatch */
+ public starterDataEntryBeforeUpdate: StarterDataEntry;
+ /** reference to the battle scene to get gamedata and update dex */
+ private scene: BattleScene;
+
+ constructor(scene: BattleScene, pokemon: PlayerPokemon, eggMoveIndex: number) {
+ this.scene = scene;
+ this.pokemon = pokemon;
+ this.eggMoveIndex = eggMoveIndex;
+ }
+
+ /**
+ * Sets the boolean for if the egg move for the hatch is a new unlock
+ * @param unlocked True if the EM is new
+ */
+ setEggMoveUnlocked(unlocked: boolean) {
+ this.eggMoveUnlocked = unlocked;
+ }
+
+ /**
+ * Stores a copy of the current DexEntry of the pokemon and StarterDataEntry of its starter
+ * Used before updating the dex, so comparing the pokemon to these entries will show the new attributes
+ */
+ setDex() {
+ const currDexEntry = this.scene.gameData.dexData[this.pokemon.species.speciesId];
+ const currStarterDataEntry = this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()];
+ this.dexEntryBeforeUpdate = {
+ seenAttr: currDexEntry.seenAttr,
+ caughtAttr: currDexEntry.caughtAttr,
+ natureAttr: currDexEntry.natureAttr,
+ seenCount: currDexEntry.seenCount,
+ caughtCount: currDexEntry.caughtCount,
+ hatchedCount: currDexEntry.hatchedCount,
+ ivs: [...currDexEntry.ivs]
+ };
+ this.starterDataEntryBeforeUpdate = {
+ moveset: currStarterDataEntry.moveset,
+ eggMoves: currStarterDataEntry.eggMoves,
+ candyCount: currStarterDataEntry.candyCount,
+ friendship: currStarterDataEntry.friendship,
+ abilityAttr: currStarterDataEntry.abilityAttr,
+ passiveAttr: currStarterDataEntry.passiveAttr,
+ valueReduction: currStarterDataEntry.valueReduction,
+ classicWinCount: currStarterDataEntry.classicWinCount
+ };
+ }
+
+ /**
+ * Gets the dex entry before update
+ * @returns Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
+ */
+ getDex(): DexEntry {
+ return this.dexEntryBeforeUpdate;
+ }
+
+ /**
+ * Gets the starter dex entry before update
+ * @returns Starter Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
+ */
+ getStarterEntry(): StarterDataEntry {
+ return this.starterDataEntryBeforeUpdate;
+ }
+
+ /**
+ * Update the pokedex data corresponding with the new hatch's pokemon data
+ * Also sets whether the egg move is a new unlock or not
+ * @param showMessage boolean to show messages for the new catches and egg moves (false by default)
+ * @returns
+ */
+ updatePokemon(showMessage : boolean = false) {
+ return new Promise(resolve => {
+ this.scene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
+ this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
+ this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => {
+ this.setEggMoveUnlocked(value);
+ resolve();
+ });
+ });
+ });
+ }
+}
diff --git a/src/data/egg.ts b/src/data/egg.ts
index 3e872d364f3..9beb944de69 100644
--- a/src/data/egg.ts
+++ b/src/data/egg.ts
@@ -139,46 +139,57 @@ export class Egg {
////
constructor(eggOptions?: IEggOptions) {
- //if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
+ const generateEggProperties = (eggOptions?: IEggOptions) => {
+ //if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
- this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
- // Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
- this._tier = eggOptions?.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
- // If egg was pulled, check if egg pity needs to override the egg tier
- if (eggOptions?.pulled) {
- // Needs this._tier and this._sourceType to work
- this.checkForPityTierOverrides(eggOptions.scene!); // TODO: is this bang correct?
- }
+ this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
+ // Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
+ this._tier = eggOptions?.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
+ // If egg was pulled, check if egg pity needs to override the egg tier
+ if (eggOptions?.pulled) {
+ // Needs this._tier and this._sourceType to work
+ this.checkForPityTierOverrides(eggOptions.scene!); // TODO: is this bang correct?
+ }
- this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
+ this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier);
- this._sourceType = eggOptions?.sourceType ?? undefined;
- this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
- this._timestamp = eggOptions?.timestamp ?? new Date().getTime();
+ this._sourceType = eggOptions?.sourceType ?? undefined;
+ this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
+ this._timestamp = eggOptions?.timestamp ?? new Date().getTime();
- // First roll shiny and variant so we can filter if species with an variant exist
- this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
- this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
- this._species = eggOptions?.species ?? this.rollSpecies(eggOptions!.scene!)!; // TODO: Are those bangs correct?
+ // First roll shiny and variant so we can filter if species with an variant exist
+ this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
+ this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant());
+ this._species = eggOptions?.species ?? this.rollSpecies(eggOptions!.scene!)!; // TODO: Are those bangs correct?
- this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
+ this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false;
- // Override egg tier and hatchwaves if species was given
- if (eggOptions?.species) {
- this._tier = this.getEggTierFromSpeciesStarterValue();
- this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
- }
- // If species has no variant, set variantTier to common. This needs to
- // be done because species with no variants get filtered at rollSpecies but if the
- // species is set via options or the legendary gacha pokemon gets choosen the check never happens
- if (this._species && !getPokemonSpecies(this._species).hasVariants()) {
- this._variantTier = VariantTier.COMMON;
- }
- // Needs this._tier so it needs to be generated afer the tier override if bought from same species
- this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
- if (eggOptions?.pulled) {
- this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
- this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
+ // Override egg tier and hatchwaves if species was given
+ if (eggOptions?.species) {
+ this._tier = this.getEggTierFromSpeciesStarterValue();
+ this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
+ }
+ // If species has no variant, set variantTier to common. This needs to
+ // be done because species with no variants get filtered at rollSpecies but if the
+ // species is set via options or the legendary gacha pokemon gets choosen the check never happens
+ if (this._species && !getPokemonSpecies(this._species).hasVariants()) {
+ this._variantTier = VariantTier.COMMON;
+ }
+ // Needs this._tier so it needs to be generated afer the tier override if bought from same species
+ this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
+ if (eggOptions?.pulled) {
+ this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
+ this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
+ }
+ };
+
+ if (eggOptions?.scene) {
+ const seedOverride = Utils.randomString(24);
+ eggOptions?.scene.executeWithSeedOffset(() => {
+ generateEggProperties(eggOptions);
+ }, 0, seedOverride);
+ } else { // For legacy eggs without scene
+ generateEggProperties(eggOptions);
}
}
@@ -200,37 +211,46 @@ export class Egg {
// Generates a PlayerPokemon from an egg
public generatePlayerPokemon(scene: BattleScene): PlayerPokemon {
- // Legacy egg wants to hatch. Generate missing properties
- if (!this._species) {
- this._isShiny = this.rollShiny();
- this._species = this.rollSpecies(scene!)!; // TODO: are these bangs correct?
- }
+ let ret: PlayerPokemon;
- let pokemonSpecies = getPokemonSpecies(this._species);
- // Special condition to have Phione eggs also have a chance of generating Manaphy
- if (this._species === Species.PHIONE) {
- pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
- }
+ const generatePlayerPokemonHelper = (scene: BattleScene) => {
+ // Legacy egg wants to hatch. Generate missing properties
+ if (!this._species) {
+ this._isShiny = this.rollShiny();
+ this._species = this.rollSpecies(scene!)!; // TODO: are these bangs correct?
+ }
- // Sets the hidden ability if a hidden ability exists and
- // the override is set or the egg hits the chance
- let abilityIndex: number | undefined = undefined;
- const sameSpeciesEggHACheck = (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE));
- const gachaEggHACheck = (!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE));
- if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility || sameSpeciesEggHACheck || gachaEggHACheck)) {
- abilityIndex = 2;
- }
+ let pokemonSpecies = getPokemonSpecies(this._species);
+ // Special condition to have Phione eggs also have a chance of generating Manaphy
+ if (this._species === Species.PHIONE) {
+ pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
+ }
- // This function has way to many optional parameters
- const ret: PlayerPokemon = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
- ret.shiny = this._isShiny;
- ret.variant = this._variantTier;
+ // Sets the hidden ability if a hidden ability exists and
+ // the override is set or the egg hits the chance
+ let abilityIndex: number | undefined = undefined;
+ const sameSpeciesEggHACheck = (this._sourceType === EggSourceType.SAME_SPECIES_EGG && !Utils.randSeedInt(SAME_SPECIES_EGG_HA_RATE));
+ const gachaEggHACheck = (!(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !Utils.randSeedInt(GACHA_EGG_HA_RATE));
+ if (pokemonSpecies.abilityHidden && (this._overrideHiddenAbility || sameSpeciesEggHACheck || gachaEggHACheck)) {
+ abilityIndex = 2;
+ }
- const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295));
+ // This function has way to many optional parameters
+ ret = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
+ ret.shiny = this._isShiny;
+ ret.variant = this._variantTier;
- for (let s = 0; s < ret.ivs.length; s++) {
- ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]);
- }
+ const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295));
+
+ for (let s = 0; s < ret.ivs.length; s++) {
+ ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[s]);
+ }
+ };
+
+ ret = ret!; // Tell TS compiler it's defined now
+ scene.executeWithSeedOffset(() => {
+ generatePlayerPokemonHelper(scene);
+ }, this._id, EGG_SEED.toString());
return ret;
}
diff --git a/src/data/move.ts b/src/data/move.ts
index d50dc7e2074..ddf043c554d 100644
--- a/src/data/move.ts
+++ b/src/data/move.ts
@@ -1,5 +1,4 @@
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
-import { BattleStat, getBattleStatName } from "./battle-stat";
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, TypeBoostTag } from "./battler-tags";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
@@ -13,7 +12,6 @@ import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilit
import { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, PokemonMoveAccuracyBoosterModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier";
import { BattlerIndex, BattleType } from "../battle";
-import { Stat } from "./pokemon-stat";
import { TerrainType } from "./terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../ui/command-ui-handler";
@@ -27,13 +25,14 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { MoveUsedEvent } from "#app/events/battle-scene";
+import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { MovePhase } from "#app/phases/move-phase";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
-import { StatChangePhase } from "#app/phases/stat-change-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
@@ -762,8 +761,7 @@ export default class Move implements Localizable {
.flat(),
);
for (const aura of fieldAuras) {
- // The only relevant values are `move` and the `power` holder
- aura.applyPreAttack(null, null, simulated, null, this, [power]);
+ aura.applyPreAttack(source, null, simulated, target, this, [power]);
}
const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField();
@@ -819,10 +817,10 @@ export class AttackMove extends Move {
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
if (attackScore) {
if (this.category === MoveCategory.PHYSICAL) {
- const atk = new Utils.IntegerHolder(user.getBattleStat(Stat.ATK, target));
+ const atk = new Utils.IntegerHolder(user.getEffectiveStat(Stat.ATK, target));
applyMoveAttrs(VariableAtkAttr, user, target, move, atk);
- if (atk.value > user.getBattleStat(Stat.SPATK, target)) {
- const statRatio = user.getBattleStat(Stat.SPATK, target) / atk.value;
+ if (atk.value > user.getEffectiveStat(Stat.SPATK, target)) {
+ const statRatio = user.getEffectiveStat(Stat.SPATK, target) / atk.value;
if (statRatio <= 0.75) {
attackScore *= 2;
} else if (statRatio <= 0.875) {
@@ -830,10 +828,10 @@ export class AttackMove extends Move {
}
}
} else {
- const spAtk = new Utils.IntegerHolder(user.getBattleStat(Stat.SPATK, target));
+ const spAtk = new Utils.IntegerHolder(user.getEffectiveStat(Stat.SPATK, target));
applyMoveAttrs(VariableAtkAttr, user, target, move, spAtk);
- if (spAtk.value > user.getBattleStat(Stat.ATK, target)) {
- const statRatio = user.getBattleStat(Stat.ATK, target) / spAtk.value;
+ if (spAtk.value > user.getEffectiveStat(Stat.ATK, target)) {
+ const statRatio = user.getEffectiveStat(Stat.ATK, target) / spAtk.value;
if (statRatio <= 0.75) {
attackScore *= 2;
} else if (statRatio <= 0.875) {
@@ -1100,9 +1098,9 @@ export class PreMoveMessageAttr extends MoveAttr {
*/
export class RespectAttackTypeImmunityAttr extends MoveAttr { }
-export class IgnoreOpponentStatChangesAttr extends MoveAttr {
+export class IgnoreOpponentStatStagesAttr extends MoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
- (args[0] as Utils.IntegerHolder).value = 0;
+ (args[0] as Utils.BooleanHolder).value = true;
return true;
}
@@ -1730,10 +1728,9 @@ export class HealOnAllyAttr extends HealAttr {
*/
export class HitHealAttr extends MoveEffectAttr {
private healRatio: number;
- private message: string;
- private healStat: Stat | null;
+ private healStat: EffectiveStat | null;
- constructor(healRatio?: number | null, healStat?: Stat) {
+ constructor(healRatio?: number | null, healStat?: EffectiveStat) {
super(true, MoveEffectTrigger.HIT);
this.healRatio = healRatio ?? 0.5;
@@ -1755,7 +1752,7 @@ export class HitHealAttr extends MoveEffectAttr {
const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false);
if (this.healStat !== null) {
// Strength Sap formula
- healAmount = target.getBattleStat(this.healStat);
+ healAmount = target.getEffectiveStat(this.healStat);
message = i18next.t("battle:drainMessage", {pokemonName: getPokemonNameWithAffix(target)});
} else {
// Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc.
@@ -1785,7 +1782,7 @@ export class HitHealAttr extends MoveEffectAttr {
*/
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
if (this.healStat) {
- const healAmount = target.getBattleStat(this.healStat);
+ const healAmount = target.getEffectiveStat(this.healStat);
return Math.floor(Math.max(0, (Math.min(1, (healAmount+user.hp)/user.getMaxHp() - 0.33))) / user.getHpRatio());
}
return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * (move.power / 4));
@@ -1953,6 +1950,13 @@ export class StatusEffectAttr extends MoveEffectAttr {
return false;
}
}
+
+ if (user !== target && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) {
+ if (move.category === MoveCategory.STATUS) {
+ user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target)}));
+ }
+ return false;
+ }
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect);
@@ -2509,14 +2513,14 @@ export class ElectroShotChargeAttr extends ChargeAttr {
const weatherType = user.scene.arena.weather?.weatherType;
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene) && (weatherType === WeatherType.RAIN || weatherType === WeatherType.HEAVY_RAIN)) {
// Apply the SPATK increase every call when used in the rain
- const statChangeAttr = new StatChangeAttr(BattleStat.SPATK, 1, true);
+ const statChangeAttr = new StatStageChangeAttr([ Stat.SPATK ], 1, true);
statChangeAttr.apply(user, target, move, args);
// After the SPATK is raised, execute the move resolution e.g. deal damage
resolve(false);
} else {
if (!this.statIncreaseApplied) {
// Apply the SPATK increase only if it hasn't been applied before e.g. on the first turn charge up animation
- const statChangeAttr = new StatChangeAttr(BattleStat.SPATK, 1, true);
+ const statChangeAttr = new StatStageChangeAttr([ Stat.SPATK ], 1, true);
statChangeAttr.apply(user, target, move, args);
// Set the flag to true so that on the following turn it doesn't raise SPATK a second time
this.statIncreaseApplied = true;
@@ -2564,18 +2568,16 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
}
}
-export class StatChangeAttr extends MoveEffectAttr {
+export class StatStageChangeAttr extends MoveEffectAttr {
public stats: BattleStat[];
- public levels: integer;
+ public stages: integer;
private condition: MoveConditionFunc | null;
private showMessage: boolean;
- constructor(stats: BattleStat | BattleStat[], levels: 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) {
super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly);
- this.stats = typeof(stats) === "number"
- ? [ stats as BattleStat ]
- : stats as BattleStat[];
- this.levels = levels;
+ this.stats = stats;
+ this.stages = stages;
this.condition = condition!; // TODO: is this bang correct?
this.showMessage = showMessage;
}
@@ -2587,8 +2589,8 @@ export class StatChangeAttr extends MoveEffectAttr {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
- const levels = this.getLevels(user);
- user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
+ const stages = this.getLevels(user);
+ user.scene.unshiftPhase(new StatStageChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage));
return true;
}
@@ -2596,7 +2598,7 @@ export class StatChangeAttr extends MoveEffectAttr {
}
getLevels(_user: Pokemon): integer {
- return this.levels;
+ return this.stages;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
@@ -2604,29 +2606,30 @@ export class StatChangeAttr extends MoveEffectAttr {
const moveLevels = this.getLevels(user);
for (const stat of this.stats) {
let levels = moveLevels;
+ const statStage = target.getStatStage(stat);
if (levels > 0) {
- levels = Math.min(target.summonData.battleStats[stat] + levels, 6) - target.summonData.battleStats[stat];
+ levels = Math.min(statStage + levels, 6) - statStage;
} else {
- levels = Math.max(target.summonData.battleStats[stat] + levels, -6) - target.summonData.battleStats[stat];
+ levels = Math.max(statStage + levels, -6) - statStage;
}
let noEffect = false;
switch (stat) {
- case BattleStat.ATK:
+ case Stat.ATK:
if (this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL);
}
break;
- case BattleStat.DEF:
+ case Stat.DEF:
if (!this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL);
}
break;
- case BattleStat.SPATK:
+ case Stat.SPATK:
if (this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL);
}
break;
- case BattleStat.SPDEF:
+ case Stat.SPDEF:
if (!this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL);
}
@@ -2641,18 +2644,16 @@ export class StatChangeAttr extends MoveEffectAttr {
}
}
-export class PostVictoryStatChangeAttr extends MoveAttr {
+export class PostVictoryStatStageChangeAttr extends MoveAttr {
private stats: BattleStat[];
- private levels: integer;
+ private stages: number;
private condition: MoveConditionFunc | null;
private showMessage: boolean;
- constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) {
+ constructor(stats: BattleStat[], stages: number, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) {
super();
- this.stats = typeof(stats) === "number"
- ? [ stats as BattleStat ]
- : stats as BattleStat[];
- this.levels = levels;
+ this.stats = stats;
+ this.stages = stages;
this.condition = condition!; // TODO: is this bang correct?
this.showMessage = showMessage;
}
@@ -2660,49 +2661,48 @@ export class PostVictoryStatChangeAttr extends MoveAttr {
if (this.condition && !this.condition(user, target, move)) {
return;
}
- const statChangeAttr = new StatChangeAttr(this.stats, this.levels, this.showMessage);
+ const statChangeAttr = new StatStageChangeAttr(this.stats, this.stages, this.showMessage);
statChangeAttr.apply(user, target, move);
}
}
-export class AcupressureStatChangeAttr extends MoveEffectAttr {
+export class AcupressureStatStageChangeAttr extends MoveEffectAttr {
constructor() {
super();
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise {
- let randStats = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD, BattleStat.ACC, BattleStat.EVA ];
- randStats = randStats.filter(s => target.summonData.battleStats[s] < 6);
+ const randStats = BATTLE_STATS.filter(s => target.getStatStage(s) < 6);
if (randStats.length > 0) {
- const boostStat = [randStats[Utils.randInt(randStats.length)]];
- user.scene.unshiftPhase(new StatChangePhase(user.scene, target.getBattlerIndex(), this.selfTarget, boostStat, 2));
+ const boostStat = [randStats[user.randSeedInt(randStats.length)]];
+ user.scene.unshiftPhase(new StatStageChangePhase(user.scene, target.getBattlerIndex(), this.selfTarget, boostStat, 2));
return true;
}
return false;
}
}
-export class GrowthStatChangeAttr extends StatChangeAttr {
+export class GrowthStatStageChangeAttr extends StatStageChangeAttr {
constructor() {
- super([ BattleStat.ATK, BattleStat.SPATK ], 1, true);
+ super([ Stat.ATK, Stat.SPATK ], 1, true);
}
getLevels(user: Pokemon): number {
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
const weatherType = user.scene.arena.weather?.weatherType;
if (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN) {
- return this.levels + 1;
+ return this.stages + 1;
}
}
- return this.levels;
+ return this.stages;
}
}
-export class CutHpStatBoostAttr extends StatChangeAttr {
+export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
private cutRatio: integer;
private messageCallback: ((user: Pokemon) => void) | undefined;
- constructor(stat: BattleStat | BattleStat[], levels: integer, cutRatio: integer, messageCallback?: ((user: Pokemon) => void) | undefined) {
+ constructor(stat: BattleStat[], levels: integer, cutRatio: integer, messageCallback?: ((user: Pokemon) => void) | undefined) {
super(stat, levels, true, null, true);
this.cutRatio = cutRatio;
@@ -2723,7 +2723,7 @@ export class CutHpStatBoostAttr extends StatChangeAttr {
}
getCondition(): MoveConditionFunc {
- return (user, target, move) => user.getHpRatio() > 1 / this.cutRatio && this.stats.some(s => user.summonData.battleStats[s] < 6);
+ return (user, _target, _move) => user.getHpRatio() > 1 / this.cutRatio && this.stats.some(s => user.getStatStage(s) < 6);
}
}
@@ -2733,9 +2733,11 @@ export class CopyStatsAttr extends MoveEffectAttr {
return false;
}
- for (let s = 0; s < target.summonData.battleStats.length; s++) {
- user.summonData.battleStats[s] = target.summonData.battleStats[s];
+ // Copy all stat stages
+ for (const s of BATTLE_STATS) {
+ user.setStatStage(s, target.getStatStage(s));
}
+
if (target.getTag(BattlerTagType.CRIT_BOOST)) {
user.addTag(BattlerTagType.CRIT_BOOST, 0, move.id);
} else {
@@ -2755,9 +2757,10 @@ export class InvertStatsAttr extends MoveEffectAttr {
return false;
}
- for (let s = 0; s < target.summonData.battleStats.length; s++) {
- target.summonData.battleStats[s] *= -1;
+ for (const s of BATTLE_STATS) {
+ target.setStatStage(s, -target.getStatStage(s));
}
+
target.updateInfo();
user.updateInfo();
@@ -2791,39 +2794,61 @@ export class ResetStatsAttr extends MoveEffectAttr {
}
resetStats(pokemon: Pokemon) {
- for (let s = 0; s < pokemon.summonData.battleStats.length; s++) {
- pokemon.summonData.battleStats[s] = 0;
+ for (const s of BATTLE_STATS) {
+ pokemon.setStatStage(s, 0);
}
pokemon.updateInfo();
}
}
/**
- * Attribute used for moves which swap the user and the target's stat changes.
+ * Attribute used for status moves, specifically Heart, Guard, and Power Swap,
+ * that swaps the user's and target's corresponding stat stages.
+ * @extends MoveEffectAttr
+ * @see {@linkcode apply}
*/
-export class SwapStatsAttr extends MoveEffectAttr {
+export class SwapStatStagesAttr extends MoveEffectAttr {
+ /** The stat stages to be swapped between the user and the target */
+ private stats: readonly BattleStat[];
+
+ constructor(stats: readonly BattleStat[]) {
+ super();
+
+ this.stats = stats;
+ }
+
/**
- * Swaps the user and the target's stat changes.
- * @param user Pokemon that used the move
- * @param target The target of the move
- * @param move Move with this attribute
+ * For all {@linkcode stats}, swaps the user's and target's corresponding stat
+ * stage.
+ * @param user the {@linkcode Pokemon} that used the move
+ * @param target the {@linkcode Pokemon} that the move was used on
+ * @param move N/A
* @param args N/A
- * @returns true if the function succeeds
+ * @returns true if attribute application succeeds
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any []): boolean {
- if (!super.apply(user, target, move, args)) {
- return false;
- } //Exits if the move can't apply
- let priorBoost : integer; //For storing a stat boost
- for (let s = 0; s < target.summonData.battleStats.length; s++) {
- priorBoost = user.summonData.battleStats[s]; //Store user stat boost
- user.summonData.battleStats[s] = target.summonData.battleStats[s]; //Applies target boost to self
- target.summonData.battleStats[s] = priorBoost; //Applies stored boost to target
+ if (super.apply(user, target, move, args)) {
+ for (const s of BATTLE_STATS) {
+ const temp = user.getStatStage(s);
+ user.setStatStage(s, target.getStatStage(s));
+ target.setStatStage(s, temp);
+ }
+
+ target.updateInfo();
+ user.updateInfo();
+
+ if (this.stats.length === 7) {
+ user.scene.queueMessage(i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) }));
+ } else if (this.stats.length === 2) {
+ user.scene.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", {
+ pokemonName: getPokemonNameWithAffix(user),
+ firstStat: i18next.t(getStatKey(this.stats[0])),
+ secondStat: i18next.t(getStatKey(this.stats[1]))
+ }));
+ }
+ return true;
}
- target.updateInfo();
- user.updateInfo();
- target.scene.queueMessage(i18next.t("moveTriggers:switchedStatChanges", {pokemonName: getPokemonNameWithAffix(user)}));
- return true;
+ return false;
}
}
@@ -3068,7 +3093,7 @@ export class WeightPowerAttr extends VariablePowerAttr {
**/
export class ElectroBallPowerAttr extends VariablePowerAttr {
/**
- * Move that deals more damage the faster {@linkcode BattleStat.SPD}
+ * Move that deals more damage the faster {@linkcode Stat.SPD}
* the user is compared to the target.
* @param user Pokemon that used the move
* @param target The target of the move
@@ -3079,7 +3104,7 @@ export class ElectroBallPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const power = args[0] as Utils.NumberHolder;
- const statRatio = target.getBattleStat(Stat.SPD) / user.getBattleStat(Stat.SPD);
+ const statRatio = target.getEffectiveStat(Stat.SPD) / user.getEffectiveStat(Stat.SPD);
const statThresholds = [ 0.25, 1 / 3, 0.5, 1, -1 ];
const statThresholdPowers = [ 150, 120, 80, 60, 40 ];
@@ -3103,7 +3128,7 @@ export class ElectroBallPowerAttr extends VariablePowerAttr {
**/
export class GyroBallPowerAttr extends VariablePowerAttr {
/**
- * Move that deals more damage the slower {@linkcode BattleStat.SPD}
+ * Move that deals more damage the slower {@linkcode Stat.SPD}
* the user is compared to the target.
* @param user Pokemon that used the move
* @param target The target of the move
@@ -3113,14 +3138,14 @@ export class GyroBallPowerAttr extends VariablePowerAttr {
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const power = args[0] as Utils.NumberHolder;
- const userSpeed = user.getBattleStat(Stat.SPD);
+ const userSpeed = user.getEffectiveStat(Stat.SPD);
if (userSpeed < 1) {
// Gen 6+ always have 1 base power
power.value = 1;
return true;
}
- power.value = Math.floor(Math.min(150, 25 * target.getBattleStat(Stat.SPD) / userSpeed + 1));
+ power.value = Math.floor(Math.min(150, 25 * target.getEffectiveStat(Stat.SPD) / userSpeed + 1));
return true;
}
}
@@ -3340,18 +3365,18 @@ export class HitCountPowerAttr extends VariablePowerAttr {
}
/**
- * Turning a once was (StatChangeCountPowerAttr) statement and making it available to call for any attribute.
- * @param {Pokemon} pokemon The pokemon that is being used to calculate the count of positive stats
- * @returns {number} Returns the amount of positive stats
+ * Tallies the number of positive stages for a given {@linkcode Pokemon}.
+ * @param pokemon The {@linkcode Pokemon} that is being used to calculate the count of positive stats
+ * @returns the amount of positive stats
*/
-const countPositiveStats = (pokemon: Pokemon): number => {
- return pokemon.summonData.battleStats.reduce((total, stat) => (stat && stat > 0) ? total + stat : total, 0);
+const countPositiveStatStages = (pokemon: Pokemon): number => {
+ return pokemon.getStatStages().reduce((total, stat) => (stat && stat > 0) ? total + stat : total, 0);
};
/**
- * Attribute that increases power based on the amount of positive stat increases.
+ * Attribute that increases power based on the amount of positive stat stage increases.
*/
-export class StatChangeCountPowerAttr extends VariablePowerAttr {
+export class PositiveStatStagePowerAttr extends VariablePowerAttr {
/**
* @param {Pokemon} user The pokemon that is being used to calculate the amount of positive stats
@@ -3361,9 +3386,9 @@ export class StatChangeCountPowerAttr extends VariablePowerAttr {
* @returns {boolean} Returns true if attribute is applied
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
- const positiveStats: number = countPositiveStats(user);
+ const positiveStatStages: number = countPositiveStatStages(user);
- (args[0] as Utils.NumberHolder).value += positiveStats * 20;
+ (args[0] as Utils.NumberHolder).value += positiveStatStages * 20;
return true;
}
}
@@ -3385,10 +3410,10 @@ export class PunishmentPowerAttr extends VariablePowerAttr {
* @returns Returns true if attribute is applied
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
- const positiveStats: number = countPositiveStats(target);
+ const positiveStatStages: number = countPositiveStatStages(target);
(args[0] as Utils.NumberHolder).value = Math.min(
this.PUNISHMENT_MAX_BASE_POWER,
- this.PUNISHMENT_MIN_BASE_POWER + positiveStats * 20
+ this.PUNISHMENT_MIN_BASE_POWER + positiveStatStages * 20
);
return true;
}
@@ -3608,7 +3633,7 @@ export class TargetAtkUserAtkAttr extends VariableAtkAttr {
super();
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
- (args[0] as Utils.IntegerHolder).value = target.getBattleStat(Stat.ATK, target);
+ (args[0] as Utils.IntegerHolder).value = target.getEffectiveStat(Stat.ATK, target);
return true;
}
}
@@ -3619,7 +3644,7 @@ export class DefAtkAttr extends VariableAtkAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
- (args[0] as Utils.IntegerHolder).value = user.getBattleStat(Stat.DEF, target);
+ (args[0] as Utils.IntegerHolder).value = user.getEffectiveStat(Stat.DEF, target);
return true;
}
}
@@ -3641,7 +3666,7 @@ export class DefDefAttr extends VariableDefAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
- (args[0] as Utils.IntegerHolder).value = target.getBattleStat(Stat.DEF, user);
+ (args[0] as Utils.IntegerHolder).value = target.getEffectiveStat(Stat.DEF, user);
return true;
}
}
@@ -3763,7 +3788,7 @@ export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder);
- if (user.getBattleStat(Stat.ATK, target, move) > user.getBattleStat(Stat.SPATK, target, move)) {
+ if (user.getEffectiveStat(Stat.ATK, target, move) > user.getEffectiveStat(Stat.SPATK, target, move)) {
category.value = MoveCategory.PHYSICAL;
return true;
}
@@ -3776,7 +3801,7 @@ export class TeraBlastCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder);
- if (user.isTerastallized() && user.getBattleStat(Stat.ATK, target, move) > user.getBattleStat(Stat.SPATK, target, move)) {
+ if (user.isTerastallized() && user.getEffectiveStat(Stat.ATK, target, move) > user.getEffectiveStat(Stat.SPATK, target, move)) {
category.value = MoveCategory.PHYSICAL;
return true;
}
@@ -3840,8 +3865,8 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.IntegerHolder);
- const atkRatio = user.getBattleStat(Stat.ATK, target, move) / target.getBattleStat(Stat.DEF, user, move);
- const specialRatio = user.getBattleStat(Stat.SPATK, target, move) / target.getBattleStat(Stat.SPDEF, user, move);
+ const atkRatio = user.getEffectiveStat(Stat.ATK, target, move) / target.getEffectiveStat(Stat.DEF, user, move);
+ const specialRatio = user.getEffectiveStat(Stat.SPATK, target, move) / target.getEffectiveStat(Stat.SPDEF, user, move);
// Shell Side Arm is much more complicated than it looks, this is a partial implementation to try to achieve something similar to the games
if (atkRatio > specialRatio) {
@@ -4598,8 +4623,8 @@ export class CurseAttr extends MoveEffectAttr {
target.addTag(BattlerTagType.CURSED, 0, move.id, user.id);
return true;
} else {
- user.scene.unshiftPhase(new StatChangePhase(user.scene, user.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF], 1));
- user.scene.unshiftPhase(new StatChangePhase(user.scene, user.getBattlerIndex(), true, [BattleStat.SPD], -1));
+ user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF], 1));
+ user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), true, [ Stat.SPD ], -1));
return true;
}
}
@@ -4659,6 +4684,17 @@ export class ConfuseAttr extends AddBattlerTagAttr {
constructor(selfTarget?: boolean) {
super(BattlerTagType.CONFUSED, selfTarget, false, 2, 5);
}
+
+ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+ if (!this.selfTarget && target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)) {
+ if (move.category === MoveCategory.STATUS) {
+ user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target)}));
+ }
+ return false;
+ }
+
+ return super.apply(user, target, move, args);
+ }
}
export class RechargeAttr extends AddBattlerTagAttr {
@@ -5145,8 +5181,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
if (this.user && this.batonPass) {
- const battleStatTotal = user.summonData.battleStats.reduce((bs: integer, total: integer) => total += bs, 0);
- ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(battleStatTotal), 10) / 10) * (battleStatTotal >= 0 ? 10 : -10));
+ const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
+ ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10));
}
return ret;
}
@@ -5888,9 +5924,9 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
target.summonData.ability = tempAbilityId;
user.scene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", {pokemonName: getPokemonNameWithAffix(user)}));
- // Swaps Forecast from Castform
+ // Swaps Forecast/Flower Gift from Castform/Cherrim
user.scene.arena.triggerWeatherBasedFormChangesToNormal();
- // Swaps Forecast to Castform (edge case)
+ // Swaps Forecast/Flower Gift to Castform/Cherrim (edge case)
user.scene.arena.triggerWeatherBasedFormChanges();
return true;
@@ -5971,8 +6007,17 @@ export class TransformAttr extends MoveEffectAttr {
user.summonData.ability = target.getAbility().id;
user.summonData.gender = target.getGender();
user.summonData.fusionGender = target.getFusionGender();
- user.summonData.stats = [ user.stats[Stat.HP] ].concat(target.stats.slice(1));
- user.summonData.battleStats = target.summonData.battleStats.slice(0);
+
+ // Copy all stats (except HP)
+ for (const s of EFFECTIVE_STATS) {
+ user.setStat(s, target.getStat(s, false), false);
+ }
+
+ // Copy all stat stages
+ for (const s of BATTLE_STATS) {
+ user.setStatStage(s, target.getStatStage(s));
+ }
+
user.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m?.moveId!, m?.ppUsed, m?.ppUp)); // TODO: is this bang correct?
user.summonData.types = target.getTypes();
@@ -5980,12 +6025,102 @@ export class TransformAttr extends MoveEffectAttr {
user.loadAssets(false).then(() => {
user.playAnim();
+ user.updateInfo();
resolve(true);
});
});
}
}
+/**
+ * Attribute used for status moves, namely Speed Swap,
+ * that swaps the user's and target's corresponding stats.
+ * @extends MoveEffectAttr
+ * @see {@linkcode apply}
+ */
+export class SwapStatAttr extends MoveEffectAttr {
+ /** The stat to be swapped between the user and the target */
+ private stat: EffectiveStat;
+
+ constructor(stat: EffectiveStat) {
+ super();
+
+ this.stat = stat;
+ }
+
+ /**
+ * Takes the average of the user's and target's corresponding current
+ * {@linkcode stat} values and sets that stat to the average for both
+ * temporarily.
+ * @param user the {@linkcode Pokemon} that used the move
+ * @param target the {@linkcode Pokemon} that the move was used on
+ * @param move N/A
+ * @param args N/A
+ * @returns true if attribute application succeeds
+ */
+ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+ if (super.apply(user, target, move, args)) {
+ const temp = user.getStat(this.stat, false);
+ user.setStat(this.stat, target.getStat(this.stat, false), false);
+ target.setStat(this.stat, temp, false);
+
+ user.scene.queueMessage(i18next.t("moveTriggers:switchedStat", {
+ pokemonName: getPokemonNameWithAffix(user),
+ stat: i18next.t(getStatKey(this.stat)),
+ }));
+
+ return true;
+ }
+ return false;
+ }
+}
+
+/**
+ * Attribute used for status moves, namely Power Split and Guard Split,
+ * that take the average of a user's and target's corresponding
+ * stats and assign that average back to each corresponding stat.
+ * @extends MoveEffectAttr
+ * @see {@linkcode apply}
+ */
+export class AverageStatsAttr extends MoveEffectAttr {
+ /** The stats to be averaged individually between the user and the target */
+ private stats: readonly EffectiveStat[];
+ private msgKey: string;
+
+ constructor(stats: readonly EffectiveStat[], msgKey: string) {
+ super();
+
+ this.stats = stats;
+ this.msgKey = msgKey;
+ }
+
+ /**
+ * Takes the average of the user's and target's corresponding {@linkcode stat}
+ * values and sets those stats to the corresponding average for both
+ * temporarily.
+ * @param user the {@linkcode Pokemon} that used the move
+ * @param target the {@linkcode Pokemon} that the move was used on
+ * @param move N/A
+ * @param args N/A
+ * @returns true if attribute application succeeds
+ */
+ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+ if (super.apply(user, target, move, args)) {
+ for (const s of this.stats) {
+ const avg = Math.floor((user.getStat(s, false) + target.getStat(s, false)) / 2);
+
+ user.setStat(s, avg, false);
+ target.setStat(s, avg, false);
+ }
+
+ user.scene.queueMessage(i18next.t(this.msgKey, { pokemonName: getPokemonNameWithAffix(user) }));
+
+ return true;
+ }
+ return false;
+ }
+}
+
export class DiscourageFrequentUseAttr extends MoveAttr {
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
const lastMoves = user.getLastXMoves(4);
@@ -6037,6 +6172,57 @@ export class DestinyBondAttr extends MoveEffectAttr {
}
}
+/**
+ * Attribute to apply a battler tag to the target if they have had their stats boosted this turn.
+ * @extends AddBattlerTagAttr
+ */
+export class AddBattlerTagIfBoostedAttr extends AddBattlerTagAttr {
+ constructor(tag: BattlerTagType) {
+ super(tag, false, false, 2, 5);
+ }
+
+ /**
+ * @param user {@linkcode Pokemon} using this move
+ * @param target {@linkcode Pokemon} target of this move
+ * @param move {@linkcode Move} being used
+ * @param {any[]} args N/A
+ * @returns true
+ */
+ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+ if (target.turnData.statStagesIncreased) {
+ super.apply(user, target, move, args);
+ }
+ return true;
+ }
+}
+
+/**
+ * Attribute to apply a status effect to the target if they have had their stats boosted this turn.
+ * @extends MoveEffectAttr
+ */
+export class StatusIfBoostedAttr extends MoveEffectAttr {
+ public effect: StatusEffect;
+
+ constructor(effect: StatusEffect) {
+ super(true, MoveEffectTrigger.HIT);
+ this.effect = effect;
+ }
+
+ /**
+ * @param user {@linkcode Pokemon} using this move
+ * @param target {@linkcode Pokemon} target of this move
+ * @param move {@linkcode Move} N/A
+ * @param {any[]} args N/A
+ * @returns true
+ */
+ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+ if (target.turnData.statStagesIncreased) {
+ target.trySetStatus(this.effect, true, user);
+ }
+ return true;
+ }
+}
+
export class LastResortAttr extends MoveAttr {
getCondition(): MoveConditionFunc {
return (user: Pokemon, target: Pokemon, move: Move) => {
@@ -6388,7 +6574,7 @@ export function initMoves() {
.ignoresVirtual()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new SelfStatusMove(Moves.SWORDS_DANCE, Type.NORMAL, -1, 20, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ATK, 2, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 2, true)
.danceMove(),
new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1)
.slicingMove(),
@@ -6424,7 +6610,7 @@ export function initMoves() {
new AttackMove(Moves.ROLLING_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, 30, 0, 1)
.attr(FlinchAttr),
new StatusMove(Moves.SAND_ATTACK, Type.GROUND, 100, 15, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.HEADBUTT, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 15, 30, 0, 1)
.attr(FlinchAttr),
new AttackMove(Moves.HORN_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 25, -1, 0, 1),
@@ -6452,7 +6638,7 @@ export function initMoves() {
.attr(RecoilAttr, false, 0.33)
.recklessMove(),
new StatusMove(Moves.TAIL_WHIP, Type.NORMAL, 100, 30, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.POISON_STING, Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, 30, 0, 1)
.attr(StatusEffectAttr, StatusEffect.POISON)
@@ -6465,13 +6651,13 @@ export function initMoves() {
.attr(MultiHitAttr)
.makesContact(false),
new StatusMove(Moves.LEER, Type.NORMAL, 100, 30, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BITE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 1)
.attr(FlinchAttr)
.bitingMove(),
new StatusMove(Moves.GROWL, Type.NORMAL, 100, 40, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ATK, -1)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1)
@@ -6490,7 +6676,7 @@ export function initMoves() {
.attr(DisableMoveAttr)
.condition(failOnMaxCondition),
new AttackMove(Moves.ACID, Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.EMBER, Type.FIRE, MoveCategory.SPECIAL, 40, 100, 25, 10, 0, 1)
.attr(StatusEffectAttr, StatusEffect.BURN),
@@ -6515,9 +6701,9 @@ export function initMoves() {
new AttackMove(Moves.PSYBEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)
.attr(ConfuseAttr),
new AttackMove(Moves.BUBBLE_BEAM, Type.WATER, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPD, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1),
new AttackMove(Moves.AURORA_BEAM, Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new AttackMove(Moves.HYPER_BEAM, Type.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 1)
.attr(RechargeAttr),
new AttackMove(Moves.PECK, Type.FLYING, MoveCategory.PHYSICAL, 35, 100, 35, -1, 0, 1),
@@ -6544,7 +6730,7 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.SEEDED)
.condition((user, target, move) => !target.getTag(BattlerTagType.SEEDED) && !target.isOfType(Type.GRASS)),
new SelfStatusMove(Moves.GROWTH, Type.NORMAL, -1, 20, -1, 0, 1)
- .attr(GrowthStatChangeAttr),
+ .attr(GrowthStatStageChangeAttr),
new AttackMove(Moves.RAZOR_LEAF, Type.GRASS, MoveCategory.PHYSICAL, 55, 95, 25, -1, 0, 1)
.attr(HighCritAttr)
.makesContact(false)
@@ -6571,7 +6757,7 @@ export function initMoves() {
.danceMove()
.target(MoveTarget.RANDOM_NEAR_ENEMY),
new StatusMove(Moves.STRING_SHOT, Type.BUG, 95, 40, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPD, -2)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -2)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_RAGE, Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1)
.attr(FixedDamageAttr, 40),
@@ -6608,13 +6794,13 @@ export function initMoves() {
new AttackMove(Moves.CONFUSION, Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, 10, 0, 1)
.attr(ConfuseAttr),
new AttackMove(Moves.PSYCHIC, Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1),
new StatusMove(Moves.HYPNOSIS, Type.PSYCHIC, 60, 20, -1, 0, 1)
.attr(StatusEffectAttr, StatusEffect.SLEEP),
new SelfStatusMove(Moves.MEDITATE, Type.PSYCHIC, -1, 40, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ATK, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true),
new SelfStatusMove(Moves.AGILITY, Type.PSYCHIC, -1, 30, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPD, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1),
new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1)
.partial(),
@@ -6627,28 +6813,28 @@ export function initMoves() {
.attr(MovesetCopyMoveAttr)
.ignoresVirtual(),
new StatusMove(Moves.SCREECH, Type.NORMAL, 85, 40, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, -2)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -2)
.soundBased(),
new SelfStatusMove(Moves.DOUBLE_TEAM, Type.NORMAL, -1, 15, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.EVA, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.EVA ], 1, true),
new SelfStatusMove(Moves.RECOVER, Type.NORMAL, -1, 5, -1, 0, 1)
.attr(HealAttr, 0.5)
.triageMove(),
new SelfStatusMove(Moves.HARDEN, Type.NORMAL, -1, 30, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
new SelfStatusMove(Moves.MINIMIZE, Type.NORMAL, -1, 10, -1, 0, 1)
.attr(AddBattlerTagAttr, BattlerTagType.MINIMIZED, true, false)
- .attr(StatChangeAttr, BattleStat.EVA, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.EVA ], 2, true),
new StatusMove(Moves.SMOKESCREEN, Type.NORMAL, 100, 20, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new StatusMove(Moves.CONFUSE_RAY, Type.GHOST, 100, 10, -1, 0, 1)
.attr(ConfuseAttr),
new SelfStatusMove(Moves.WITHDRAW, Type.WATER, -1, 40, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
new SelfStatusMove(Moves.DEFENSE_CURL, Type.NORMAL, -1, 40, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
new SelfStatusMove(Moves.BARRIER, Type.PSYCHIC, -1, 20, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
new StatusMove(Moves.LIGHT_SCREEN, Type.PSYCHIC, -1, 30, -1, 0, 1)
.attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, true)
.target(MoveTarget.USER_SIDE),
@@ -6696,17 +6882,17 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1)
.attr(ChargeAttr, ChargeAnim.SKULL_BASH_CHARGING, i18next.t("moveTriggers:loweredItsHead", {pokemonName: "{USER}"}), null, true)
- .attr(StatChangeAttr, BattleStat.DEF, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true)
.ignoresVirtual(),
new AttackMove(Moves.SPIKE_CANNON, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, 0, 1)
.attr(MultiHitAttr)
.makesContact(false),
new AttackMove(Moves.CONSTRICT, Type.NORMAL, MoveCategory.PHYSICAL, 10, 100, 35, 10, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPD, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1),
new SelfStatusMove(Moves.AMNESIA, Type.PSYCHIC, -1, 20, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPDEF, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], 2, true),
new StatusMove(Moves.KINESIS, Type.PSYCHIC, 80, 15, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new SelfStatusMove(Moves.SOFT_BOILED, Type.NORMAL, -1, 5, -1, 0, 1)
.attr(HealAttr, 0.5)
.triageMove(),
@@ -6743,7 +6929,7 @@ export function initMoves() {
.attr(TransformAttr)
.ignoresProtect(),
new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DIZZY_PUNCH, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, 20, 0, 1)
.attr(ConfuseAttr)
@@ -6752,13 +6938,13 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.powderMove(),
new StatusMove(Moves.FLASH, Type.NORMAL, 100, 20, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.PSYWAVE, Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
.attr(RandomLevelDamageAttr),
new SelfStatusMove(Moves.SPLASH, Type.NORMAL, -1, 40, -1, 0, 1)
.condition(failOnGravityCondition),
new SelfStatusMove(Moves.ACID_ARMOR, Type.POISON, -1, 20, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.DEF, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
new AttackMove(Moves.CRABHAMMER, Type.WATER, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 1)
.attr(HighCritAttr),
new AttackMove(Moves.EXPLOSION, Type.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, -1, 0, 1)
@@ -6784,7 +6970,7 @@ export function initMoves() {
.attr(FlinchAttr)
.bitingMove(),
new SelfStatusMove(Moves.SHARPEN, Type.NORMAL, -1, 30, -1, 0, 1)
- .attr(StatChangeAttr, BattleStat.ATK, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true),
new SelfStatusMove(Moves.CONVERSION, Type.NORMAL, -1, 30, -1, 0, 1)
.attr(FirstMoveTypeAttr),
new AttackMove(Moves.TRI_ATTACK, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, 20, 0, 1)
@@ -6839,7 +7025,7 @@ export function initMoves() {
.windMove()
.attr(HighCritAttr),
new StatusMove(Moves.COTTON_SPORE, Type.GRASS, 100, 40, -1, 0, 2)
- .attr(StatChangeAttr, BattleStat.SPD, -2)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -2)
.powderMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.REVERSAL, Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2)
@@ -6854,21 +7040,21 @@ export function initMoves() {
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
.punchingMove(),
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
- .attr(StatChangeAttr, BattleStat.SPD, -2),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -2),
new AttackMove(Moves.FEINT_ATTACK, Type.DARK, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 2),
new StatusMove(Moves.SWEET_KISS, Type.FAIRY, 75, 10, -1, 0, 2)
.attr(ConfuseAttr),
new SelfStatusMove(Moves.BELLY_DRUM, Type.NORMAL, -1, 10, -1, 0, 2)
- .attr(CutHpStatBoostAttr, [BattleStat.ATK], 12, 2, (user) => {
- user.scene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", {pokemonName: getPokemonNameWithAffix(user), statName: getBattleStatName(BattleStat.ATK)}));
+ .attr(CutHpStatStageBoostAttr, [ Stat.ATK ], 12, 2, (user) => {
+ user.scene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) }));
}),
new AttackMove(Moves.SLUDGE_BOMB, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2)
.attr(StatusEffectAttr, StatusEffect.POISON)
.ballBombMove(),
new AttackMove(Moves.MUD_SLAP, Type.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 100, 0, 2)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.OCTAZOOKA, Type.WATER, MoveCategory.SPECIAL, 65, 85, 10, 50, 0, 2)
- .attr(StatChangeAttr, BattleStat.ACC, -1)
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1)
.ballBombMove(),
new StatusMove(Moves.SPIKES, Type.GROUND, -1, 20, -1, 0, 2)
.attr(AddArenaTrapTagAttr, ArenaTagType.SPIKES)
@@ -6897,7 +7083,7 @@ export function initMoves() {
.condition(failOnBossCondition)
.target(MoveTarget.ALL),
new AttackMove(Moves.ICY_WIND, Type.ICE, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 2)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
@@ -6921,13 +7107,13 @@ export function initMoves() {
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
.attr(ProtectAttr, BattlerTagType.ENDURING),
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
- .attr(StatChangeAttr, BattleStat.ATK, -2),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -2),
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
.attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL),
new AttackMove(Moves.FALSE_SWIPE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2)
.attr(SurviveDamageAttr),
new StatusMove(Moves.SWAGGER, Type.NORMAL, 85, 15, -1, 0, 2)
- .attr(StatChangeAttr, BattleStat.ATK, 2)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 2)
.attr(ConfuseAttr),
new SelfStatusMove(Moves.MILK_DRINK, Type.NORMAL, -1, 5, -1, 0, 2)
.attr(HealAttr, 0.5)
@@ -6938,7 +7124,7 @@ export function initMoves() {
.attr(ConsecutiveUseDoublePowerAttr, 3, true)
.slicingMove(),
new AttackMove(Moves.STEEL_WING, Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2)
- .attr(StatChangeAttr, BattleStat.DEF, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
new StatusMove(Moves.MEAN_LOOK, Type.NORMAL, -1, 5, -1, 0, 2)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
new StatusMove(Moves.ATTRACT, Type.NORMAL, 100, 15, -1, 0, 2)
@@ -6963,7 +7149,7 @@ export function initMoves() {
.attr(FriendshipPowerAttr, true),
new StatusMove(Moves.SAFEGUARD, Type.NORMAL, -1, 25, -1, 0, 2)
.target(MoveTarget.USER_SIDE)
- .unimplemented(),
+ .attr(AddArenaTagAttr, ArenaTagType.SAFEGUARD, 5, true, true),
new StatusMove(Moves.PAIN_SPLIT, Type.NORMAL, -1, 20, -1, 0, 2)
.attr(HpSplitAttr)
.condition(failOnBossCondition),
@@ -6992,7 +7178,7 @@ export function initMoves() {
new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2)
.partial(),
new AttackMove(Moves.RAPID_SPIN, Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2)
- .attr(StatChangeAttr, BattleStat.SPD, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
.attr(RemoveBattlerTagAttr, [
BattlerTagType.BIND,
BattlerTagType.WRAP,
@@ -7008,12 +7194,12 @@ export function initMoves() {
], true)
.attr(RemoveArenaTrapAttr),
new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2)
- .attr(StatChangeAttr, BattleStat.EVA, -2)
+ .attr(StatStageChangeAttr, [ Stat.EVA ], -2)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.IRON_TAIL, Type.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2)
- .attr(StatChangeAttr, BattleStat.DEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.METAL_CLAW, Type.STEEL, MoveCategory.PHYSICAL, 50, 95, 35, 10, 0, 2)
- .attr(StatChangeAttr, BattleStat.ATK, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true),
new AttackMove(Moves.VITAL_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 70, -1, 10, -1, -1, 2),
new SelfStatusMove(Moves.MORNING_SUN, Type.NORMAL, -1, 5, -1, 0, 2)
.attr(PlantHealAttr)
@@ -7040,7 +7226,7 @@ export function initMoves() {
.attr(WeatherChangeAttr, WeatherType.SUNNY)
.target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.CRUNCH, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 2)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.bitingMove(),
new AttackMove(Moves.MIRROR_COAT, Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 20, -1, -5, 2)
.attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.SPECIAL, 2)
@@ -7049,15 +7235,15 @@ export function initMoves() {
.attr(CopyStatsAttr),
new AttackMove(Moves.EXTREME_SPEED, Type.NORMAL, MoveCategory.PHYSICAL, 80, 100, 5, -1, 2, 2),
new AttackMove(Moves.ANCIENT_POWER, Type.ROCK, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 2)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true),
new AttackMove(Moves.SHADOW_BALL, Type.GHOST, MoveCategory.SPECIAL, 80, 100, 15, 20, 0, 2)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.ballBombMove(),
new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2)
.partial()
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", {pokemonName: "{USER}"})),
new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
- .attr(StatChangeAttr, BattleStat.DEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
@@ -7096,13 +7282,13 @@ export function initMoves() {
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
.unimplemented(),
new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPATK, 1)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 1)
.attr(ConfuseAttr),
new StatusMove(Moves.WILL_O_WISP, Type.FIRE, 85, 15, -1, 0, 3)
.attr(StatusEffectAttr, StatusEffect.BURN),
new StatusMove(Moves.MEMENTO, Type.DARK, 100, 10, -1, 0, 3)
.attr(SacrificialAttrOnHit)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -2),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -2),
new AttackMove(Moves.FACADE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 3)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.status
&& (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1)
@@ -7121,7 +7307,7 @@ export function initMoves() {
.attr(NaturePowerAttr)
.ignoresVirtual(),
new SelfStatusMove(Moves.CHARGE, Type.ELECTRIC, -1, 20, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPDEF, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false),
new StatusMove(Moves.TAUNT, Type.DARK, 100, 20, -1, 0, 3)
.unimplemented(),
@@ -7141,7 +7327,7 @@ export function initMoves() {
new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true),
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1, true),
new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3)
.unimplemented(),
new SelfStatusMove(Moves.RECYCLE, Type.NORMAL, -1, 10, -1, 0, 3)
@@ -7152,7 +7338,7 @@ export function initMoves() {
.attr(RemoveScreensAttr),
new StatusMove(Moves.YAWN, Type.NORMAL, -1, 10, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true)
- .condition((user, target, move) => !target.status),
+ .condition((user, target, move) => !target.status && !target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)),
new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferrable).length > 0 ? 1.5 : 1)
.attr(RemoveHeldItemAttr, false),
@@ -7185,14 +7371,14 @@ export function initMoves() {
new SelfStatusMove(Moves.CAMOUFLAGE, Type.NORMAL, -1, 20, -1, 0, 3)
.attr(CopyBiomeTypeAttr),
new SelfStatusMove(Moves.TAIL_GLOW, Type.BUG, -1, 20, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPATK, 3, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 3, true),
new AttackMove(Moves.LUSTER_PURGE, Type.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1),
new AttackMove(Moves.MIST_BALL, Type.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPATK, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
.ballBombMove(),
new StatusMove(Moves.FEATHER_DANCE, Type.FLYING, 100, 15, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.ATK, -2)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -2)
.danceMove(),
new StatusMove(Moves.TEETER_DANCE, Type.NORMAL, 100, 20, -1, 0, 3)
.attr(ConfuseAttr)
@@ -7219,13 +7405,13 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.TOXIC)
.bitingMove(),
new AttackMove(Moves.CRUSH_CLAW, Type.NORMAL, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 3)
- .attr(StatChangeAttr, BattleStat.DEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.BLAST_BURN, Type.FIRE, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3)
.attr(RechargeAttr),
new AttackMove(Moves.HYDRO_CANNON, Type.WATER, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3)
.attr(RechargeAttr),
new AttackMove(Moves.METEOR_MASH, Type.STEEL, MoveCategory.PHYSICAL, 90, 90, 10, 20, 0, 3)
- .attr(StatChangeAttr, BattleStat.ATK, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true)
.punchingMove(),
new AttackMove(Moves.ASTONISH, Type.GHOST, MoveCategory.PHYSICAL, 30, 100, 15, 30, 0, 3)
.attr(FlinchAttr),
@@ -7237,33 +7423,33 @@ export function initMoves() {
.attr(PartyStatusCureAttr, i18next.t("moveTriggers:soothingAromaWaftedThroughArea"), Abilities.SAP_SIPPER)
.target(MoveTarget.PARTY),
new StatusMove(Moves.FAKE_TEARS, Type.DARK, 100, 20, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPDEF, -2),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
new AttackMove(Moves.AIR_CUTTER, Type.FLYING, MoveCategory.SPECIAL, 60, 95, 25, -1, 0, 3)
.attr(HighCritAttr)
.slicingMove()
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.OVERHEAT, Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPATK, -2, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE),
new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3)
.attr(ExposedMoveAttr, BattlerTagType.IGNORE_GHOST),
new AttackMove(Moves.ROCK_TOMB, Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.makesContact(false),
new AttackMove(Moves.SILVER_WIND, Type.BUG, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.windMove(),
new StatusMove(Moves.METAL_SOUND, Type.STEEL, 85, 40, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPDEF, -2)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2)
.soundBased(),
new StatusMove(Moves.GRASS_WHISTLE, Type.GRASS, 55, 15, -1, 0, 3)
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.soundBased(),
new StatusMove(Moves.TICKLE, Type.NORMAL, 100, 20, -1, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1),
new SelfStatusMove(Moves.COSMIC_POWER, Type.PSYCHIC, -1, 20, -1, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, true),
new AttackMove(Moves.WATER_SPOUT, Type.WATER, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 3)
.attr(HpPowerAttr)
.target(MoveTarget.ALL_NEAR_ENEMIES),
@@ -7284,7 +7470,7 @@ export function initMoves() {
.attr(OneHitKOAttr)
.attr(SheerColdAccuracyAttr),
new AttackMove(Moves.MUDDY_WATER, Type.WATER, MoveCategory.SPECIAL, 90, 85, 10, 30, 0, 3)
- .attr(StatChangeAttr, BattleStat.ACC, -1)
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BULLET_SEED, Type.GRASS, MoveCategory.PHYSICAL, 25, 100, 30, -1, 0, 3)
.attr(MultiHitAttr)
@@ -7296,25 +7482,25 @@ export function initMoves() {
.attr(MultiHitAttr)
.makesContact(false),
new SelfStatusMove(Moves.IRON_DEFENSE, Type.STEEL, -1, 15, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.DEF, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
new StatusMove(Moves.BLOCK, Type.NORMAL, -1, 5, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
new StatusMove(Moves.HOWL, Type.NORMAL, -1, 40, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.ATK, 1)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1)
.soundBased()
.target(MoveTarget.USER_AND_ALLIES),
new AttackMove(Moves.DRAGON_CLAW, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 3),
new AttackMove(Moves.FRENZY_PLANT, Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3)
.attr(RechargeAttr),
new SelfStatusMove(Moves.BULK_UP, Type.FIGHTING, -1, 20, -1, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1, true),
new AttackMove(Moves.BOUNCE, Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, 30, 0, 3)
.attr(ChargeAttr, ChargeAnim.BOUNCE_CHARGING, i18next.t("moveTriggers:sprangUp", {pokemonName: "{USER}"}), BattlerTagType.FLYING)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
.condition(failOnGravityCondition)
.ignoresVirtual(),
new AttackMove(Moves.MUD_SHOT, Type.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPD, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1),
new AttackMove(Moves.POISON_TAIL, Type.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 10, 0, 3)
.attr(HighCritAttr)
.attr(StatusEffectAttr, StatusEffect.POISON),
@@ -7329,12 +7515,12 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.WATER_SPORT, 5)
.target(MoveTarget.BOTH_SIDES),
new SelfStatusMove(Moves.CALM_MIND, Type.PSYCHIC, -1, 20, -1, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF ], 1, true),
new AttackMove(Moves.LEAF_BLADE, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 3)
.attr(HighCritAttr)
.slicingMove(),
new SelfStatusMove(Moves.DRAGON_DANCE, Type.DRAGON, -1, 20, -1, 0, 3)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPD ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.ROCK_BLAST, Type.ROCK, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 3)
.attr(MultiHitAttr)
@@ -7348,7 +7534,7 @@ export function initMoves() {
.partial()
.attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", {pokemonName: "{USER}"})),
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3)
- .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true),
new SelfStatusMove(Moves.ROOST, Type.FLYING, -1, 5, -1, 0, 4)
.attr(HealAttr, 0.5)
.attr(AddBattlerTagAttr, BattlerTagType.ROOSTED, true, false)
@@ -7362,7 +7548,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => targetSleptOrComatoseCondition(user, target, move) ? 2 : 1)
.attr(HealStatusEffectAttr, false, StatusEffect.SLEEP),
new AttackMove(Moves.HAMMER_ARM, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPD, -1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1, true)
.punchingMove(),
new AttackMove(Moves.GYRO_BALL, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
.attr(GyroBallPowerAttr)
@@ -7387,7 +7573,7 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.TAILWIND, 4, true)
.target(MoveTarget.USER_SIDE),
new StatusMove(Moves.ACUPRESSURE, Type.NORMAL, -1, 30, -1, 0, 4)
- .attr(AcupressureStatChangeAttr)
+ .attr(AcupressureStatStageChangeAttr)
.target(MoveTarget.USER_OR_NEAR_ALLY),
new AttackMove(Moves.METAL_BURST, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 4)
.attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5)
@@ -7397,7 +7583,7 @@ export function initMoves() {
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
.attr(ForceSwitchOutAttr, true, false),
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.BALL ? 2 : 1),
new AttackMove(Moves.ASSURANCE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4)
@@ -7441,9 +7627,9 @@ export function initMoves() {
.attr(CopyMoveAttr)
.ignoresVirtual(),
new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
- .unimplemented(),
+ .attr(SwapStatStagesAttr, [ Stat.ATK, Stat.SPATK ]),
new StatusMove(Moves.GUARD_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
- .unimplemented(),
+ .attr(SwapStatStagesAttr, [ Stat.DEF, Stat.SPDEF ]),
new AttackMove(Moves.PUNISHMENT, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
.makesContact(true)
.attr(PunishmentPowerAttr),
@@ -7457,7 +7643,7 @@ export function initMoves() {
.attr(AddArenaTrapTagAttr, ArenaTagType.TOXIC_SPIKES)
.target(MoveTarget.ENEMY_SIDE),
new StatusMove(Moves.HEART_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 4)
- .attr(SwapStatsAttr),
+ .attr(SwapStatStagesAttr, BATTLE_STATS),
new SelfStatusMove(Moves.AQUA_RING, Type.WATER, -1, 20, -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true),
new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4)
@@ -7474,7 +7660,7 @@ export function initMoves() {
.pulseMove()
.ballBombMove(),
new SelfStatusMove(Moves.ROCK_POLISH, Type.ROCK, -1, 20, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPD, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
new AttackMove(Moves.POISON_JAB, Type.POISON, MoveCategory.PHYSICAL, 80, 100, 20, 30, 0, 4)
.attr(StatusEffectAttr, StatusEffect.POISON),
new AttackMove(Moves.DARK_PULSE, Type.DARK, MoveCategory.SPECIAL, 80, 100, 15, 20, 0, 4)
@@ -7493,7 +7679,7 @@ export function initMoves() {
new AttackMove(Moves.X_SCISSOR, Type.BUG, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 4)
.slicingMove(),
new AttackMove(Moves.BUG_BUZZ, Type.BUG, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.soundBased(),
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
.pulseMove(),
@@ -7508,22 +7694,22 @@ export function initMoves() {
.triageMove(),
new AttackMove(Moves.VACUUM_WAVE, Type.FIGHTING, MoveCategory.SPECIAL, 40, 100, 30, -1, 1, 4),
new AttackMove(Moves.FOCUS_BLAST, Type.FIGHTING, MoveCategory.SPECIAL, 120, 70, 5, 10, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.ballBombMove(),
new AttackMove(Moves.ENERGY_BALL, Type.GRASS, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.ballBombMove(),
new AttackMove(Moves.BRAVE_BIRD, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 4)
.attr(RecoilAttr, false, 0.33)
.recklessMove(),
new AttackMove(Moves.EARTH_POWER, Type.GROUND, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1),
new StatusMove(Moves.SWITCHEROO, Type.DARK, 100, 10, -1, 0, 4)
.unimplemented(),
new AttackMove(Moves.GIGA_IMPACT, Type.NORMAL, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
.attr(RechargeAttr),
new SelfStatusMove(Moves.NASTY_PLOT, Type.DARK, -1, 20, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPATK, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 2, true),
new AttackMove(Moves.BULLET_PUNCH, Type.STEEL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4)
.punchingMove(),
new AttackMove(Moves.AVALANCHE, Type.ICE, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 4)
@@ -7546,7 +7732,7 @@ export function initMoves() {
.bitingMove(),
new AttackMove(Moves.SHADOW_SNEAK, Type.GHOST, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4),
new AttackMove(Moves.MUD_BOMB, Type.GROUND, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4)
- .attr(StatChangeAttr, BattleStat.ACC, -1)
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1)
.ballBombMove(),
new AttackMove(Moves.PSYCHO_CUT, Type.PSYCHIC, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
.attr(HighCritAttr)
@@ -7555,13 +7741,13 @@ export function initMoves() {
new AttackMove(Moves.ZEN_HEADBUTT, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 20, 0, 4)
.attr(FlinchAttr),
new AttackMove(Moves.MIRROR_SHOT, Type.STEEL, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.FLASH_CANNON, Type.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 10, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1),
new AttackMove(Moves.ROCK_CLIMB, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 20, 0, 4)
.attr(ConfuseAttr),
new StatusMove(Moves.DEFOG, Type.FLYING, -1, 15, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.EVA, -1)
+ .attr(StatStageChangeAttr, [ Stat.EVA ], -1)
.attr(ClearWeatherAttr, WeatherType.FOG)
.attr(ClearTerrainAttr)
.attr(RemoveScreensAttr, false)
@@ -7571,7 +7757,7 @@ export function initMoves() {
.ignoresProtect()
.target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.DRACO_METEOR, Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true),
new AttackMove(Moves.DISCHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
.target(MoveTarget.ALL_NEAR_OTHERS),
@@ -7579,7 +7765,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.LEAF_STORM, Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true),
new AttackMove(Moves.POWER_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 4),
new AttackMove(Moves.ROCK_WRECKER, Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
.attr(RechargeAttr)
@@ -7601,7 +7787,7 @@ export function initMoves() {
.attr(HighCritAttr)
.makesContact(false),
new StatusMove(Moves.CAPTIVATE, Type.NORMAL, 100, 20, -1, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPATK, -2)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2)
.condition((user, target, move) => target.isOppositeGender(user))
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.STEALTH_ROCK, Type.ROCK, -1, 20, -1, 0, 4)
@@ -7619,7 +7805,7 @@ export function initMoves() {
new AttackMove(Moves.BUG_BITE, Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4)
.attr(StealEatBerryAttr),
new AttackMove(Moves.CHARGE_BEAM, Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 70, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPATK, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true),
new AttackMove(Moves.WOOD_HAMMER, Type.GRASS, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 4)
.attr(RecoilAttr, false, 0.33)
.recklessMove(),
@@ -7628,7 +7814,7 @@ export function initMoves() {
.attr(HighCritAttr)
.makesContact(false),
new SelfStatusMove(Moves.DEFEND_ORDER, Type.BUG, -1, 10, -1, 0, 4)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, true),
new SelfStatusMove(Moves.HEAL_ORDER, Type.BUG, -1, 10, -1, 0, 4)
.attr(HealAttr, 0.5)
.triageMove(),
@@ -7654,23 +7840,23 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.SEED_FLARE, Type.GRASS, MoveCategory.SPECIAL, 120, 85, 5, 40, 0, 4)
- .attr(StatChangeAttr, BattleStat.SPDEF, -2),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
new AttackMove(Moves.OMINOUS_WIND, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 4)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.windMove(),
new AttackMove(Moves.SHADOW_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
.attr(ChargeAttr, ChargeAnim.SHADOW_FORCE_CHARGING, i18next.t("moveTriggers:vanishedInstantly", {pokemonName: "{USER}"}), BattlerTagType.HIDDEN)
.ignoresProtect()
.ignoresVirtual(),
new SelfStatusMove(Moves.HONE_CLAWS, Type.DARK, -1, 15, -1, 0, 5)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.ACC ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true),
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
.target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true),
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
- .unimplemented(),
+ .attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"),
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
- .unimplemented(),
+ .attr(AverageStatsAttr, [ Stat.ATK, Stat.SPATK ], "moveTriggers:sharedPower"),
new StatusMove(Moves.WONDER_ROOM, Type.PSYCHIC, -1, 10, -1, 0, 5)
.ignoresProtect()
.target(MoveTarget.BOTH_SIDES)
@@ -7680,7 +7866,7 @@ export function initMoves() {
new AttackMove(Moves.VENOSHOCK, Type.POISON, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1),
new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPD, 2, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true)
.partial(),
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
.powderMove()
@@ -7706,7 +7892,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.POISON)
.target(MoveTarget.ALL_NEAR_OTHERS),
new SelfStatusMove(Moves.QUIVER_DANCE, Type.BUG, -1, 20, -1, 0, 5)
- .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
.attr(MinimizeAccuracyAttr)
@@ -7723,13 +7909,13 @@ export function initMoves() {
new StatusMove(Moves.SOAK, Type.WATER, 100, 20, -1, 0, 5)
.attr(ChangeTypeAttr, Type.WATER),
new AttackMove(Moves.FLAME_CHARGE, Type.FIRE, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPD, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true),
new SelfStatusMove(Moves.COIL, Type.POISON, -1, 20, -1, 0, 5)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.ACC ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.ACC ], 1, true),
new AttackMove(Moves.LOW_SWEEP, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 20, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPD, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1),
new AttackMove(Moves.ACID_SPRAY, Type.POISON, MoveCategory.SPECIAL, 40, 100, 20, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPDEF, -2)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2)
.ballBombMove(),
new AttackMove(Moves.FOUL_PLAY, Type.DARK, MoveCategory.PHYSICAL, 95, 100, 15, -1, 0, 5)
.attr(TargetAtkUserAtkAttr),
@@ -7747,11 +7933,11 @@ export function initMoves() {
.attr(ConsecutiveUseMultiBasePowerAttr, 5, false)
.soundBased(),
new AttackMove(Moves.CHIP_AWAY, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 5)
- .attr(IgnoreOpponentStatChangesAttr),
+ .attr(IgnoreOpponentStatStagesAttr),
new AttackMove(Moves.CLEAR_SMOG, Type.POISON, MoveCategory.SPECIAL, 50, -1, 15, -1, 0, 5)
.attr(ResetStatsAttr, false),
new AttackMove(Moves.STORED_POWER, Type.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, -1, 0, 5)
- .attr(StatChangeCountPowerAttr),
+ .attr(PositiveStatStagePowerAttr),
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
.target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true),
@@ -7763,8 +7949,8 @@ export function initMoves() {
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
.attr(StatusEffectAttr, StatusEffect.BURN),
new SelfStatusMove(Moves.SHELL_SMASH, Type.NORMAL, -1, 15, -1, 0, 5)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 2, true)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
new StatusMove(Moves.HEAL_PULSE, Type.PSYCHIC, -1, 10, -1, 0, 5)
.attr(HealAttr, 0.5, false, false)
.pulseMove()
@@ -7778,8 +7964,8 @@ export function initMoves() {
.condition(failOnGravityCondition)
.ignoresVirtual(),
new SelfStatusMove(Moves.SHIFT_GEAR, Type.STEEL, -1, 10, -1, 0, 5)
- .attr(StatChangeAttr, BattleStat.ATK, 1, true)
- .attr(StatChangeAttr, BattleStat.SPD, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
.attr(ForceSwitchOutAttr),
new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
@@ -7810,10 +7996,10 @@ export function initMoves() {
new AttackMove(Moves.VOLT_SWITCH, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5)
.attr(ForceSwitchOutAttr, true, false),
new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPATK, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BULLDOZE, Type.GROUND, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.FROST_BREATH, Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
@@ -7822,9 +8008,9 @@ export function initMoves() {
.attr(ForceSwitchOutAttr)
.hidesTarget(),
new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, true),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true),
new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.WILD_CHARGE, Type.ELECTRIC, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 5)
.attr(RecoilAttr)
@@ -7839,10 +8025,10 @@ export function initMoves() {
.attr(HitHealAttr)
.triageMove(),
new AttackMove(Moves.SACRED_SWORD, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 5)
- .attr(IgnoreOpponentStatChangesAttr)
+ .attr(IgnoreOpponentStatStagesAttr)
.slicingMove(),
new AttackMove(Moves.RAZOR_SHELL, Type.WATER, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 5)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.slicingMove(),
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
.attr(MinimizeAccuracyAttr)
@@ -7850,13 +8036,13 @@ export function initMoves() {
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.condition(failOnMaxCondition),
new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5)
.attr(FlinchAttr),
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
- .attr(StatChangeAttr, BattleStat.DEF, 3, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 3, true),
new AttackMove(Moves.NIGHT_DAZE, Type.DARK, MoveCategory.SPECIAL, 85, 95, 10, 40, 0, 5)
- .attr(StatChangeAttr, BattleStat.ACC, -1),
+ .attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.PSYSTRIKE, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 5)
.attr(DefDefAttr),
new AttackMove(Moves.TAIL_SLAP, Type.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, 0, 5)
@@ -7885,14 +8071,14 @@ export function initMoves() {
.attr(DefDefAttr)
.slicingMove(),
new AttackMove(Moves.GLACIATE, Type.ICE, MoveCategory.SPECIAL, 65, 95, 10, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BOLT_STRIKE, Type.ELECTRIC, MoveCategory.PHYSICAL, 130, 85, 5, 20, 0, 5)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.BLUE_FLARE, Type.FIRE, MoveCategory.SPECIAL, 130, 85, 5, 20, 0, 5)
.attr(StatusEffectAttr, StatusEffect.BURN),
new AttackMove(Moves.FIERY_DANCE, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 50, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPATK, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true)
.danceMove(),
new AttackMove(Moves.FREEZE_SHOCK, Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 5)
.attr(ChargeAttr, ChargeAnim.FREEZE_SHOCK_CHARGING, i18next.t("moveTriggers:becameCloakedInFreezingLight", {pokemonName: "{USER}"}))
@@ -7903,14 +8089,14 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN)
.ignoresVirtual(),
new AttackMove(Moves.SNARL, Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
- .attr(StatChangeAttr, BattleStat.SPATK, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.ICICLE_CRASH, Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, 30, 0, 5)
.attr(FlinchAttr)
.makesContact(false),
new AttackMove(Moves.V_CREATE, Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, 0, 5)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF, BattleStat.SPD ], -1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF, Stat.SPD ], -1, true),
new AttackMove(Moves.FUSION_FLARE, Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 5)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
.attr(LastMoveDoublePowerAttr, Moves.FUSION_BOLT),
@@ -7934,12 +8120,12 @@ export function initMoves() {
// If any fielded pokémon is grass-type and grounded.
return [...user.scene.getEnemyParty(), ...user.scene.getParty()].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded());
})
- .attr(StatChangeAttr, [BattleStat.ATK, BattleStat.SPATK], 1, false, (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded()),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded()),
new StatusMove(Moves.STICKY_WEB, Type.BUG, -1, 20, -1, 0, 6)
.attr(AddArenaTrapTagAttr, ArenaTagType.STICKY_WEB)
.target(MoveTarget.ENEMY_SIDE),
new AttackMove(Moves.FELL_STINGER, Type.BUG, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 6)
- .attr(PostVictoryStatChangeAttr, BattleStat.ATK, 3, true ),
+ .attr(PostVictoryStatStageChangeAttr, [ Stat.ATK ], 3, true ),
new AttackMove(Moves.PHANTOM_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
.attr(ChargeAttr, ChargeAnim.PHANTOM_FORCE_CHARGING, i18next.t("moveTriggers:vanishedInstantly", {pokemonName: "{USER}"}), BattlerTagType.HIDDEN)
.ignoresProtect()
@@ -7948,7 +8134,7 @@ export function initMoves() {
.attr(AddTypeAttr, Type.GHOST)
.partial(),
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1)
.soundBased(),
new StatusMove(Moves.ION_DELUGE, Type.ELECTRIC, -1, 25, -1, 1, 6)
.target(MoveTarget.BOTH_SIDES)
@@ -7972,7 +8158,7 @@ export function initMoves() {
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
.attr(ForceSwitchOutAttr, true, false)
.soundBased(),
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
@@ -7986,7 +8172,7 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
.target(MoveTarget.ALL)
- .attr(StatChangeAttr, BattleStat.DEF, 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
.attr(TerrainChangeAttr, TerrainType.GRASSY)
.target(MoveTarget.BOTH_SIDES),
@@ -7996,11 +8182,11 @@ export function initMoves() {
new StatusMove(Moves.ELECTRIFY, Type.ELECTRIC, -1, 20, -1, 0, 6)
.unimplemented(),
new AttackMove(Moves.PLAY_ROUGH, Type.FAIRY, MoveCategory.PHYSICAL, 90, 90, 10, 10, 0, 6)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new AttackMove(Moves.FAIRY_WIND, Type.FAIRY, MoveCategory.SPECIAL, 40, 100, 30, -1, 0, 6)
.windMove(),
new AttackMove(Moves.MOONBLAST, Type.FAIRY, MoveCategory.SPECIAL, 95, 100, 15, 30, 0, 6)
- .attr(StatChangeAttr, BattleStat.SPATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
new AttackMove(Moves.BOOMBURST, Type.NORMAL, MoveCategory.SPECIAL, 140, 100, 10, -1, 0, 6)
.soundBased()
.target(MoveTarget.ALL_NEAR_OTHERS),
@@ -8010,12 +8196,12 @@ export function initMoves() {
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
- .attr(StatChangeAttr, BattleStat.SPATK, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
.soundBased(),
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6)
- .attr(StatChangeAttr, BattleStat.DEF, 2, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.STEAM_ERUPTION, Type.WATER, MoveCategory.SPECIAL, 110, 95, 5, 30, 0, 6)
@@ -8029,26 +8215,26 @@ export function initMoves() {
.attr(WaterShurikenPowerAttr)
.attr(WaterShurikenMultiHitTypeAttr),
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
- .attr(StatChangeAttr, BattleStat.SPATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD),
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
- .attr(StatChangeAttr, BattleStat.SPDEF, 1)
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1)
.target(MoveTarget.NEAR_ALLY),
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
- .attr(StatChangeAttr, BattleStat.SPATK, -2),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2),
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, -1, 0, 6)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
.powderMove()
.unimplemented(),
new SelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6)
.attr(ChargeAttr, ChargeAnim.GEOMANCY_CHARGING, i18next.t("moveTriggers:isChargingPower", {pokemonName: "{USER}"}))
- .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
.ignoresVirtual(),
new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false)))
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false)))
.target(MoveTarget.USER_AND_ALLIES)
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))),
new StatusMove(Moves.HAPPY_HOUR, Type.NORMAL, -1, 30, -1, 0, 6) // No animation
@@ -8063,7 +8249,7 @@ export function initMoves() {
new StatusMove(Moves.HOLD_HANDS, Type.NORMAL, -1, 40, -1, 0, 6)
.target(MoveTarget.NEAR_ALLY),
new StatusMove(Moves.BABY_DOLL_EYES, Type.FAIRY, 100, 30, -1, 1, 6)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new AttackMove(Moves.NUZZLE, Type.ELECTRIC, MoveCategory.PHYSICAL, 20, 100, 20, 100, 0, 6)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.HOLD_BACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6)
@@ -8072,7 +8258,7 @@ export function initMoves() {
.makesContact()
.attr(TrapAttr, BattlerTagType.INFESTATION),
new AttackMove(Moves.POWER_UP_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 20, 100, 0, 6)
- .attr(StatChangeAttr, BattleStat.ATK, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true)
.punchingMove(),
new AttackMove(Moves.OBLIVION_WING, Type.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
.attr(HitHealAttr, 0.75)
@@ -8103,9 +8289,9 @@ export function initMoves() {
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_ASCENT, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 6)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
new AttackMove(Moves.HYPERSPACE_FURY, Type.DARK, MoveCategory.PHYSICAL, 100, -1, 5, -1, 0, 6)
- .attr(StatChangeAttr, BattleStat.DEF, -1, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true)
.makesContact(false)
.ignoresProtect(),
/* Unused */
@@ -8232,13 +8418,13 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
.makesContact(false),
new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
- .attr(IgnoreOpponentStatChangesAttr),
+ .attr(IgnoreOpponentStatStagesAttr),
new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, 100, 0, 7)
.attr(HealStatusEffectAttr, false, StatusEffect.BURN)
.soundBased()
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.ICE_HAMMER, Type.ICE, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7)
- .attr(StatChangeAttr, BattleStat.SPD, -1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1, true)
.punchingMove(),
new StatusMove(Moves.FLORAL_HEALING, Type.FAIRY, -1, 10, -1, 0, 7)
.attr(BoostHealAttr, 0.5, 2/3, true, false, (user, target, move) => user.scene.arena.terrain?.terrainType === TerrainType.GRASSY)
@@ -8246,8 +8432,8 @@ export function initMoves() {
new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7),
new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, -1, 0, 7)
.attr(HitHealAttr, null, Stat.ATK)
- .attr(StatChangeAttr, BattleStat.ATK, -1)
- .condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1)
+ .condition((user, target, move) => target.getStatStage(Stat.ATK) > -6)
.triageMove(),
new AttackMove(Moves.SOLAR_BLADE, Type.GRASS, MoveCategory.PHYSICAL, 125, 100, 10, -1, 0, 7)
.attr(SunlightChargeAttr, ChargeAnim.SOLAR_BLADE_CHARGING, i18next.t("moveTriggers:isGlowing", {pokemonName: "{USER}"}))
@@ -8259,11 +8445,11 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false),
new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, -1, 0, 7)
.attr(StatusEffectAttr, StatusEffect.POISON)
- .attr(StatChangeAttr, BattleStat.SPD, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1),
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false),
new StatusMove(Moves.GEAR_UP, Type.STEEL, -1, 20, -1, 0, 7)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false)))
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false)))
.target(MoveTarget.USER_AND_ALLIES)
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))),
new AttackMove(Moves.THROAT_CHOP, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
@@ -8278,11 +8464,11 @@ export function initMoves() {
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
.target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.LUNGE, Type.BUG, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new AttackMove(Moves.FIRE_LASH, Type.FIRE, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
- .attr(StatChangeAttr, BattleStat.DEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.POWER_TRIP, Type.DARK, MoveCategory.PHYSICAL, 20, 100, 10, -1, 0, 7)
- .attr(StatChangeCountPowerAttr),
+ .attr(PositiveStatStagePowerAttr),
new AttackMove(Moves.BURN_UP, Type.FIRE, MoveCategory.SPECIAL, 130, 100, 5, -1, 0, 7)
.condition((user) => {
const userTypes = user.getTypes(true);
@@ -8293,7 +8479,7 @@ export function initMoves() {
user.scene.queueMessage(i18next.t("moveTriggers:burnedItselfOut", {pokemonName: getPokemonNameWithAffix(user)}));
}),
new StatusMove(Moves.SPEED_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 7)
- .unimplemented(),
+ .attr(SwapStatAttr, Stat.SPD),
new AttackMove(Moves.SMART_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 7),
new StatusMove(Moves.PURIFY, Type.POISON, -1, 20, -1, 0, 7)
.condition(
@@ -8308,7 +8494,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES)
.attr(SuppressAbilitiesIfActedAttr),
new AttackMove(Moves.TROP_KICK, Type.GRASS, MoveCategory.PHYSICAL, 70, 100, 15, 100, 0, 7)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new StatusMove(Moves.INSTRUCT, Type.PSYCHIC, -1, 15, -1, 0, 7)
.unimplemented(),
new AttackMove(Moves.BEAK_BLAST, Type.FLYING, MoveCategory.PHYSICAL, 100, 100, 15, -1, -3, 7)
@@ -8316,7 +8502,7 @@ export function initMoves() {
.ballBombMove()
.makesContact(false),
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
- .attr(StatChangeAttr, BattleStat.DEF, -1, true, null, true, false, MoveEffectTrigger.HIT, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, null, true, false, MoveEffectTrigger.HIT, true)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_HAMMER, Type.DRAGON, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 7),
@@ -8350,7 +8536,7 @@ export function initMoves() {
.partial()
.ignoresVirtual(),
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
.ignoresVirtual(),
new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
@@ -8362,18 +8548,18 @@ export function initMoves() {
// Fails if the user was not hit by a physical attack during the turn
.condition((user, target, move) => user.getTag(ShellTrapTag)?.activated === true),
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7)
- .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true),
new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
.bitingMove()
.attr(RemoveScreensAttr),
new AttackMove(Moves.STOMPING_TANTRUM, Type.GROUND, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 7)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
new AttackMove(Moves.SHADOW_BONE, Type.GHOST, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.makesContact(false),
new AttackMove(Moves.ACCELEROCK, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 1, 7),
new AttackMove(Moves.LIQUIDATION, Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7)
- .attr(StatChangeAttr, BattleStat.DEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.PRISMATIC_LASER, Type.PSYCHIC, MoveCategory.SPECIAL, 160, 100, 10, -1, 0, 7)
.attr(RechargeAttr),
new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7)
@@ -8385,7 +8571,7 @@ export function initMoves() {
.ignoresAbilities()
.partial(),
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1),
new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7)
.attr(FlinchAttr),
new AttackMove(Moves.NATURES_MADNESS, Type.FAIRY, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 7)
@@ -8427,7 +8613,7 @@ export function initMoves() {
.makesContact(false)
.ignoresVirtual(),
new AttackMove(Moves.CLANGOROUS_SOULBLAZE, Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES)
.partial()
@@ -8494,18 +8680,18 @@ export function initMoves() {
.bitingMove(),
new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
.attr(EatBerryAttr)
- .attr(StatChangeAttr, BattleStat.DEF, 2, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
.condition((user) => {
const userBerries = user.scene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
return userBerries.length > 0;
})
.partial(),
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
- .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, false, 1)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
+ .attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false)
.condition((user, target, move) => user.getTag(TrappedTag)?.sourceMove !== Moves.NO_RETREAT), // fails if the user is currently trapped by No Retreat
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.partial(),
new StatusMove(Moves.MAGIC_POWDER, Type.PSYCHIC, 100, 20, -1, 0, 8)
.attr(ChangeTypeAttr, Type.PSYCHIC)
@@ -8600,16 +8786,16 @@ export function initMoves() {
.ignoresVirtual(),
/* End Unused */
new SelfStatusMove(Moves.CLANGOROUS_SOUL, Type.DRAGON, 100, 5, -1, 0, 8)
- .attr(CutHpStatBoostAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, 3)
+ .attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, 3)
.soundBased()
.danceMove(),
new AttackMove(Moves.BODY_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
.attr(DefAtkAttr),
new StatusMove(Moves.DECORATE, Type.FAIRY, -1, 15, -1, 0, 8)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 2)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 2)
.ignoresProtect(),
new AttackMove(Moves.DRUM_BEATING, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.makesContact(false),
new AttackMove(Moves.SNAP_TRAP, Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, -1, 0, 8)
.attr(TrapAttr, BattlerTagType.SNAP_TRAP),
@@ -8622,25 +8808,25 @@ export function initMoves() {
.slicingMove(),
new AttackMove(Moves.BEHEMOTH_BASH, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 8),
new AttackMove(Moves.AURA_WHEEL, Type.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPD, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
.makesContact(false)
.attr(AuraWheelTypeAttr)
.condition((user, target, move) => [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)), // Missing custom fail message
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
.target(MoveTarget.ALL_NEAR_ENEMIES)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new AttackMove(Moves.BRANCH_POKE, Type.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 8),
new AttackMove(Moves.OVERDRIVE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.APPLE_ACID, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPDEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1),
new AttackMove(Moves.GRAV_APPLE, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1)
.makesContact(false),
new AttackMove(Moves.SPIRIT_BREAK, Type.FAIRY, MoveCategory.PHYSICAL, 75, 100, 15, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
new AttackMove(Moves.STRANGE_STEAM, Type.FAIRY, MoveCategory.SPECIAL, 90, 95, 10, 20, 0, 8)
.attr(ConfuseAttr),
new StatusMove(Moves.LIFE_DEW, Type.WATER, -1, 10, -1, 0, 8)
@@ -8664,14 +8850,14 @@ export function initMoves() {
.attr(ClearTerrainAttr)
.condition((user, target, move) => !!user.scene.arena.terrain),
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
- //.attr(StatChangeAttr, BattleStat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit
- //.attr(StatChangeAttr, BattleStat.DEF, -1, true)
+ //.attr(StatStageChangeAttr, Stat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit
+ //.attr(StatStageChangeAttr, Stat.DEF, -1, true)
.attr(MultiHitAttr)
.makesContact(false)
.partial(),
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(StatChangeAttr, BattleStat.SPATK, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true)
.ignoresVirtual(),
new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8)
.attr(ShellSideArmCategoryAttr)
@@ -8692,12 +8878,12 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() !== TerrainType.NONE && user.isGrounded() ? 2 : 1)
.pulseMove(),
new AttackMove(Moves.SKITTER_SMACK, Type.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
new AttackMove(Moves.BURNING_JEALOUSY, Type.FIRE, MoveCategory.SPECIAL, 70, 100, 5, 100, 0, 8)
- .target(MoveTarget.ALL_NEAR_ENEMIES)
- .partial(),
+ .attr(StatusIfBoostedAttr, StatusEffect.BURN)
+ .target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8)
- .partial(),
+ .attr(MovePowerMultiplierAttr, (user, _target, _move) => user.turnData.statStagesDecreased ? 2 : 1),
new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8)
.attr(AttackedByItemAttr)
.makesContact(false),
@@ -8705,7 +8891,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_OTHERS)
.unimplemented(),
new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1)
.target(MoveTarget.NEAR_ALLY),
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
.attr(ForceSwitchOutAttr, true, false),
@@ -8741,7 +8927,7 @@ export function initMoves() {
.attr(FlinchAttr)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.THUNDEROUS_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.DEF, -1),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.GLACIAL_LANCE, Type.ICE, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.makesContact(false),
@@ -8753,18 +8939,18 @@ export function initMoves() {
new AttackMove(Moves.DIRE_CLAW, Type.POISON, MoveCategory.PHYSICAL, 80, 100, 15, 50, 0, 8)
.attr(MultiStatusEffectAttr, [StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP]),
new AttackMove(Moves.PSYSHIELD_BASH, Type.PSYCHIC, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.DEF, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true),
new SelfStatusMove(Moves.POWER_SHIFT, Type.NORMAL, -1, 10, -1, 0, 8)
.unimplemented(),
new AttackMove(Moves.STONE_AXE, Type.ROCK, MoveCategory.PHYSICAL, 65, 90, 15, 100, 0, 8)
.attr(AddArenaTrapTagHitAttr, ArenaTagType.STEALTH_ROCK)
.slicingMove(),
new AttackMove(Moves.SPRINGTIDE_STORM, Type.FAIRY, MoveCategory.SPECIAL, 100, 80, 5, 30, 0, 8)
- .attr(StatChangeAttr, BattleStat.ATK, -1)
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1)
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.MYSTICAL_POWER, Type.PSYCHIC, MoveCategory.SPECIAL, 70, 90, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.SPATK, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true),
new AttackMove(Moves.RAGING_FURY, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 8)
.makesContact(false)
.attr(FrenzyAttr)
@@ -8780,10 +8966,10 @@ export function initMoves() {
.makesContact(false)
.attr(FlinchAttr),
new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, -1, 0, 8)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPD ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.HEADLONG_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true)
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true)
.punchingMove(),
new AttackMove(Moves.BARB_BARRAGE, Type.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8)
.makesContact(false)
@@ -8791,15 +8977,15 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.POISON),
new AttackMove(Moves.ESPER_WING, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8)
.attr(HighCritAttr)
- .attr(StatChangeAttr, BattleStat.SPD, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true),
new AttackMove(Moves.BITTER_MALICE, Type.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new SelfStatusMove(Moves.SHELTER, Type.STEEL, -1, 10, 100, 0, 8)
- .attr(StatChangeAttr, BattleStat.DEF, 2, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
.makesContact(false)
.attr(HighCritAttr)
- .attr(StatChangeAttr, BattleStat.DEF, -1)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.attr(FlinchAttr)
.partial(),
new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8)
@@ -8810,7 +8996,7 @@ export function initMoves() {
.slicingMove(),
new AttackMove(Moves.BLEAKWIND_STORM, Type.FLYING, MoveCategory.SPECIAL, 100, 80, 10, 30, 0, 8)
.attr(StormAccuracyAttr)
- .attr(StatChangeAttr, BattleStat.SPD, -1)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1)
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.WILDBOLT_STORM, Type.ELECTRIC, MoveCategory.SPECIAL, 100, 80, 10, 20, 0, 8)
@@ -8829,7 +9015,7 @@ export function initMoves() {
.target(MoveTarget.USER_AND_ALLIES)
.triageMove(),
new SelfStatusMove(Moves.TAKE_HEART, Type.PSYCHIC, -1, 10, -1, 0, 8)
- .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF ], 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF ], 1, true)
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP),
/* Unused
new AttackMove(Moves.G_MAX_WILDFIRE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
@@ -8936,7 +9122,7 @@ export function initMoves() {
.attr(TeraBlastCategoryAttr)
.attr(TeraBlastTypeAttr)
.attr(TeraBlastPowerAttr)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.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(),
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.SILK_TRAP),
@@ -8949,17 +9135,17 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 100))
.makesContact(false),
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPDEF, -2),
+ .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
.makesContact(false)
.partial(),
new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
.punchingMove(),
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9)
- .attr(StatChangeAttr, BattleStat.ATK, 2)
- .attr(StatChangeAttr, BattleStat.DEF, -2),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], 2)
+ .attr(StatStageChangeAttr, [ Stat.DEF ], -2),
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPD, -2, true),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -2, true),
new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9)
.attr(MultiHitAttr, MultiHitType._10)
.slicingMove()
@@ -9001,24 +9187,24 @@ export function initMoves() {
new StatusMove(Moves.DOODLE, Type.NORMAL, 100, 10, -1, 0, 9)
.attr(AbilityCopyAttr, true),
new SelfStatusMove(Moves.FILLET_AWAY, Type.NORMAL, -1, 10, -1, 0, 9)
- .attr(CutHpStatBoostAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 2, 2),
+ .attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, 2),
new AttackMove(Moves.KOWTOW_CLEAVE, Type.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9)
.slicingMove(),
new AttackMove(Moves.FLOWER_TRICK, Type.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, 100, 0, 9)
.attr(CritOnlyAttr)
.makesContact(false),
new AttackMove(Moves.TORCH_SONG, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPATK, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], 1, true)
.soundBased(),
new AttackMove(Moves.AQUA_STEP, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPD, 1, true)
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.RAGING_BULL, Type.NORMAL, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 9)
.attr(RagingBullTypeAttr)
.attr(RemoveScreensAttr),
new AttackMove(Moves.MAKE_IT_RAIN, Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
.attr(MoneyAttr)
- .attr(StatChangeAttr, BattleStat.SPATK, -1, true, null, true, false, MoveEffectTrigger.HIT, true)
+ .attr(StatStageChangeAttr, [ Stat.SPATK ], -1, true, null, true, false, MoveEffectTrigger.HIT, true)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1)
@@ -9040,17 +9226,17 @@ export function initMoves() {
.attr(ForceSwitchOutAttr, true, false)
.target(MoveTarget.BOTH_SIDES),
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
- .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPD ], 1, true, null, true, true)
+ .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
.attr(RemoveArenaTrapAttr, true),
new StatusMove(Moves.SNOWSCAPE, Type.ICE, -1, 10, -1, 0, 9)
.attr(WeatherChangeAttr, WeatherType.SNOW)
.target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.POUNCE, Type.BUG, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPD, -1),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1),
new AttackMove(Moves.TRAILBLAZE, Type.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPD, 1, true),
+ .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true),
new AttackMove(Moves.CHILLING_WATER, Type.WATER, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 9)
- .attr(StatChangeAttr, BattleStat.ATK, -1),
+ .attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new AttackMove(Moves.HYPER_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
.ignoresProtect(),
new AttackMove(Moves.TWIN_BEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9)
@@ -9059,7 +9245,7 @@ export function initMoves() {
.attr(HitCountPowerAttr)
.punchingMove(),
new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
- .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
+ .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
new AttackMove(Moves.BITTER_BLADE, Type.FIRE, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 9)
.attr(HitHealAttr)
.slicingMove()
@@ -9114,7 +9300,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES)
.triageMove(),
new AttackMove(Moves.SYRUP_BOMB, Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9)
- .attr(StatChangeAttr, BattleStat.SPD, -1) //Temporary
+ .attr(StatStageChangeAttr, [ Stat.SPD ], -1) //Temporary
.ballBombMove()
.partial(),
new AttackMove(Moves.IVY_CUDGEL, Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9)
@@ -9143,12 +9329,11 @@ export function initMoves() {
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
.attr(OpponentHighHpPowerAttr, 100),
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
- .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
- .target(MoveTarget.NEAR_ALLY)
- .partial(),
+ .attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
+ .target(MoveTarget.NEAR_ALLY),
new AttackMove(Moves.ALLURING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
- .soundBased()
- .partial(),
+ .attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)
+ .soundBased(),
new AttackMove(Moves.TEMPER_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
new AttackMove(Moves.SUPERCELL_SLAM, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9)
@@ -9167,7 +9352,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.TOXIC)
);
allMoves.map(m => {
- if (m.getAttrs(StatChangeAttr).some(a => a.selfTarget && a.levels < 0)) {
+ if (m.getAttrs(StatStageChangeAttr).some(a => a.selfTarget && a.stages < 0)) {
selfStatLowerMoves.push(m.id);
}
});
diff --git a/src/data/nature.ts b/src/data/nature.ts
index 72e5bb7863c..c614be465c3 100644
--- a/src/data/nature.ts
+++ b/src/data/nature.ts
@@ -1,9 +1,9 @@
-import { Stat, getStatName } from "./pokemon-stat";
import * as Utils from "../utils";
import { TextStyle, getBBCodeFrag } from "../ui/text";
import { Nature } from "#enums/nature";
import { UiTheme } from "#enums/ui-theme";
import i18next from "i18next";
+import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#app/enums/stat";
export { Nature };
@@ -14,10 +14,9 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
ret = i18next.t("nature:" + ret as any);
}
if (includeStatEffects) {
- const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat | null = null;
let decreasedStat: Stat | null = null;
- for (const stat of stats) {
+ for (const stat of EFFECTIVE_STATS) {
const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) {
increasedStat = stat;
@@ -28,7 +27,7 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW;
const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text;
if (increasedStat && decreasedStat) {
- ret = `${getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(`, textStyle)}${getTextFrag(`+${getStatName(increasedStat, true)}`, TextStyle.SUMMARY_PINK)}${getTextFrag("/", textStyle)}${getTextFrag(`-${getStatName(decreasedStat, true)}`, TextStyle.SUMMARY_BLUE)}${getTextFrag(")", textStyle)}`;
+ ret = `${getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(`, textStyle)}${getTextFrag(`+${i18next.t(getShortenedStatKey(increasedStat))}`, TextStyle.SUMMARY_PINK)}${getTextFrag("/", textStyle)}${getTextFrag(`-${i18next.t(getShortenedStatKey(decreasedStat))}`, TextStyle.SUMMARY_BLUE)}${getTextFrag(")", textStyle)}`;
} else {
ret = getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(-)`, textStyle);
}
diff --git a/src/data/pokemon-evolutions.ts b/src/data/pokemon-evolutions.ts
index 315e75e53e1..6479d620182 100644
--- a/src/data/pokemon-evolutions.ts
+++ b/src/data/pokemon-evolutions.ts
@@ -1,7 +1,7 @@
import { Gender } from "./gender";
import { PokeballType } from "./pokeball";
import Pokemon from "../field/pokemon";
-import { Stat } from "./pokemon-stat";
+import { Stat } from "#enums/stat";
import { Type } from "./type";
import * as Utils from "../utils";
import { SpeciesFormKey } from "./pokemon-species";
diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts
index e4417f8e8bb..bc815b91f3a 100644
--- a/src/data/pokemon-forms.ts
+++ b/src/data/pokemon-forms.ts
@@ -66,34 +66,34 @@ export enum FormChangeItem {
BLUE_ORB = 50,
RED_ORB,
- SHARP_METEORITE,
- HARD_METEORITE,
- SMOOTH_METEORITE,
ADAMANT_CRYSTAL,
LUSTROUS_GLOBE,
GRISEOUS_CORE,
REVEAL_GLASS,
- GRACIDEA,
MAX_MUSHROOMS,
DARK_STONE,
LIGHT_STONE,
PRISON_BOTTLE,
- N_LUNARIZER,
- N_SOLARIZER,
RUSTED_SWORD,
RUSTED_SHIELD,
ICY_REINS_OF_UNITY,
SHADOW_REINS_OF_UNITY,
- WELLSPRING_MASK,
- HEARTHFLAME_MASK,
- CORNERSTONE_MASK,
+ ULTRANECROZIUM_Z,
+
+ SHARP_METEORITE = 100,
+ HARD_METEORITE,
+ SMOOTH_METEORITE,
+ GRACIDEA,
SHOCK_DRIVE,
BURN_DRIVE,
CHILL_DRIVE,
DOUSE_DRIVE,
- ULTRANECROZIUM_Z,
-
- FIST_PLATE = 100,
+ N_SOLARIZER,
+ N_LUNARIZER,
+ WELLSPRING_MASK,
+ HEARTHFLAME_MASK,
+ CORNERSTONE_MASK,
+ FIST_PLATE,
SKY_PLATE,
TOXIC_PLATE,
EARTH_PLATE,
@@ -129,7 +129,7 @@ export enum FormChangeItem {
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
- BLANK_MEMORY // TODO: Find a potential use for this
+ NORMAL_MEMORY // TODO: Find a potential use for this
}
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
@@ -359,7 +359,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
/**
* Class used for triggering form changes based on weather.
- * Used by Castform.
+ * Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
@@ -392,7 +392,7 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/**
* Class used for reverting to the original form when the weather runs out
* or when the user loses the ability/is suppressed.
- * Used by Castform.
+ * Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
@@ -930,6 +930,11 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true),
new SpeciesFormChange(Species.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true),
],
+ [Species.CHERRIM]: [
+ new SpeciesFormChange(Species.CHERRIM, "overcast", "sunshine", new SpeciesFormChangeWeatherTrigger(Abilities.FLOWER_GIFT, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]), true),
+ new SpeciesFormChange(Species.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeRevertWeatherFormTrigger(Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ]), true),
+ new SpeciesFormChange(Species.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeActiveTrigger(), true),
+ ],
};
export function initPokemonForms() {
diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts
index 17f2de794ae..09448b332e4 100644
--- a/src/data/pokemon-species.ts
+++ b/src/data/pokemon-species.ts
@@ -14,7 +14,7 @@ import { GrowthRate } from "./exp";
import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions";
import { Type } from "./type";
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "./pokemon-level-moves";
-import { Stat } from "./pokemon-stat";
+import { Stat } from "#enums/stat";
import { Variant, VariantSet, variantColorCache, variantData } from "./variant";
export enum Region {
@@ -944,7 +944,7 @@ export function initSpecies() {
new PokemonSpecies(Species.METAPOD, 1, false, false, false, "Cocoon Pokémon", Type.BUG, null, 0.7, 9.9, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 50, 20, 55, 25, 25, 30, 120, 50, 72, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.BUTTERFREE, 1, false, false, false, "Butterfly Pokémon", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, GrowthRate.MEDIUM_FAST, 50, true, true,
new PokemonForm("Normal", "", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, true, null, true),
- new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 32, Abilities.TINTED_LENS, Abilities.TINTED_LENS, Abilities.TINTED_LENS, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true),
+ new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 32, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true),
),
new PokemonSpecies(Species.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", Type.BUG, Type.POISON, 0.3, 3.2, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.KAKUNA, 1, false, false, false, "Cocoon Pokémon", Type.BUG, Type.POISON, 0.6, 10, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false),
diff --git a/src/data/pokemon-stat.ts b/src/data/pokemon-stat.ts
deleted file mode 100644
index 16570785a62..00000000000
--- a/src/data/pokemon-stat.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Stat } from "#enums/stat";
-import i18next from "i18next";
-
-export { Stat };
-
-export function getStatName(stat: Stat, shorten: boolean = false) {
- let ret: string = "";
- switch (stat) {
- case Stat.HP:
- ret = !shorten ? i18next.t("pokemonInfo:Stat.HP") : i18next.t("pokemonInfo:Stat.HPshortened");
- break;
- case Stat.ATK:
- ret = !shorten ? i18next.t("pokemonInfo:Stat.ATK") : i18next.t("pokemonInfo:Stat.ATKshortened");
- break;
- case Stat.DEF:
- ret = !shorten ? i18next.t("pokemonInfo:Stat.DEF") : i18next.t("pokemonInfo:Stat.DEFshortened");
- break;
- case Stat.SPATK:
- ret = !shorten ? i18next.t("pokemonInfo:Stat.SPATK") : i18next.t("pokemonInfo:Stat.SPATKshortened");
- break;
- case Stat.SPDEF:
- ret = !shorten ? i18next.t("pokemonInfo:Stat.SPDEF") : i18next.t("pokemonInfo:Stat.SPDEFshortened");
- break;
- case Stat.SPD:
- ret = !shorten ? i18next.t("pokemonInfo:Stat.SPD") : i18next.t("pokemonInfo:Stat.SPDshortened");
- break;
- }
- return ret;
-}
diff --git a/src/data/temp-battle-stat.ts b/src/data/temp-battle-stat.ts
deleted file mode 100644
index 2d461a1d647..00000000000
--- a/src/data/temp-battle-stat.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { BattleStat, getBattleStatName } from "./battle-stat";
-import i18next from "i18next";
-
-export enum TempBattleStat {
- ATK,
- DEF,
- SPATK,
- SPDEF,
- SPD,
- ACC,
- CRIT
-}
-
-export function getTempBattleStatName(tempBattleStat: TempBattleStat) {
- if (tempBattleStat === TempBattleStat.CRIT) {
- return i18next.t("modifierType:TempBattleStatBoosterStatName.CRIT");
- }
- return getBattleStatName(tempBattleStat as integer as BattleStat);
-}
-
-export function getTempBattleStatBoosterItemName(tempBattleStat: TempBattleStat) {
- switch (tempBattleStat) {
- case TempBattleStat.ATK:
- return "X Attack";
- case TempBattleStat.DEF:
- return "X Defense";
- case TempBattleStat.SPATK:
- return "X Sp. Atk";
- case TempBattleStat.SPDEF:
- return "X Sp. Def";
- case TempBattleStat.SPD:
- return "X Speed";
- case TempBattleStat.ACC:
- return "X Accuracy";
- case TempBattleStat.CRIT:
- return "Dire Hit";
- }
-}
diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts
index 1265b815bf4..1c79750c91a 100644
--- a/src/enums/arena-tag-type.ts
+++ b/src/enums/arena-tag-type.ts
@@ -22,5 +22,6 @@ export enum ArenaTagType {
CRAFTY_SHIELD = "CRAFTY_SHIELD",
TAILWIND = "TAILWIND",
HAPPY_HOUR = "HAPPY_HOUR",
+ SAFEGUARD = "SAFEGUARD",
NO_CRIT = "NO_CRIT"
}
diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts
index b133b442801..20ceb1b331f 100644
--- a/src/enums/battler-tag-type.ts
+++ b/src/enums/battler-tag-type.ts
@@ -69,5 +69,7 @@ export enum BattlerTagType {
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU",
BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING",
- SHELL_TRAP = "SHELL_TRAP"
+ SHELL_TRAP = "SHELL_TRAP",
+ DRAGON_CHEER = "DRAGON_CHEER",
+ NO_RETREAT = "NO_RETREAT",
}
diff --git a/src/enums/shop-cursor-target.ts b/src/enums/shop-cursor-target.ts
index d2f72fed0d6..11f524399b2 100644
--- a/src/enums/shop-cursor-target.ts
+++ b/src/enums/shop-cursor-target.ts
@@ -1,13 +1,13 @@
/**
- * Determines the cursor target when entering the shop phase.
+ * Determines the row cursor target when entering the shop phase.
*/
export enum ShopCursorTarget {
- /** Cursor points to Reroll */
+ /** Cursor points to Reroll row */
REROLL,
- /** Cursor points to Items */
- ITEMS,
- /** Cursor points to Shop */
+ /** Cursor points to Rewards row */
+ REWARDS,
+ /** Cursor points to Shop row */
SHOP,
- /** Cursor points to Check Team */
+ /** Cursor points to Check Team row */
CHECK_TEAM
}
diff --git a/src/enums/stat.ts b/src/enums/stat.ts
index a40319664d6..a12d53e8559 100644
--- a/src/enums/stat.ts
+++ b/src/enums/stat.ts
@@ -1,8 +1,75 @@
+/** Enum that comprises all possible stat-related attributes, in-battle and permanent, of a Pokemon. */
export enum Stat {
+ /** Hit Points */
HP = 0,
+ /** Attack */
ATK,
+ /** Defense */
DEF,
+ /** Special Attack */
SPATK,
+ /** Special Defense */
SPDEF,
+ /** Speed */
SPD,
+ /** Accuracy */
+ ACC,
+ /** Evasiveness */
+ EVA
+}
+
+/** A constant array comprised of the {@linkcode Stat} values that make up {@linkcode PermanentStat}. */
+export const PERMANENT_STATS = [ Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ] as const;
+/** Type used to describe the core, permanent stats of a Pokemon. */
+export type PermanentStat = typeof PERMANENT_STATS[number];
+
+/** A constant array comprised of the {@linkcode Stat} values that make up {@linkcode EFfectiveStat}. */
+export const EFFECTIVE_STATS = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ] as const;
+/** Type used to describe the intersection of core stats and stats that have stages in battle. */
+export type EffectiveStat = typeof EFFECTIVE_STATS[number];
+
+/** A constant array comprised of {@linkcode Stat} the values that make up {@linkcode BattleStat}. */
+export const BATTLE_STATS = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD, Stat.ACC, Stat.EVA ] as const;
+/** Type used to describe the stats that have stages which can be incremented and decremented in battle. */
+export type BattleStat = typeof BATTLE_STATS[number];
+
+/** A constant array comprised of {@linkcode Stat} the values that make up {@linkcode TempBattleStat}. */
+export const TEMP_BATTLE_STATS = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD, Stat.ACC ] as const;
+/** Type used to describe the stats that have X item (`TEMP_STAT_STAGE_BOOSTER`) equivalents. */
+export type TempBattleStat = typeof TEMP_BATTLE_STATS[number];
+
+/**
+ * Provides the translation key corresponding to the amount of stat stages and whether those stat stages
+ * are positive or negative.
+ * @param stages the amount of stages
+ * @param isIncrease dictates a negative (`false`) or a positive (`true`) stat stage change
+ * @returns the translation key fitting the conditions described by {@linkcode stages} and {@linkcode isIncrease}
+ */
+export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boolean) {
+ if (stages === 1) {
+ return isIncrease ? "battle:statRose" : "battle:statFell";
+ } else if (stages === 2) {
+ return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell";
+ } else if (stages <= 6) {
+ return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell";
+ }
+ return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower";
+}
+
+/**
+ * Provides the translation key corresponding to a given stat which can be translated into its full name.
+ * @param stat the {@linkcode Stat} to be translated
+ * @returns the translation key corresponding to the given {@linkcode Stat}
+ */
+export function getStatKey(stat: Stat) {
+ return `pokemonInfo:Stat.${Stat[stat]}`;
+}
+
+/**
+ * Provides the translation key corresponding to a given stat which can be translated into its shortened name.
+ * @param stat the {@linkcode Stat} to be translated
+ * @returns the translation key corresponding to the given {@linkcode Stat}
+ */
+export function getShortenedStatKey(stat: PermanentStat) {
+ return `pokemonInfo:Stat.${Stat[stat]}shortened`;
}
diff --git a/src/field/arena.ts b/src/field/arena.ts
index 7622b9a014f..e8defbd1a8e 100644
--- a/src/field/arena.ts
+++ b/src/field/arena.ts
@@ -339,7 +339,10 @@ export class Arena {
*/
triggerWeatherBasedFormChanges(): void {
this.scene.getField(true).forEach( p => {
- if (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM) {
+ const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM);
+ const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM);
+
+ if (isCastformWithForecast || isCherrimWithFlowerGift) {
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
this.scene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger);
}
@@ -351,7 +354,10 @@ export class Arena {
*/
triggerWeatherBasedFormChangesToNormal(): void {
this.scene.getField(true).forEach( p => {
- if (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM) {
+ const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM);
+ const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM);
+
+ if (isCastformWithForecast || isCherrimWithFlowerGift) {
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
return this.scene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger);
}
diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts
index 9fcbdd8dc6c..e0a9a4a86ce 100644
--- a/src/field/pokemon.ts
+++ b/src/field/pokemon.ts
@@ -3,26 +3,24 @@ import BattleScene, { AnySound } from "../battle-scene";
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
-import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr } 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 } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import { Constructor } from "#app/utils";
import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
-import { Stat } from "../data/pokemon-stat";
-import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
+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 } from "../modifier/modifier";
import { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
-import { BattleStat } from "../data/battle-stat";
-import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags";
+import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather";
-import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
-import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr } 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 } from "../data/ability";
import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui";
@@ -40,7 +38,7 @@ import Overrides from "#app/overrides";
import i18next from "i18next";
import { speciesEggMoves } from "../data/egg-moves";
import { ModifierTier } from "../modifier/modifier-tier";
-import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
+import { applyChallenges, ChallengeType } from "#app/data/challenge";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec";
@@ -49,17 +47,17 @@ import { BerryType } from "#enums/berry-type";
import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
+import { getPokemonNameWithAffix } from "#app/messages";
+import { DamagePhase } from "#app/phases/damage-phase";
+import { FaintPhase } from "#app/phases/faint-phase";
+import { LearnMovePhase } from "#app/phases/learn-move-phase";
+import { MoveEffectPhase } from "#app/phases/move-effect-phase";
+import { MoveEndPhase } from "#app/phases/move-end-phase";
+import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
+import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
+import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase";
import { Challenges } from "#enums/challenges";
-import { getPokemonNameWithAffix } from "#app/messages.js";
-import { DamagePhase } from "#app/phases/damage-phase.js";
-import { FaintPhase } from "#app/phases/faint-phase.js";
-import { LearnMovePhase } from "#app/phases/learn-move-phase.js";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
-import { MoveEndPhase } from "#app/phases/move-end-phase.js";
-import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase.js";
-import { StatChangePhase } from "#app/phases/stat-change-phase.js";
-import { SwitchSummonPhase } from "#app/phases/switch-summon-phase.js";
-import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-phase.js";
export enum FieldPosition {
CENTER,
@@ -119,6 +117,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public maskEnabled: boolean;
public maskSprite: Phaser.GameObjects.Sprite | null;
+ public usedTMs: Moves[];
+
private shinySparkle: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) {
@@ -133,9 +133,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
}
- const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value);
- const randAbilityIndex = Utils.randSeedInt(2);
-
this.species = species;
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level;
@@ -146,6 +143,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.abilityIndex = abilityIndex; // Use the provided ability index if it is defined
} else {
// If abilityIndex is not provided, determine it based on species and hidden ability
+ const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value);
+ const randAbilityIndex = Utils.randSeedInt(2);
if (species.abilityHidden && hasHiddenAbility) {
// If the species has a hidden ability and the hidden ability is present
this.abilityIndex = 2;
@@ -196,6 +195,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionVariant = dataSource.fusionVariant || 0;
this.fusionGender = dataSource.fusionGender;
this.fusionLuck = dataSource.fusionLuck;
+ this.usedTMs = dataSource.usedTMs ?? [];
} else {
this.id = Utils.randSeedInt(4294967296);
this.ivs = ivs || Utils.getIvsFromId(this.id);
@@ -674,49 +674,139 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
});
}
- getStat(stat: Stat): integer {
+ /**
+ * Retrieves the entire set of stats of the {@linkcode Pokemon}.
+ * @param bypassSummonData prefer actual stats (`true` by default) or in-battle overriden stats (`false`)
+ * @returns the numeric values of the {@linkcode Pokemon}'s stats
+ */
+ getStats(bypassSummonData: boolean = true): number[] {
+ if (!bypassSummonData && this.summonData?.stats) {
+ return this.summonData.stats;
+ }
+ return this.stats;
+ }
+
+ /**
+ * Retrieves the corresponding {@linkcode PermanentStat} of the {@linkcode Pokemon}.
+ * @param stat the desired {@linkcode PermanentStat}
+ * @param bypassSummonData prefer actual stats (`true` by default) or in-battle overridden stats (`false`)
+ * @returns the numeric value of the desired {@linkcode Stat}
+ */
+ getStat(stat: PermanentStat, bypassSummonData: boolean = true): number {
+ if (!bypassSummonData && this.summonData && (this.summonData.stats[stat] !== 0)) {
+ return this.summonData.stats[stat];
+ }
return this.stats[stat];
}
- getBattleStat(stat: Stat, opponent?: Pokemon, move?: Move, isCritical: boolean = false): integer {
- if (stat === Stat.HP) {
- return this.getStat(Stat.HP);
- }
- const battleStat = (stat - 1) as BattleStat;
- const statLevel = new Utils.IntegerHolder(this.summonData.battleStats[battleStat]);
- if (opponent) {
- if (isCritical) {
- switch (stat) {
- case Stat.ATK:
- case Stat.SPATK:
- statLevel.value = Math.max(statLevel.value, 0);
- break;
- case Stat.DEF:
- case Stat.SPDEF:
- statLevel.value = Math.min(statLevel.value, 0);
- break;
- }
- }
- applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, false, statLevel);
- if (move) {
- applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, opponent, move, statLevel);
+ /**
+ * Writes the value to the corrseponding {@linkcode PermanentStat} of the {@linkcode Pokemon}.
+ *
+ * Note that this does nothing if {@linkcode value} is less than 0.
+ * @param stat the desired {@linkcode PermanentStat} to be overwritten
+ * @param value the desired numeric value
+ * @param bypassSummonData write to actual stats (`true` by default) or in-battle overridden stats (`false`)
+ */
+ setStat(stat: PermanentStat, value: number, bypassSummonData: boolean = true): void {
+ if (value >= 0) {
+ if (!bypassSummonData && this.summonData) {
+ this.summonData.stats[stat] = value;
+ } else {
+ this.stats[stat] = value;
}
}
- if (this.isPlayer()) {
- this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel);
+ }
+
+ /**
+ * Retrieves the entire set of in-battle stat stages of the {@linkcode Pokemon}.
+ * @returns the numeric values of the {@linkcode Pokemon}'s in-battle stat stages if available, a fresh stat stage array otherwise
+ */
+ getStatStages(): number[] {
+ return this.summonData ? this.summonData.statStages : [ 0, 0, 0, 0, 0, 0, 0 ];
+ }
+
+ /**
+ * Retrieves the in-battle stage of the specified {@linkcode BattleStat}.
+ * @param stat the {@linkcode BattleStat} whose stage is desired
+ * @returns the stage of the desired {@linkcode BattleStat} if available, 0 otherwise
+ */
+ getStatStage(stat: BattleStat): number {
+ return this.summonData ? this.summonData.statStages[stat - 1] : 0;
+ }
+
+ /**
+ * Writes the value to the in-battle stage of the corresponding {@linkcode BattleStat} of the {@linkcode Pokemon}.
+ *
+ * Note that, if the value is not within a range of [-6, 6], it will be forced to the closest range bound.
+ * @param stat the {@linkcode BattleStat} whose stage is to be overwritten
+ * @param value the desired numeric value
+ */
+ setStatStage(stat: BattleStat, value: number): void {
+ if (this.summonData) {
+ if (value >= -6) {
+ this.summonData.statStages[stat - 1] = Math.min(value, 6);
+ } else {
+ this.summonData.statStages[stat - 1] = Math.max(value, -6);
+ }
}
- const statValue = new Utils.NumberHolder(this.getStat(stat));
+ }
+
+ /**
+ * Retrieves the critical-hit stage considering the move used and the Pokemon
+ * who used it.
+ * @param source the {@linkcode Pokemon} who using the move
+ * @param move the {@linkcode Move} being used
+ * @returns the final critical-hit stage value
+ */
+ getCritStage(source: Pokemon, move: Move): number {
+ const critStage = new Utils.IntegerHolder(0);
+ applyMoveAttrs(HighCritAttr, source, this, move, critStage);
+ this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
+ this.scene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
+ const bonusCrit = new Utils.BooleanHolder(false);
+ //@ts-ignore
+ if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
+ if (bonusCrit.value) {
+ critStage.value += 1;
+ }
+ }
+ const critBoostTag = source.getTag(CritBoostTag);
+ if (critBoostTag) {
+ if (critBoostTag instanceof DragonCheerTag) {
+ critStage.value += critBoostTag.typesOnAdd.includes(Type.DRAGON) ? 2 : 1;
+ } else {
+ critStage.value += 2;
+ }
+ }
+
+ console.log(`crit stage: +${critStage.value}`);
+ return critStage.value;
+ }
+
+ /**
+ * Calculates and retrieves the final value of a stat considering any held
+ * items, move effects, opponent abilities, and whether there was a critical
+ * hit.
+ * @param stat the desired {@linkcode EffectiveStat}
+ * @param opponent the target {@linkcode Pokemon}
+ * @param move the {@linkcode Move} being used
+ * @param isCritical determines whether a critical hit has occurred or not (`false` by default)
+ * @returns the final in-battle value of a stat
+ */
+ getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, isCritical: boolean = false): integer {
+ const statValue = new Utils.NumberHolder(this.getStat(stat, false));
this.scene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
const fieldApplied = new Utils.BooleanHolder(false);
for (const pokemon of this.scene.getField(true)) {
- applyFieldBattleStatMultiplierAbAttrs(FieldMultiplyBattleStatAbAttr, pokemon, stat, statValue, this, fieldApplied);
+ applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied);
if (fieldApplied.value) {
break;
}
}
- applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, battleStat, statValue);
- let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value));
+ applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue);
+ let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, isCritical);
+
switch (stat) {
case Stat.ATK:
if (this.getTag(BattlerTagType.SLOW_START)) {
@@ -763,24 +853,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!this.stats) {
this.stats = [ 0, 0, 0, 0, 0, 0 ];
}
- const baseStats = this.getSpeciesForm().baseStats.slice(0);
- if (this.fusionSpecies) {
- const fusionBaseStats = this.getFusionSpeciesForm().baseStats;
- for (let s = 0; s < this.stats.length; s++) {
+
+ // Get and manipulate base stats
+ const baseStats = this.getSpeciesForm(true).baseStats.slice();
+ if (this.isFusion()) {
+ const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats;
+ for (const s of PERMANENT_STATS) {
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
}
} else if (this.scene.gameMode.isSplicedOnly) {
- for (let s = 0; s < this.stats.length; s++) {
+ for (const s of PERMANENT_STATS) {
baseStats[s] = Math.ceil(baseStats[s] / 2);
}
}
- this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats);
- const stats = Utils.getEnumValues(Stat);
- for (const s of stats) {
- const isHp = s === Stat.HP;
- const baseStat = baseStats[s];
- let value = Math.floor(((2 * baseStat + this.ivs[s]) * this.level) * 0.01);
- if (isHp) {
+ this.scene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats);
+
+ // Using base stats, calculate and store stats one by one
+ for (const s of PERMANENT_STATS) {
+ let value = Math.floor(((2 * baseStats[s] + this.ivs[s]) * this.level) * 0.01);
+ if (s === Stat.HP) {
value = value + this.level + 10;
if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) {
value = 1;
@@ -801,7 +892,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
value = Math.max(Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](value * natureStatMultiplier.value), 1);
}
}
- this.stats[s] = value;
+
+ this.setStat(s, value);
}
}
@@ -936,7 +1028,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) {
levelMoves = this.getUnlockedEggMoves().concat(levelMoves);
}
- return levelMoves.filter(lm => !this.moveset.some(m => m?.moveId === lm));
+ if (Array.isArray(this.usedTMs) && this.usedTMs.length > 0) {
+ levelMoves = this.usedTMs.filter(m => !levelMoves.includes(m)).concat(levelMoves);
+ }
+ levelMoves = levelMoves.filter(lm => !this.moveset.some(m => m?.moveId === lm));
+ return levelMoves;
}
/**
@@ -1210,6 +1306,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag));
}
+ /**
+ * Determines whether this Pokemon is prevented from running or switching due
+ * to effects from moves and/or abilities.
+ * @param trappedAbMessages `string[]` If defined, ability trigger messages
+ * (e.g. from Shadow Tag) are forwarded through this array.
+ * @param simulated `boolean` if `true`, applies abilities via simulated calls.
+ * @returns
+ */
+ isTrapped(trappedAbMessages: string[] = [], simulated: boolean = true): boolean {
+ if (this.isOfType(Type.GHOST)) {
+ return false;
+ }
+
+ const trappedByAbility = new Utils.BooleanHolder(false);
+
+ this.scene.getEnemyField()!.forEach(enemyPokemon =>
+ applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trappedByAbility, this, trappedAbMessages, simulated)
+ );
+
+ return (trappedByAbility.value || !!this.getTag(TrappedTag));
+ }
+
/**
* Calculates the type of a move when used by this Pokemon after
* type-changing move and ability attributes have applied.
@@ -1276,6 +1394,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
+ // Apply Tera Shell's effect to attacks after all immunities are accounted for
+ if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
+ applyPreDefendAbAttrs(FullHpResistTypeAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier);
+ }
+
return (!cancelledHolder.value ? typeMultiplier.value : 0) as TypeDamageMultiplier;
}
@@ -1345,7 +1468,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true);
const enemyTypes = opponent.getTypes(true, true);
/** Is this Pokemon faster than the opponent? */
- const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, opponent) : this.getStat(Stat.SPD)) >= opponent.getBattleStat(Stat.SPD, this);
+ const outspeed = (this.isActive(true) ? this.getEffectiveStat(Stat.SPD, opponent) : this.getStat(Stat.SPD, false)) >= opponent.getEffectiveStat(Stat.SPD, this);
/**
* Based on how effective this Pokemon's types are offensively against the opponent's types.
* This score is increased by 25 percent if this Pokemon is faster than the opponent.
@@ -1501,13 +1624,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
/**
- * Function that tries to set a Pokemon shiny based on the trainer's trainer ID and secret ID
+ * Function that tries to set a Pokemon shiny based on the trainer's trainer ID and secret ID.
* Endless Pokemon in the end biome are unable to be set to shiny
*
- * 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
- * The XOR of E and F are then compared to the thresholdOverride (default case 32) to see whether or not to generate a shiny
- * @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance
+ * 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.
+ * 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
+ * @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
*/
trySetShiny(thresholdOverride?: integer): boolean {
@@ -1522,7 +1646,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId;
const F = rand1 ^ rand2;
- const shinyThreshold = new Utils.IntegerHolder(32);
+ /** `64/65536 -> 1/1024` */
+ const baseShinyChance = 64;
+ const shinyThreshold = new Utils.IntegerHolder(baseShinyChance);
if (thresholdOverride === undefined) {
if (this.scene.eventManager.isEventActive()) {
shinyThreshold.value *= this.scene.eventManager.getShinyMultiplier();
@@ -1535,9 +1661,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
this.shiny = (E ^ F) < shinyThreshold.value;
- if ((E ^ F) < 32) {
- console.log("REAL SHINY!!");
- }
if (this.shiny) {
this.initShinySparkle();
@@ -1724,7 +1847,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 0.5 : 1)]);
// Trainers get a weight bump to stat buffing moves
- movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => a.levels > 1 && a.selfTarget) ? 1.25 : 1)]);
+ movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1)]);
// Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1)]);
}
@@ -1736,8 +1859,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].category === MoveCategory.STATUS ? 1 : Math.max(Math.min(allMoves[m[0]].power/maxPower, 1), 0.5))]);
// Weight damaging moves against the lower stat
- const worseCategory: MoveCategory = this.stats[Stat.ATK] > this.stats[Stat.SPATK] ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL;
- const statRatio = worseCategory === MoveCategory.PHYSICAL ? this.stats[Stat.ATK]/this.stats[Stat.SPATK] : this.stats[Stat.SPATK]/this.stats[Stat.ATK];
+ const atk = this.getStat(Stat.ATK);
+ const spAtk = this.getStat(Stat.SPATK);
+ const worseCategory: MoveCategory = atk > spAtk ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL;
+ const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk;
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].category === worseCategory ? statRatio : 1)]);
let weightMultiplier = 0.9; // The higher this is the more the game weights towards higher level moves. At 0 all moves are equal weight.
@@ -1923,6 +2048,48 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField();
}
+ /**
+ * Calculates the stat stage multiplier of the user against an opponent.
+ *
+ * Note that this does not apply to evasion or accuracy
+ * @see {@linkcode getAccuracyMultiplier}
+ * @param stat the desired {@linkcode EffectiveStat}
+ * @param opponent the target {@linkcode Pokemon}
+ * @param move the {@linkcode Move} being used
+ * @param isCritical determines whether a critical hit has occurred or not (`false` by default)
+ * @return the stat stage multiplier to be used for effective stat calculation
+ */
+ getStatStageMultiplier(stat: EffectiveStat, opponent?: Pokemon, move?: Move, isCritical: boolean = false): number {
+ const statStage = new Utils.IntegerHolder(this.getStatStage(stat));
+ const ignoreStatStage = new Utils.BooleanHolder(false);
+
+ if (opponent) {
+ if (isCritical) {
+ switch (stat) {
+ case Stat.ATK:
+ case Stat.SPATK:
+ statStage.value = Math.max(statStage.value, 0);
+ break;
+ case Stat.DEF:
+ case Stat.SPDEF:
+ statStage.value = Math.min(statStage.value, 0);
+ break;
+ }
+ }
+ applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, false, stat, ignoreStatStage);
+ if (move) {
+ applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage);
+ }
+ }
+
+ if (!ignoreStatStage.value) {
+ const statStageMultiplier = new Utils.NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value));
+ this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier);
+ return Math.min(statStageMultiplier.value, 4);
+ }
+ return 1;
+ }
+
/**
* Calculates the accuracy multiplier of the user against a target.
*
@@ -1939,34 +2106,38 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return 1;
}
- const userAccuracyLevel = new Utils.IntegerHolder(this.summonData.battleStats[BattleStat.ACC]);
- const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
+ const userAccStage = new Utils.IntegerHolder(this.getStatStage(Stat.ACC));
+ const targetEvaStage = new Utils.IntegerHolder(target.getStatStage(Stat.EVA));
- applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, false, userAccuracyLevel);
- applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, false, targetEvasionLevel);
- applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, false, targetEvasionLevel);
- applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel);
- this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel);
+ const ignoreAccStatStage = new Utils.BooleanHolder(false);
+ const ignoreEvaStatStage = new Utils.BooleanHolder(false);
+
+ applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage);
+ applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage);
+ applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage);
+
+ this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
+
+ userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6);
+ targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value;
if (target.findTag(t => t instanceof ExposedTag)) {
- targetEvasionLevel.value = Math.min(0, targetEvasionLevel.value);
+ targetEvaStage.value = Math.min(0, targetEvaStage.value);
}
const accuracyMultiplier = new Utils.NumberHolder(1);
- if (userAccuracyLevel.value !== targetEvasionLevel.value) {
- accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value
- ? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3
- : 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6));
+ if (userAccStage.value !== targetEvaStage.value) {
+ accuracyMultiplier.value = userAccStage.value > targetEvaStage.value
+ ? (3 + Math.min(userAccStage.value - targetEvaStage.value, 6)) / 3
+ : 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
}
- applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, false, sourceMove);
+ applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, false, sourceMove);
const evasionMultiplier = new Utils.NumberHolder(1);
- applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier);
+ applyStatMultiplierAbAttrs(StatMultiplierAbAttr, target, Stat.EVA, evasionMultiplier);
- accuracyMultiplier.value /= evasionMultiplier.value;
-
- return accuracyMultiplier.value;
+ return accuracyMultiplier.value / evasionMultiplier.value;
}
/**
@@ -2053,22 +2224,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (critOnly.value || critAlways) {
isCritical = true;
} else {
- const critLevel = new Utils.IntegerHolder(0);
- applyMoveAttrs(HighCritAttr, source, this, move, critLevel);
- this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critLevel);
- this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
- const bonusCrit = new Utils.BooleanHolder(false);
- //@ts-ignore
- if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus.
- if (bonusCrit.value) {
- critLevel.value += 1;
- }
- }
- if (source.getTag(BattlerTagType.CRIT_BOOST)) {
- critLevel.value += 2;
- }
- console.log(`crit stage: +${critLevel.value}`);
- const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
+ const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false;
@@ -2082,8 +2238,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false;
}
}
- const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
- const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
+ const sourceAtk = new Utils.IntegerHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
+ const targetDef = new Utils.IntegerHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, false, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1);
@@ -2494,24 +2650,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param source {@linkcode Pokemon} the pokemon whose stats/Tags are to be passed on from, ie: the Pokemon using Baton Pass
*/
transferSummon(source: Pokemon): void {
- const battleStats = Utils.getEnumValues(BattleStat);
- for (const stat of battleStats) {
- this.summonData.battleStats[stat] = source.summonData.battleStats[stat];
+ // Copy all stat stages
+ for (const s of BATTLE_STATS) {
+ const sourceStage = source.getStatStage(s);
+ if ((this instanceof PlayerPokemon) && (sourceStage === 6)) {
+ this.scene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE);
+ }
+ this.setStatStage(s, sourceStage);
}
+
for (const tag of source.summonData.tags) {
-
- // bypass those can not be passed via Baton Pass
- const excludeTagTypes = new Set([BattlerTagType.DROWSY, BattlerTagType.INFATUATED, BattlerTagType.FIRE_BOOST]);
-
- if (excludeTagTypes.has(tag.tagType)) {
+ if (!tag.isBatonPassable) {
continue;
}
this.summonData.tags.push(tag);
}
- if (this instanceof PlayerPokemon && source.summonData.battleStats.find(bs => bs === 6)) {
- this.scene.validateAchv(achvs.TRANSFER_MAX_BATTLE_STAT);
- }
+
this.updateInfo();
}
@@ -2754,6 +2909,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true, true);
+ const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
+ if (sourcePokemon && sourcePokemon !== this && this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
+ return false;
+ }
+
switch (effect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
@@ -3319,6 +3479,7 @@ export default interface Pokemon {
export class PlayerPokemon extends Pokemon {
public compatibleTms: Moves[];
+ public usedTms: Moves[];
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) {
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource);
@@ -3342,6 +3503,7 @@ export class PlayerPokemon extends Pokemon {
}
}
this.generateCompatibleTms();
+ this.usedTms = [];
}
initBattleInfo(): void {
@@ -3600,6 +3762,9 @@ export class PlayerPokemon extends Pokemon {
newPokemon.moveset = this.moveset.slice();
newPokemon.moveset = this.copyMoveset();
newPokemon.luck = this.luck;
+ newPokemon.metLevel = this.metLevel;
+ newPokemon.metBiome = this.metBiome;
+ newPokemon.metSpecies = this.metSpecies;
newPokemon.fusionSpecies = this.fusionSpecies;
newPokemon.fusionFormIndex = this.fusionFormIndex;
newPokemon.fusionAbilityIndex = this.fusionAbilityIndex;
@@ -3679,16 +3844,17 @@ export class PlayerPokemon extends Pokemon {
this.scene.gameData.gameStats.pokemonFused++;
// Store the average HP% that each Pokemon has
- const newHpPercent = ((pokemon.hp / pokemon.stats[Stat.HP]) + (this.hp / this.stats[Stat.HP])) / 2;
+ const maxHp = this.getMaxHp();
+ const newHpPercent = ((pokemon.hp / pokemon.getMaxHp()) + (this.hp / maxHp)) / 2;
this.generateName();
this.calculateStats();
// Set this Pokemon's HP to the average % of both fusion components
- this.hp = Math.round(this.stats[Stat.HP] * newHpPercent);
+ this.hp = Math.round(maxHp * newHpPercent);
if (!this.isFainted()) {
// If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum
- this.hp = Math.min(this.hp, this.stats[Stat.HP]);
+ this.hp = Math.min(this.hp, maxHp);
this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two
} else if (!pokemon.isFainted()) {
// If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero
@@ -4138,7 +4304,7 @@ export class EnemyPokemon extends Pokemon {
//console.log('damage', damage, 'segment', segmentsBypassed + 1, 'segment size', segmentSize, 'damage needed', Math.round(segmentSize * Math.pow(2, segmentsBypassed + 1)));
}
- damage = hpRemainder + Math.round(segmentSize * segmentsBypassed);
+ damage = Utils.toDmgValue(this.hp - hpThreshold + segmentSize * segmentsBypassed);
clearedBossSegmentIndex = s - segmentsBypassed;
}
break;
@@ -4181,43 +4347,40 @@ export class EnemyPokemon extends Pokemon {
handleBossSegmentCleared(segmentIndex: integer): void {
while (segmentIndex - 1 < this.bossSegmentIndex) {
- let boostedStat = BattleStat.RAND;
+ // Filter out already maxed out stat stages and weigh the rest based on existing stats
+ const leftoverStats = EFFECTIVE_STATS.filter((s: EffectiveStat) => this.getStatStage(s) < 6);
+ const statWeights = leftoverStats.map((s: EffectiveStat) => this.getStat(s, false));
- const battleStats = Utils.getEnumValues(BattleStat).slice(0, -3);
- const statWeights = new Array().fill(battleStats.length).filter((bs: BattleStat) => this.summonData.battleStats[bs] < 6).map((bs: BattleStat) => this.getStat(bs + 1));
- const statThresholds: integer[] = [];
+ let boostedStat: EffectiveStat;
+ const statThresholds: number[] = [];
let totalWeight = 0;
- for (const bs of battleStats) {
- totalWeight += statWeights[bs];
+
+ for (const i in statWeights) {
+ totalWeight += statWeights[i];
statThresholds.push(totalWeight);
}
+ // Pick a random stat from the leftover stats to increase its stages
const randInt = Utils.randSeedInt(totalWeight);
-
- for (const bs of battleStats) {
- if (randInt < statThresholds[bs]) {
- boostedStat = bs;
+ for (const i in statThresholds) {
+ if (randInt < statThresholds[i]) {
+ boostedStat = leftoverStats[i];
break;
}
}
- let statLevels = 1;
+ let stages = 1;
- switch (segmentIndex) {
- case 1:
- if (this.bossSegments >= 3) {
- statLevels++;
- }
- break;
- case 2:
- if (this.bossSegments >= 5) {
- statLevels++;
- }
- break;
+ // increase the boost if the boss has at least 3 segments and we passed last shield
+ if (this.bossSegments >= 3 && this.bossSegmentIndex === 1) {
+ stages++;
+ }
+ // increase the boost if the boss has at least 5 segments and we passed the second to last shield
+ if (this.bossSegments >= 5 && this.bossSegmentIndex === 2) {
+ stages++;
}
- this.scene.unshiftPhase(new StatChangePhase(this.scene, this.getBattlerIndex(), true, [ boostedStat ], statLevels, true, true));
-
+ this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.getBattlerIndex(), true, [ boostedStat! ], stages, true, true));
this.bossSegmentIndex--;
}
}
@@ -4274,7 +4437,7 @@ export interface TurnMove {
targets?: BattlerIndex[];
result: MoveResult;
virtual?: boolean;
- turn?: integer;
+ turn?: number;
}
export interface QueuedMove {
@@ -4286,17 +4449,17 @@ export interface QueuedMove {
export interface AttackMoveResult {
move: Moves;
result: DamageResult;
- damage: integer;
+ damage: number;
critical: boolean;
- sourceId: integer;
+ sourceId: number;
sourceBattlerIndex: BattlerIndex;
}
export class PokemonSummonData {
- public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ];
+ public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
public moveQueue: QueuedMove[] = [];
public disabledMove: Moves = Moves.NONE;
- public disabledTurns: integer = 0;
+ public disabledTurns: number = 0;
public tags: BattlerTag[] = [];
public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = [];
@@ -4306,14 +4469,14 @@ export class PokemonSummonData {
public ability: Abilities = Abilities.NONE;
public gender: Gender;
public fusionGender: Gender;
- public stats: integer[];
+ public stats: number[] = [ 0, 0, 0, 0, 0, 0 ];
public moveset: (PokemonMove | null)[];
// If not initialized this value will not be populated from save data.
public types: Type[] = [];
}
export class PokemonBattleData {
- public hitCount: integer = 0;
+ public hitCount: number = 0;
public endured: boolean = false;
public berriesEaten: BerryType[] = [];
public abilitiesApplied: Abilities[] = [];
@@ -4322,21 +4485,23 @@ export class PokemonBattleData {
export class PokemonBattleSummonData {
/** The number of turns the pokemon has passed since entering the battle */
- public turnCount: integer = 1;
+ public turnCount: number = 1;
/** The list of moves the pokemon has used since entering the battle */
public moveHistory: TurnMove[] = [];
}
export class PokemonTurnData {
- public flinched: boolean;
- public acted: boolean;
- public hitCount: integer;
- public hitsLeft: integer;
- public damageDealt: integer = 0;
- public currDamageDealt: integer = 0;
- public damageTaken: integer = 0;
+ public flinched: boolean = false;
+ public acted: boolean = false;
+ public hitCount: number;
+ public hitsLeft: number;
+ public damageDealt: number = 0;
+ public currDamageDealt: number = 0;
+ public damageTaken: number = 0;
public attacksReceived: AttackMoveResult[] = [];
public order: number;
+ public statStagesIncreased: boolean = false;
+ public statStagesDecreased: boolean = false;
}
export enum AiType {
diff --git a/src/interfaces/locales.ts b/src/interfaces/locales.ts
index 5f7c52100c1..4405095e0fe 100644
--- a/src/interfaces/locales.ts
+++ b/src/interfaces/locales.ts
@@ -37,8 +37,7 @@ export interface ModifierTypeTranslationEntries {
ModifierType: { [key: string]: ModifierTypeTranslationEntry },
SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry },
AttackTypeBoosterItem: SimpleTranslationEntries,
- TempBattleStatBoosterItem: SimpleTranslationEntries,
- TempBattleStatBoosterStatName: SimpleTranslationEntries,
+ TempStatStageBoosterItem: SimpleTranslationEntries,
BaseStatBoosterItem: SimpleTranslationEntries,
EvolutionItem: SimpleTranslationEntries,
FormChangeItem: SimpleTranslationEntries,
diff --git a/src/loading-scene.ts b/src/loading-scene.ts
index b086b0cb002..f6bc41f744d 100644
--- a/src/loading-scene.ts
+++ b/src/loading-scene.ts
@@ -41,8 +41,6 @@ export class LoadingScene extends SceneBase {
this.loadImage("loading_bg", "arenas");
this.loadImage("logo", "");
- // this.loadImage("pride-update", "events");
- this.loadImage("august-variant-update", "events");
// Load menu images
this.loadAtlas("bg", "ui");
@@ -80,6 +78,7 @@ export class LoadingScene extends SceneBase {
this.loadAtlas("overlay_hp_boss", "ui");
this.loadImage("overlay_exp", "ui");
this.loadImage("icon_owned", "ui");
+ this.loadImage("icon_egg_move", "ui");
this.loadImage("ability_bar_left", "ui");
this.loadImage("bgm_bar", "ui");
this.loadImage("party_exp_bar", "ui");
@@ -100,6 +99,8 @@ export class LoadingScene extends SceneBase {
this.loadImage("ha_capsule", "ui", "ha_capsule.png");
this.loadImage("champion_ribbon", "ui", "champion_ribbon.png");
this.loadImage("icon_spliced", "ui");
+ this.loadImage("icon_lock", "ui", "icon_lock.png");
+ this.loadImage("icon_stop", "ui", "icon_stop.png");
this.loadImage("icon_tera", "ui");
this.loadImage("type_tera", "ui");
this.loadAtlas("type_bgs", "ui");
@@ -246,7 +247,12 @@ export class LoadingScene extends SceneBase {
} else {
this.loadAtlas("types", "");
}
-
+ const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"];
+ if (lang && availableLangs.includes(lang)) {
+ this.loadImage("september-update-"+lang, "events");
+ } else {
+ this.loadImage("september-update-en", "events");
+ }
this.loadAtlas("statuses", "");
this.loadAtlas("categories", "");
@@ -267,6 +273,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("gacha_knob", "egg");
this.loadImage("egg_list_bg", "ui");
+ this.loadImage("egg_summary_bg", "ui");
this.loadImage("end_m", "cg");
this.loadImage("end_f", "cg");
diff --git a/src/locales/de/achv.json b/src/locales/de/achv.json
index d2e56089720..21a1d89f9d6 100644
--- a/src/locales/de/achv.json
+++ b/src/locales/de/achv.json
@@ -89,7 +89,7 @@
"name": "Bänder-Meister",
"name_female": "Bänder-Meisterin"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "Teamwork",
"description": "Nutze Staffette, während der Anwender mindestens eines Statuswertes maximiert hat."
},
@@ -274,4 +274,4 @@
"name": "Spieglein, Spieglein an der Wand",
"description": "Schließe die 'Umkehrkampf' Herausforderung ab"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/de/arena-tag.json b/src/locales/de/arena-tag.json
index 454effae60c..3bed4fefbd0 100644
--- a/src/locales/de/arena-tag.json
+++ b/src/locales/de/arena-tag.json
@@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "Der Rückenwind auf deiner Seite hat sich gelegt!",
"tailwindOnRemoveEnemy": "Der Rückenwind auf gegnerischer Seite hat sich gelegt!",
"happyHourOnAdd": "Goldene Zeiten sind angebrochen!",
- "happyHourOnRemove": "Die goldenen Zeiten sind vorbei!"
+ "happyHourOnRemove": "Die goldenen Zeiten sind vorbei!",
+ "safeguardOnAdd": "Das ganze Feld wird von einem Schleier umhüllt!",
+ "safeguardOnAddPlayer": "Das Team des Anwenders wird von einem Schleier umhüllt!",
+ "safeguardOnAddEnemy": "Das gegnerische Team wird von einem Schleier umhüllt!",
+ "safeguardOnRemove": "Der mystische Schleier, der das ganze Feld umgab, hat sich gelüftet!",
+ "safeguardOnRemovePlayer": "Der mystische Schleier, der dein Team umgab, hat sich gelüftet!",
+ "safeguardOnRemoveEnemy": "Der mystische Schleier, der das gegnerische Team umgab, hat sich gelüftet!"
}
\ No newline at end of file
diff --git a/src/locales/de/battle.json b/src/locales/de/battle.json
index 762b5848439..05205b001b6 100644
--- a/src/locales/de/battle.json
+++ b/src/locales/de/battle.json
@@ -94,5 +94,6 @@
"retryBattle": "Möchtest du vom Beginn des Kampfes neustarten?",
"unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.",
"congratulations": "Glückwunsch!",
- "beatModeFirstTime": "{{speciesName}} hat den {{gameMode}} Modus zum ersten Mal beendet! Du erhältst {{newModifier}}!"
+ "beatModeFirstTime": "{{speciesName}} hat den {{gameMode}} Modus zum ersten Mal beendet! Du erhältst {{newModifier}}!",
+ "eggSkipPrompt": "Zur Ei-Zusammenfassung springen?"
}
\ No newline at end of file
diff --git a/src/locales/de/challenges.json b/src/locales/de/challenges.json
index 17c33353bc6..c8836c50549 100644
--- a/src/locales/de/challenges.json
+++ b/src/locales/de/challenges.json
@@ -1,10 +1,11 @@
{
+ "noneSelected": "Keine ausgewählt",
"title": "Herausforderungsmodifikatoren",
"illegalEvolution": "{{pokemon}} hat sich in ein Pokémon verwandelt, dass für diese Herausforderung nicht zulässig ist!",
"singleGeneration": {
"name": "Mono-Generation",
"desc": "Du kannst nur Pokémon aus der {{gen}} Generation verwenden.",
- "desc_default": "Du kannst nur Pokémon gewählten Generation verwenden.",
+ "desc_default": "Du kannst nur Pokémon aus der gewählten Generation verwenden.",
"gen_1": "ersten",
"gen_2": "zweiten",
"gen_3": "dritten",
diff --git a/src/locales/de/dialogue-misc.json b/src/locales/de/dialogue-misc.json
index 1529831d7c5..69c704c66c6 100644
--- a/src/locales/de/dialogue-misc.json
+++ b/src/locales/de/dialogue-misc.json
@@ -1,6 +1,6 @@
{
- "ending": "@c{smile}Oh? Du hast gewonnen?@d{96} @c{smile_eclosed}Ich schätze, das hätte ich wissen sollen.\n$Aber, du bist jetzt zurück.\n$@c{smile}Es ist vorbei.@d{64} Du hast die Schleife beendet.\n$@c{serious_smile_fists}Du hast auch deinen Traum erfüllt, nicht wahr?\nDu hast nicht einmal verloren.\n$@c{neutral}Ich bin der Einzige, der sich daran erinnern wird, was du getan hast.@d{96}\n$Ich schätze, das ist in Ordnung, oder?\n$@c{serious_smile_fists}Deine Legende wird immer in unseren Herzen weiterleben.\n$@c{smile_eclosed}Wie auch immer, ich habe genug von diesem Ort, oder nicht? Lass uns nach Hause gehen.\n$@c{serious_smile_fists}Vielleicht können wir, wenn wir zurück sind, noch einen Kampf haben?\n$Wenn du dazu bereit bist.",
- "ending_female": "@c{shock}Du bist zurück?@d{32} Bedeutet das…@d{96} du hast gewonnen?!\n$@c{smile_ehalf}Ich hätte wissen sollen, dass du es in dir hast.\n$@c{smile_eclosed}Natürlich… ich hatte immer dieses Gefühl.\n$@c{smile}Es ist jetzt vorbei, richtig? Du hast die Schleife beendet.\n$@c{smile_ehalf}Du hast auch deinen Traum erfüllt, nicht wahr?\n$Du hast nicht einmal verloren.\n$Ich werde die Einzige sein, die sich daran erinnert, was du getan hast.\n$@c{angry_mopen}Ich werde versuchen, es nicht zu vergessen!\n$@c{smile_wave_wink}Nur ein Scherz!@d{64} @c{smile}Ich würde es nie vergessen.@d{32}\n$Deine Legende wird in unseren Herzen weiterleben.\n$@c{smile_wave}Wie auch immer,@d{64} es wird spät…@d{96} denke ich?\nEs ist schwer zu sagen an diesem Ort.\n$Lass uns nach Hause gehen. \n$@c{smile_wave_wink}Vielleicht können wir morgen noch einen Kampf haben, der alten Zeiten willen?",
+ "ending": "@c{shock}Du bist zurück?@d{32} Bedeutet das…@d{96} du hast gewonnen?!\n$@c{smile_ehalf}Ich hätte wissen sollen, dass du es in dir hast.\n$@c{smile_eclosed}Natürlich… ich hatte immer dieses Gefühl.\n$@c{smile}Es ist jetzt vorbei, richtig? Du hast die Schleife beendet.\n$@c{smile_ehalf}Du hast auch deinen Traum erfüllt, nicht wahr?\n$Du hast nicht einmal verloren.\n$Ich werde die Einzige sein, die sich daran erinnert, was du getan hast.\n$@c{angry_mopen}Ich werde versuchen, es nicht zu vergessen!\n$@c{smile_wave_wink}Nur ein Scherz!@d{64} @c{smile}Ich würde es nie vergessen.@d{32}\n$Deine Legende wird in unseren Herzen weiterleben.\n$@c{smile_wave}Wie auch immer,@d{64} es wird spät…@d{96} denke ich?\nEs ist schwer zu sagen an diesem Ort.\n$Lass uns nach Hause gehen. \n$@c{smile_wave_wink}Vielleicht können wir morgen noch einen Kampf haben, der alten Zeiten willen?",
+ "ending_female": "@c{smile}Oh? Du hast gewonnen?@d{96} @c{smile_eclosed}Ich schätze, das hätte ich wissen sollen.\n$Aber, du bist jetzt zurück.\n$@c{smile}Es ist vorbei.@d{64} Du hast die Schleife beendet.\n$@c{serious_smile_fists}Du hast auch deinen Traum erfüllt, nicht wahr?\nDu hast nicht einmal verloren.\n$@c{neutral}Ich bin der Einzige, der sich daran erinnern wird, was du getan hast.@d{96}\n$Ich schätze, das ist in Ordnung, oder?\n$@c{serious_smile_fists}Deine Legende wird immer in unseren Herzen weiterleben.\n$@c{smile_eclosed}Wie auch immer, ich habe genug von diesem Ort, oder nicht? Lass uns nach Hause gehen.\n$@c{serious_smile_fists}Vielleicht können wir, wenn wir zurück sind, noch einen Kampf haben?\n$Wenn du dazu bereit bist.",
"ending_endless": "Glückwunsch! Du hast das aktuelle Ende erreicht!\nWir arbeiten an mehr Spielinhalten.",
"ending_name": "Entwickler"
}
diff --git a/src/locales/de/dialogue.json b/src/locales/de/dialogue.json
index e5bcb81ce52..8a3dbb8880e 100644
--- a/src/locales/de/dialogue.json
+++ b/src/locales/de/dialogue.json
@@ -1403,19 +1403,19 @@
"1": "Ich muss dein Potenzial als Trainer und die Stärke der Pokémon sehen, die mit dir kämpfen!",
"2": "Los geht's! Dies sind meine Gesteins-Pokémon, mein ganzer Stolz!",
"3": "Gesteins-Pokémon sind einfach die besten!",
- "4": "Ich muss dein Potenzial als Trainer und die Stärke der Pokémon sehen, die mit dir kämpfen!"
+ "4": "Tag für Tag grabe ich hier nach Fossilien.\n$Die viele Arbeit hat meine Pokémon felsenfest gemacht\nund das wirst du jetzt im Kampf zu spüren bekommen!"
},
"victory": {
"1": "W-was? Das kann nicht sein! Meine total tranierten Pokémon!",
"2": "…Wir haben die Kontrolle verloren. Beim nächsten Mal fordere ich dich\n$zu einem Fossilien-Ausgrabungswettbewerb heraus.",
"3": "Mit deinem Können ist es nur natürlich, dass du gewinnst.",
- "4": "W-was?! Das kann nicht sein! Selbst das war nicht genug?",
- "5": "Ich habe es vermasselt."
+ "4": "W-was?! Das kann nicht sein! Selbst das war nicht genug?"
},
"defeat": {
"1": "Siehst du? Ich bin stolz auf meinen steinigen Kampfstil!",
"2": "Danke! Der Kampf hat mir Vertrauen gegeben, dass ich vielleicht meinen Vater besiegen kann!",
- "3": "Ich fühle mich, als hätte ich gerade einen wirklich hartnäckigen Felsen durchbrochen!"
+ "3": "Na, was sagst du jetzt? Meine felsenfesten Pokémon waren hart genug für dich, was?",
+ "4": "Ich wusste, dass ich gewinnen würde!"
}
},
"morty": {
diff --git a/src/locales/de/menu-ui-handler.json b/src/locales/de/menu-ui-handler.json
index 56c03102b9c..93c3f4c38e8 100644
--- a/src/locales/de/menu-ui-handler.json
+++ b/src/locales/de/menu-ui-handler.json
@@ -25,5 +25,6 @@
"unlinkGoogle": "Google trennen",
"cancel": "Abbrechen",
"losingProgressionWarning": "Du wirst jeglichen Fortschritt seit Anfang dieses Kampfes verlieren. Fortfahren?",
- "noEggs": "Du brütest aktuell keine Eier aus!"
+ "noEggs": "Du brütest aktuell keine Eier aus!",
+ "donate": "Spenden"
}
\ No newline at end of file
diff --git a/src/locales/de/modifier-type.json b/src/locales/de/modifier-type.json
index c9927636c2a..8e2372cb447 100644
--- a/src/locales/de/modifier-type.json
+++ b/src/locales/de/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "Verdoppelt die Wahrscheinlichkeit, dass die nächsten {{battleCount}} Begegnungen mit wilden Pokémon ein Doppelkampf sind."
},
- "TempBattleStatBoosterModifierType": {
- "description": "Erhöht die {{tempBattleStatName}} aller Teammitglieder für 5 Kämpfe um eine Stufe."
+ "TempStatStageBoosterModifierType": {
+ "description": "Erhöht die {{stat}} aller Teammitglieder für 5 Kämpfe um eine Stufe."
},
"AttackTypeBoosterModifierType": {
"description": "Erhöht die Stärke aller {{moveType}}-Attacken eines Pokémon um 20%."
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "Erhöht das Level aller Teammitglieder um {{levels}}."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "Erhöht den {{statName}} Basiswert des Trägers um 10%. Das Stapellimit erhöht sich, je höher dein IS-Wert ist."
+ "BaseStatBoosterModifierType": {
+ "description": "Erhöht den {{stat}} Basiswert des Trägers um 10%. Das Stapellimit erhöht sich, je höher dein IS-Wert ist."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "Stellt 100% der KP aller Pokémon her."
@@ -248,6 +248,12 @@
"name": "Scope-Linse",
"description": "Ein Item zum Tragen. Es erhöht die Volltrefferquote."
},
+ "DIRE_HIT": {
+ "name": "X-Volltreffer",
+ "extra": {
+ "raises": "Volltrefferquote"
+ }
+ },
"LEEK": {
"name": "Lauchstange",
"description": "Ein Item, das von Porenta getragen werden kann. Diese lange Lauchstange erhöht die Volltrefferquote stark."
@@ -411,25 +417,13 @@
"description": "Ein Item, das Ditto zum Tragen gegeben werden kann. Fein und doch hart, erhöht dieses sonderbare Pulver die Initiative."
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "X-Angriff",
"x_defense": "X-Verteidigung",
"x_sp_atk": "X-Sp.-Ang.",
"x_sp_def": "X-Sp.-Vert.",
"x_speed": "X-Tempo",
- "x_accuracy": "X-Treffer",
- "dire_hit": "X-Volltreffer"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "Angriff",
- "DEF": "Verteidigung",
- "SPATK": "Sp. Ang",
- "SPDEF": "Sp. Vert",
- "SPD": "Initiative",
- "ACC": "Genauigkeit",
- "CRIT": "Volltrefferquote",
- "EVA": "Fluchtwert",
- "DEFAULT": "???"
+ "x_accuracy": "X-Treffer"
},
"AttackTypeBoosterItem": {
"silk_scarf": "Seidenschal",
@@ -604,6 +598,6 @@
"DRAGON_MEMORY": "Drachen-Disc",
"DARK_MEMORY": "Unlicht-Disc",
"FAIRY_MEMORY": "Feen-Disc",
- "BLANK_MEMORY": "Leere-Disc"
+ "NORMAL_MEMORY": "Normal-Disc"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/de/modifier.json b/src/locales/de/modifier.json
index 22053b1da63..37227973410 100644
--- a/src/locales/de/modifier.json
+++ b/src/locales/de/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!",
"hitHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} wurde durch {{typeName}} wiederbelebt!",
- "pokemonResetNegativeStatStageApply": "Die negative Statuswertveränderung von {{pokemonNameWithAffix}} wurde durch {{typeName}} aufgehoben!",
+ "resetNegativeStatStageApply": "Die negative Statuswertveränderung von {{pokemonNameWithAffix}} wurde durch {{typeName}} aufgehoben!",
"moneyInterestApply": "Du erhählst {{moneyAmount}} ₽ durch das Item {{typeName}}!",
"turnHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} absorbiert!",
"contactHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} geklaut!",
diff --git a/src/locales/de/move-trigger.json b/src/locales/de/move-trigger.json
index 5b2b2471df9..61283c9e62e 100644
--- a/src/locales/de/move-trigger.json
+++ b/src/locales/de/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}} nutzt seine KP um seine Attacke zu verstärken!",
"absorbedElectricity": "{{pokemonName}} absorbiert elektrische Energie!",
"switchedStatChanges": "{{pokemonName}} tauschte die Statuswerteveränderungen mit dem Ziel!",
+ "switchedTwoStatChanges": "{{pokemonName}} tauscht Veränderungen an {{firstStat}} und {{secondStat}} mit dem Ziel!",
+ "switchedStat": "{{pokemonName}} tauscht seinen {{stat}}-Wert mit dem des Zieles!",
+ "sharedGuard": "{{pokemonName}} addiert seine Schutzkräfte mit jenen des Zieles und teilt sie gerecht auf!",
+ "sharedPower": "{{pokemonName}} addiert seine Kräfte mit jenen des Zieles und teilt sie gerecht auf!",
"goingAllOutForAttack": "{{pokemonName}} legt sich ins Zeug!",
"regainedHealth": "{{pokemonName}} erholt sich!",
"keptGoingAndCrashed": "{{pokemonName}} springt daneben und verletzt sich!",
@@ -61,5 +65,6 @@
"suppressAbilities": "Die Fähigkeit von {{pokemonName}} wirkt nicht mehr!",
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
- "exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!"
-}
\ No newline at end of file
+ "exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
+ "safeguard": "{{targetName}} wird durch Bodyguard geschützt!"
+}
diff --git a/src/locales/de/pokemon-form-battle.json b/src/locales/de/pokemon-form-battle.json
index 8651b3d1318..35060c33d0b 100644
--- a/src/locales/de/pokemon-form-battle.json
+++ b/src/locales/de/pokemon-form-battle.json
@@ -10,5 +10,5 @@
"eternamaxChange": "{{preName}} hat sich zu {{pokemonName}} unendynamaximiert!",
"revertChange": "{{pokemonName}} hat seine ursprüngliche Form zurückerlangt!",
"formChange": "{{preName}} hat seine Form geändert!",
- "disguiseChange": "Its disguise served it as a decoy!"
+ "disguiseChange": "Sein Kostüm hat die Attacke absorbiert!"
}
\ No newline at end of file
diff --git a/src/locales/de/pokemon-info.json b/src/locales/de/pokemon-info.json
index a559001f663..2d625d52ba7 100644
--- a/src/locales/de/pokemon-info.json
+++ b/src/locales/de/pokemon-info.json
@@ -1,7 +1,6 @@
{
"Stat": {
"HP": "KP",
- "HPStat": "KP",
"HPshortened": "KP",
"ATK": "Angriff",
"ATKshortened": "Ang",
diff --git a/src/locales/de/settings.json b/src/locales/de/settings.json
index d72a026cf5a..31406f28d17 100644
--- a/src/locales/de/settings.json
+++ b/src/locales/de/settings.json
@@ -100,7 +100,7 @@
"moveTouchControls": "Bewegung Touch Steuerung",
"shopOverlayOpacity": "Shop Overlay Deckkraft",
"shopCursorTarget": "Shop-Cursor Ziel",
- "items": "Items",
+ "rewards": "Items",
"reroll": "Neu rollen",
"shop": "Shop",
"checkTeam": "Team überprüfen"
diff --git a/src/locales/en/ability-trigger.json b/src/locales/en/ability-trigger.json
index 307ab70b85c..4f1d4dac766 100644
--- a/src/locales/en/ability-trigger.json
+++ b/src/locales/en/ability-trigger.json
@@ -12,6 +12,7 @@
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
+ "fullHpResistType": "{{pokemonNameWithAffix}} made its shell gleam!\nIt's distorting type matchups!",
"moveImmunity": "It doesn't affect {{pokemonNameWithAffix}}!",
"reverseDrain": "{{pokemonNameWithAffix}} sucked up the liquid ooze!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}'s {{abilityName}}\nmade it the {{typeName}} type!",
diff --git a/src/locales/en/achv-female.json b/src/locales/en/achv-female.json
deleted file mode 100644
index edcd8c53fb7..00000000000
--- a/src/locales/en/achv-female.json
+++ /dev/null
@@ -1,268 +0,0 @@
-{
- "Achievements": {
- "name": "Achievements"
- },
- "Locked": {
- "name": "Locked"
- },
- "MoneyAchv": {
- "description": "Accumulate a total of ₽{{moneyAmount}}"
- },
- "10K_MONEY": {
- "name": "Money Haver"
- },
- "100K_MONEY": {
- "name": "Rich"
- },
- "1M_MONEY": {
- "name": "Millionaire"
- },
- "10M_MONEY": {
- "name": "One Percenter"
- },
- "DamageAchv": {
- "description": "Inflict {{damageAmount}} damage in one hit"
- },
- "250_DMG": {
- "name": "Hard Hitter"
- },
- "1000_DMG": {
- "name": "Harder Hitter"
- },
- "2500_DMG": {
- "name": "That's a Lotta Damage!"
- },
- "10000_DMG": {
- "name": "One Punch Man"
- },
- "HealAchv": {
- "description": "Heal {{healAmount}} {{HP}} at once with a move, ability, or held item"
- },
- "250_HEAL": {
- "name": "Novice Healer"
- },
- "1000_HEAL": {
- "name": "Big Healer"
- },
- "2500_HEAL": {
- "name": "Cleric"
- },
- "10000_HEAL": {
- "name": "Recovery Master"
- },
- "LevelAchv": {
- "description": "Level up a Pokémon to Lv{{level}}"
- },
- "LV_100": {
- "name": "But Wait, There's More!"
- },
- "LV_250": {
- "name": "Elite"
- },
- "LV_1000": {
- "name": "To Go Even Further Beyond"
- },
- "RibbonAchv": {
- "description": "Accumulate a total of {{ribbonAmount}} Ribbons"
- },
- "10_RIBBONS": {
- "name": "Pokémon League Champion"
- },
- "25_RIBBONS": {
- "name": "Great League Champion"
- },
- "50_RIBBONS": {
- "name": "Ultra League Champion"
- },
- "75_RIBBONS": {
- "name": "Rogue League Champion"
- },
- "100_RIBBONS": {
- "name": "Master League Champion"
- },
- "TRANSFER_MAX_BATTLE_STAT": {
- "name": "Teamwork",
- "description": "Baton pass to another party member with at least one stat maxed out"
- },
- "MAX_FRIENDSHIP": {
- "name": "Friendmaxxing",
- "description": "Reach max friendship on a Pokémon"
- },
- "MEGA_EVOLVE": {
- "name": "Megamorph",
- "description": "Mega evolve a Pokémon"
- },
- "GIGANTAMAX": {
- "name": "Absolute Unit",
- "description": "Gigantamax a Pokémon"
- },
- "TERASTALLIZE": {
- "name": "STAB Enthusiast",
- "description": "Terastallize a Pokémon"
- },
- "STELLAR_TERASTALLIZE": {
- "name": "The Hidden Type",
- "description": "Stellar Terastallize a Pokémon"
- },
- "SPLICE": {
- "name": "Infinite Fusion",
- "description": "Splice two Pokémon together with DNA Splicers"
- },
- "MINI_BLACK_HOLE": {
- "name": "A Hole Lot of Items",
- "description": "Acquire a Mini Black Hole"
- },
- "CATCH_MYTHICAL": {
- "name": "Mythical",
- "description": "Catch a mythical Pokémon"
- },
- "CATCH_SUB_LEGENDARY": {
- "name": "(Sub-)Legendary",
- "description": "Catch a sub-legendary Pokémon"
- },
- "CATCH_LEGENDARY": {
- "name": "Legendary",
- "description": "Catch a legendary Pokémon"
- },
- "SEE_SHINY": {
- "name": "Shiny",
- "description": "Find a shiny Pokémon in the wild"
- },
- "SHINY_PARTY": {
- "name": "That's Dedication",
- "description": "Have a full party of shiny Pokémon"
- },
- "HATCH_MYTHICAL": {
- "name": "Mythical Egg",
- "description": "Hatch a mythical Pokémon from an egg"
- },
- "HATCH_SUB_LEGENDARY": {
- "name": "Sub-Legendary Egg",
- "description": "Hatch a sub-legendary Pokémon from an egg"
- },
- "HATCH_LEGENDARY": {
- "name": "Legendary Egg",
- "description": "Hatch a legendary Pokémon from an egg"
- },
- "HATCH_SHINY": {
- "name": "Shiny Egg",
- "description": "Hatch a shiny Pokémon from an egg"
- },
- "HIDDEN_ABILITY": {
- "name": "Hidden Potential",
- "description": "Catch a Pokémon with a hidden ability"
- },
- "PERFECT_IVS": {
- "name": "Certificate of Authenticity",
- "description": "Get perfect IVs on a Pokémon"
- },
- "CLASSIC_VICTORY": {
- "name": "Undefeated",
- "description": "Beat the game in classic mode"
- },
- "UNEVOLVED_CLASSIC_VICTORY": {
- "name": "Bring Your Child To Work Day",
- "description": "Beat the game in Classic Mode with at least one unevolved party member."
- },
- "MONO_GEN_ONE": {
- "name": "The Original Rival",
- "description": "Complete the generation one only challenge."
- },
- "MONO_GEN_TWO": {
- "name": "Generation 1.5",
- "description": "Complete the generation two only challenge."
- },
- "MONO_GEN_THREE": {
- "name": "Too much water?",
- "description": "Complete the generation three only challenge."
- },
- "MONO_GEN_FOUR": {
- "name": "Is she really the hardest?",
- "description": "Complete the generation four only challenge."
- },
- "MONO_GEN_FIVE": {
- "name": "All Original",
- "description": "Complete the generation five only challenge."
- },
- "MONO_GEN_SIX": {
- "name": "Almost Royalty",
- "description": "Complete the generation six only challenge."
- },
- "MONO_GEN_SEVEN": {
- "name": "Only Technically",
- "description": "Complete the generation seven only challenge."
- },
- "MONO_GEN_EIGHT": {
- "name": "A Champion Time!",
- "description": "Complete the generation eight only challenge."
- },
- "MONO_GEN_NINE": {
- "name": "She was going easy on you",
- "description": "Complete the generation nine only challenge."
- },
- "MonoType": {
- "description": "Complete the {{type}} monotype challenge."
- },
- "MONO_NORMAL": {
- "name": "Extra Ordinary"
- },
- "MONO_FIGHTING": {
- "name": "I Know Kung Fu"
- },
- "MONO_FLYING": {
- "name": "Angry Birds"
- },
- "MONO_POISON": {
- "name": "Kanto's Favourite"
- },
- "MONO_GROUND": {
- "name": "Forecast: Earthquakes"
- },
- "MONO_ROCK": {
- "name": "Brock Hard"
- },
- "MONO_BUG": {
- "name": "You Like Jazz?"
- },
- "MONO_GHOST": {
- "name": "Who You Gonna Call?"
- },
- "MONO_STEEL": {
- "name": "Iron Giant"
- },
- "MONO_FIRE": {
- "name": "I Cast Fireball!"
- },
- "MONO_WATER": {
- "name": "When It Rains, It Pours"
- },
- "MONO_GRASS": {
- "name": "Can't Touch This"
- },
- "MONO_ELECTRIC": {
- "name": "Aim For The Horn!"
- },
- "MONO_PSYCHIC": {
- "name": "Big Brain Energy"
- },
- "MONO_ICE": {
- "name": "Walking On Thin Ice"
- },
- "MONO_DRAGON": {
- "name": "Pseudo-Legend Club"
- },
- "MONO_DARK": {
- "name": "It's Just A Phase"
- },
- "MONO_FAIRY": {
- "name": "Hey! Listen!"
- },
- "FRESH_START": {
- "name": "First Try!",
- "description": "Complete the Fresh Start challenge."
- },
- "INVERSE_BATTLE": {
- "name": "Mirror rorriM",
- "description": "Complete the Inverse Battle challenge.\n.egnellahc elttaB esrevnI eht etelpmoC"
- }
-}
\ No newline at end of file
diff --git a/src/locales/en/achv.json b/src/locales/en/achv.json
index 0ed746c77b3..32d519fbf78 100644
--- a/src/locales/en/achv.json
+++ b/src/locales/en/achv.json
@@ -10,19 +10,19 @@
},
"10K_MONEY": {
"name": "Money Haver",
- "name_female": null
+ "name_female": "Money Haver"
},
"100K_MONEY": {
"name": "Rich",
- "name_female": null
+ "name_female": "Rich"
},
"1M_MONEY": {
"name": "Millionaire",
- "name_female": null
+ "name_female": "Millionaire"
},
"10M_MONEY": {
"name": "One Percenter",
- "name_female": null
+ "name_female": "One Percenter"
},
"DamageAchv": {
"description": "Inflict {{damageAmount}} damage in one hit"
@@ -32,11 +32,11 @@
},
"1000_DMG": {
"name": "Harder Hitter",
- "name_female": null
+ "name_female": "Harder Hitter"
},
"2500_DMG": {
"name": "That's a Lotta Damage!",
- "name_female": null
+ "name_female": "That's a Lotta Damage!"
},
"10000_DMG": {
"name": "One Punch Man",
@@ -47,19 +47,19 @@
},
"250_HEAL": {
"name": "Novice Healer",
- "name_female": null
+ "name_female": "Novice Healer"
},
"1000_HEAL": {
"name": "Big Healer",
- "name_female": null
+ "name_female": "Big Healer"
},
"2500_HEAL": {
"name": "Cleric",
- "name_female": null
+ "name_female": "Cleric"
},
"10000_HEAL": {
"name": "Recovery Master",
- "name_female": null
+ "name_female": "Recovery Master"
},
"LevelAchv": {
"description": "Level up a Pokémon to Lv{{level}}"
@@ -69,7 +69,7 @@
},
"LV_250": {
"name": "Elite",
- "name_female": null
+ "name_female": "Elite"
},
"LV_1000": {
"name": "To Go Even Further Beyond"
@@ -79,27 +79,27 @@
},
"10_RIBBONS": {
"name": "Pokémon League Champion",
- "name_female": null
+ "name_female": "Pokémon League Champion"
},
"25_RIBBONS": {
"name": "Great League Champion",
- "name_female": null
+ "name_female": "Great League Champion"
},
"50_RIBBONS": {
"name": "Ultra League Champion",
- "name_female": null
+ "name_female": "Ultra League Champion"
},
"75_RIBBONS": {
"name": "Rogue League Champion",
- "name_female": null
+ "name_female": "Rogue League Champion"
},
"100_RIBBONS": {
"name": "Master League Champion",
- "name_female": null
+ "name_female": "Master League Champion"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "Teamwork",
- "description": "Baton pass to another party member with at least one stat maxed out"
+ "description": "Baton pass to another party member with at least one stat stage maxed out"
},
"MAX_FRIENDSHIP": {
"name": "Friendmaxxing",
@@ -147,7 +147,7 @@
},
"SHINY_PARTY": {
"name": "That's Dedication",
- "name_female": null,
+ "name_female": "That's Dedication",
"description": "Have a full party of shiny Pokémon"
},
"HATCH_MYTHICAL": {
@@ -176,7 +176,7 @@
},
"CLASSIC_VICTORY": {
"name": "Undefeated",
- "name_female": null,
+ "name_female": "Undefeated",
"description": "Beat the game in classic mode"
},
"UNEVOLVED_CLASSIC_VICTORY": {
@@ -284,4 +284,4 @@
"name": "Mirror rorriM",
"description": "Complete the Inverse Battle challenge.\n.egnellahc elttaB esrevnI eht etelpmoC"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/en/arena-flyout.json b/src/locales/en/arena-flyout.json
index 141ed4f743d..043d4127eb8 100644
--- a/src/locales/en/arena-flyout.json
+++ b/src/locales/en/arena-flyout.json
@@ -39,5 +39,6 @@
"matBlock": "Mat Block",
"craftyShield": "Crafty Shield",
"tailwind": "Tailwind",
- "happyHour": "Happy Hour"
-}
+ "happyHour": "Happy Hour",
+ "safeguard": "Safeguard"
+}
\ No newline at end of file
diff --git a/src/locales/en/arena-tag.json b/src/locales/en/arena-tag.json
index ef0b55b691b..d8fed386b24 100644
--- a/src/locales/en/arena-tag.json
+++ b/src/locales/en/arena-tag.json
@@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "Your team's Tailwind petered out!",
"tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!",
"happyHourOnAdd": "Everyone is caught up in the happy atmosphere!",
- "happyHourOnRemove": "The atmosphere returned to normal."
+ "happyHourOnRemove": "The atmosphere returned to normal.",
+ "safeguardOnAdd": "The whole field is cloaked in a mystical veil!",
+ "safeguardOnAddPlayer": "Your team cloaked itself in a mystical veil!",
+ "safeguardOnAddEnemy": "The opposing team cloaked itself in a mystical veil!",
+ "safeguardOnRemove": "The field is no longer protected by Safeguard!",
+ "safeguardOnRemovePlayer": "Your team is no longer protected by Safeguard!",
+ "safeguardOnRemoveEnemy": "The opposing team is no longer protected by Safeguard!"
}
\ No newline at end of file
diff --git a/src/locales/en/battle.json b/src/locales/en/battle.json
index 662678e7673..918fb38b520 100644
--- a/src/locales/en/battle.json
+++ b/src/locales/en/battle.json
@@ -61,6 +61,7 @@
"skipItemQuestion": "Are you sure you want to skip taking an item?",
"itemStackFull": "The stack for {{fullItemName}} is full.\nYou will receive {{itemName}} instead.",
"eggHatching": "Oh?",
+ "eggSkipPrompt": "Skip to egg summary?",
"ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?",
"wildPokemonWithAffix": "Wild {{pokemonName}}",
"foePokemonWithAffix": "Foe {{pokemonName}}",
diff --git a/src/locales/en/challenges.json b/src/locales/en/challenges.json
index f189266cea2..7d330401407 100644
--- a/src/locales/en/challenges.json
+++ b/src/locales/en/challenges.json
@@ -1,6 +1,7 @@
{
"title": "Challenge Modifiers",
"illegalEvolution": "{{pokemon}} changed into an ineligble pokémon\nfor this challenge!",
+ "noneSelected": "None Selected",
"singleGeneration": {
"name": "Mono Gen",
"desc": "You can only use Pokémon from Generation {{gen}}.",
diff --git a/src/locales/en/dialogue-double-battle.json b/src/locales/en/dialogue-double-battle.json
index 9484aa2edcc..4190af49d15 100644
--- a/src/locales/en/dialogue-double-battle.json
+++ b/src/locales/en/dialogue-double-battle.json
@@ -58,7 +58,7 @@
"iris_alder_double": {
"encounter": {
"1": "Iris: Welcome Challenger! I am THE Unova Champion!\n$Alder: Iris, aren't you a bit too excited?",
- "1_female": null
+ "1_female": "Iris: Welcome Challenger! I am THE Unova Champion!\n$Alder: Iris, aren't you a bit too excited?"
},
"victory": {
"1": "Iris: A loss like this is not easy to take...\n$Alder: But we will only get stronger with every loss!"
@@ -75,7 +75,7 @@
"marnie_piers_double": {
"encounter": {
"1": "Piers: Ready for a concert?\n$Marnie: Brother... They are here to fight, not to sing...",
- "1_female": null
+ "1_female": "Piers: Ready for a concert?\n$Marnie: Brother... They are here to fight, not to sing..."
},
"victory": {
"1": "Piers: Now that was a great concert!\n$Marnie: Brother..."
diff --git a/src/locales/en/dialogue-final-boss.json b/src/locales/en/dialogue-final-boss.json
index 3abe4cd8831..6f99aae3e0c 100644
--- a/src/locales/en/dialogue-final-boss.json
+++ b/src/locales/en/dialogue-final-boss.json
@@ -1,6 +1,6 @@
{
"encounter": "It appears the time has finally come once again.\nYou know why you have come here, do you not?\n$You were drawn here, because you have been here before.\nCountless times.\n$Though, perhaps it can be counted.\nTo be precise, this is in fact your {{cycleCount}} cycle.\n$Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain.\n$Until now you have yet to succeed, but I sense a different presence in you this time.\n\n$You are the only one here, though it is as if there is… another.\n$Will you finally prove a formidable challenge to me?\nThe challenge I have longed after for millennia?\n$We begin.",
- "encounter_female": null,
+ "encounter_female": "It appears the time has finally come once again.\nYou know why you have come here, do you not?\n$You were drawn here, because you have been here before.\nCountless times.\n$Though, perhaps it can be counted.\nTo be precise, this is in fact your {{cycleCount}} cycle.\n$Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain.\n$Until now you have yet to succeed, but I sense a different presence in you this time.\n\n$You are the only one here, though it is as if there is… another.\n$Will you finally prove a formidable challenge to me?\nThe challenge I have longed after for millennia?\n$We begin.",
"firstStageWin": "I see. The presence I felt was indeed real.\nIt appears I no longer need to hold back.\n$Do not disappoint me.",
"secondStageWin": "…Magnificent.",
"key_ordinal_one": "st",
diff --git a/src/locales/en/dialogue-misc.json b/src/locales/en/dialogue-misc.json
index f5c63a85410..2f333b5f383 100644
--- a/src/locales/en/dialogue-misc.json
+++ b/src/locales/en/dialogue-misc.json
@@ -1,6 +1,6 @@
{
- "ending": "@c{smile}Oh? You won?@d{96} @c{smile_eclosed}I guess I should've known.\nBut, you're back now.\n$@c{smile}It's over.@d{64} You ended the loop.\n$@c{serious_smile_fists}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$@c{neutral}I'm the only one who'll remember what you did.@d{96}\nI guess that's okay, isn't it?\n$@c{serious_smile_fists}Your legend will always live on in our hearts.\n$@c{smile_eclosed}Anyway, I've had about enough of this place, haven't you? Let's head home.\n$@c{serious_smile_fists}Maybe when we get back, we can have another battle?\nIf you're up to it.",
- "ending_female": "@c{shock}You're back?@d{32} Does that mean…@d{96} you won?!\n@c{smile_ehalf}I should have known you had it in you.\n$@c{smile_eclosed}Of course… I always had that feeling.\n@c{smile}It's over now, right? You ended the loop.\n$@c{smile_ehalf}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$I'll be the only one to remember what you did.\n@c{angry_mopen}I'll try not to forget!\n$@c{smile_wave_wink}Just kidding!@d{64} @c{smile}I'd never forget.@d{32}\nYour legend will live on in our hearts.\n$@c{smile_wave}Anyway,@d{64} it's getting late…@d{96} I think?\nIt's hard to tell in this place.\n$Let's go home. @c{smile_wave_wink}Maybe tomorrow, we can have another battle, for old time's sake?",
+ "ending": "@c{shock}You're back?@d{32} Does that mean…@d{96} you won?!\n@c{smile_ehalf}I should have known you had it in you.\n$@c{smile_eclosed}Of course… I always had that feeling.\n@c{smile}It's over now, right? You ended the loop.\n$@c{smile_ehalf}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$I'll be the only one to remember what you did.\n@c{angry_mopen}I'll try not to forget!\n$@c{smile_wave_wink}Just kidding!@d{64} @c{smile}I'd never forget.@d{32}\nYour legend will live on in our hearts.\n$@c{smile_wave}Anyway,@d{64} it's getting late…@d{96} I think?\nIt's hard to tell in this place.\n$Let's go home. @c{smile_wave_wink}Maybe tomorrow, we can have another battle, for old time's sake?",
+ "ending_female": "@c{smile}Oh? You won?@d{96} @c{smile_eclosed}I guess I should've known.\nBut, you're back now.\n$@c{smile}It's over.@d{64} You ended the loop.\n$@c{serious_smile_fists}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$@c{neutral}I'm the only one who'll remember what you did.@d{96}\nI guess that's okay, isn't it?\n$@c{serious_smile_fists}Your legend will always live on in our hearts.\n$@c{smile_eclosed}Anyway, I've had about enough of this place, haven't you? Let's head home.\n$@c{serious_smile_fists}Maybe when we get back, we can have another battle?\nIf you're up to it.",
"ending_endless": "Congratulations on reaching the current end!\nMore content is coming soon.",
"ending_name": "Devs"
-}
\ No newline at end of file
+}
diff --git a/src/locales/en/dialogue.json b/src/locales/en/dialogue.json
index e96a42daf1d..5565d2258c2 100644
--- a/src/locales/en/dialogue.json
+++ b/src/locales/en/dialogue.json
@@ -3,31 +3,31 @@
"encounter": {
"1": "Hey, wanna battle?",
"2": "Are you a new trainer too?",
- "2_female": null,
+ "2_female": "Are you a new trainer too?",
"3": "Hey, I haven't seen you before. Let's battle!",
"4": "I just lost, so I'm trying to find more Pokémon.\nWait! You look weak! Come on, let's battle!",
- "4_female": null,
+ "4_female": "I just lost, so I'm trying to find more Pokémon.\nWait! You look weak! Come on, let's battle!",
"5": "Have we met or not? I don't really remember. Well, I guess it's nice to meet you anyway!",
"6": "All right! Let's go!",
"7": "All right! Here I come! I'll show you my power!",
"8": "Haw haw haw... I'll show you how hawesome my Pokémon are!",
"9": "No need to waste time saying hello. Bring it on whenever you're ready!",
- "9_female": null,
+ "9_female": "No need to waste time saying hello. Bring it on whenever you're ready!",
"10": "Don't let your guard down, or you may be crying when a kid beats you.",
"11": "I've raised my Pokémon with great care. You're not allowed to hurt them!",
"12": "Glad you made it! It won't be an easy job from here.",
- "12_female": null,
+ "12_female": "Glad you made it! It won't be an easy job from here.",
"13": "The battles continue forever! Welcome to the world with no end!",
- "13_female": null
+ "13_female": "The battles continue forever! Welcome to the world with no end!"
},
"victory": {
"1": "Wow! You're strong!",
- "1_female": null,
+ "1_female": "Wow! You're strong!",
"2": "I didn't stand a chance, huh?",
"3": "I'll find you again when I'm older and beat you!",
"4": "Ugh. I don't have any more Pokémon.",
"5": "No way… NO WAY! How could I lose again…",
- "5_female": null,
+ "5_female": "No way… NO WAY! How could I lose again…",
"6": "No! I lost!",
"7": "Whoa! You are incredible! I'm amazed and surprised!",
"8": "Could it be… How… My Pokémon and I are the strongest, though…",
@@ -42,12 +42,12 @@
"encounter": {
"1": "Let's have a battle, shall we?",
"2": "You look like a new trainer. Let's have a battle!",
- "2_female": null,
+ "2_female": "You look like a new trainer. Let's have a battle!",
"3": "I don't recognize you. How about a battle?",
"4": "Let's have a fun Pokémon battle!",
"5": "I'll show you the ropes of how to really use Pokémon!",
"6": "A serious battle starts from a serious beginning! Are you sure you're ready?",
- "6_female": null,
+ "6_female": "A serious battle starts from a serious beginning! Are you sure you're ready?",
"7": "You're only young once. And you only get one shot at a given battle. Soon, you'll be nothing but a memory.",
"8": "You'd better go easy on me, OK? Though I'll be seriously fighting!",
"9": "School is boring. I've got nothing to do. Yawn. I'm only battling to kill the time."
@@ -55,15 +55,15 @@
"victory": {
"1": "That was impressive! I've got a lot to learn.",
"2": "I didn't think you'd beat me that bad…",
- "2_female": null,
+ "2_female": "I didn't think you'd beat me that bad…",
"3": "I hope we get to have a rematch some day.",
"4": "That was pretty amazingly fun! You've totally exhausted me…",
"5": "You actually taught me a lesson! You're pretty amazing!",
"6": "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.",
- "6_female": null,
+ "6_female": "Seriously, I lost. That is, like, seriously depressing, but you were seriously cool.",
"7": "I don't need memories like this. Deleting memory…",
"8": "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.",
- "8_female": null,
+ "8_female": "Hey! I told you to go easy on me! Still, you're pretty cool when you're serious.",
"9": "I'm actually getting tired of battling… There's gotta be something new to do…"
}
},
@@ -154,7 +154,7 @@
"ace_trainer": {
"encounter": {
"1": "You seem quite confident.",
- "1_female": null,
+ "1_female": "You seem quite confident.",
"2": "Your Pokémon… Show them to me…",
"3": "Because I'm an Ace Trainer, people think I'm strong.",
"4": "Are you aware of what it takes to be an Ace Trainer?"
@@ -163,9 +163,9 @@
"1": "Yes… You have good Pokémon…",
"2": "What?! But I'm a battling genius!",
"3": "Of course, you are the main character!",
- "3_female": null,
+ "3_female": "Of course, you are the main character!",
"4": "OK! OK! You could be an Ace Trainer!",
- "4_female": null
+ "4_female": "OK! OK! You could be an Ace Trainer!"
},
"defeat": {
"1": "I am devoting my body and soul to Pokémon battles!",
@@ -187,7 +187,7 @@
"1": "Get ready, because when we team up, it's double the trouble!",
"2": "Two hearts, one strategy – let's see if you can keep up with our twin power!",
"3": "Hope you're ready for double trouble, because we're about to bring the heat!",
- "3_female": null
+ "3_female": "Hope you're ready for double trouble, because we're about to bring the heat!"
},
"victory": {
"1": "We may have lost this round, but our bond remains unbreakable!",
@@ -216,7 +216,7 @@
"encounter": {
"1": "I praise your courage in challenging me! For I am the one with the strongest kick!",
"2": "Oh, I see. Would you like to be cut to pieces? Or do you prefer the role of punching bag?",
- "2_female": null
+ "2_female": "Oh, I see. Would you like to be cut to pieces? Or do you prefer the role of punching bag?"
},
"victory": {
"1": "Oh. The Pokémon did the fighting. My strong kick didn't help a bit.",
@@ -328,7 +328,7 @@
"defeat": {
"1": "New age simply refers to twentieth century classical composers, right?",
"2": "Don't get hung up on sadness or frustration. You can use your grudges to motivate yourself.",
- "2_female": null
+ "2_female": "Don't get hung up on sadness or frustration. You can use your grudges to motivate yourself."
}
},
"psychic": {
@@ -360,7 +360,7 @@
"baker": {
"encounter": {
"1": "Hope you're ready to taste defeat!",
- "1_female": null
+ "1_female": "Hope you're ready to taste defeat!"
},
"victory": {
"1": "I'll bake a comeback."
@@ -391,7 +391,7 @@
"1": "Matey, you're walking the plank if you lose!",
"2": "Come on then! My sailor's pride is at stake!",
"3": "Ahoy there! Are you seasick?",
- "3_female": null
+ "3_female": "Ahoy there! Are you seasick?"
},
"victory": {
"1": "Argh! Beaten by a kid!",
@@ -413,13 +413,13 @@
},
"ariana": {
"encounter": {
- "1": "Hold it right there! We can't someone on the loose.\n$It's harmful to Team Rocket's pride, you see.",
+ "1": "Hold it right there!\nWe can't have someone on the loose.\n$It's harmful to Team Rocket's pride, you see.",
"2": "I don't know or care if what I'm doing is right or wrong...\n$I just put my faith in Giovanni and do as I am told",
"3": "Your trip ends here. I'm going to take you down!"
},
"victory": {
"1": "Tch, you really are strong. It's too bad.\n$If you were to join Team Rocket, you could become an Executive.",
- "1_female": null,
+ "1_female": "Tch, you really are strong. It's too bad.\n$If you were to join Team Rocket, you could become an Executive.",
"2": "I... I'm shattered...",
"3": "Aaaieeeee! This can't be happening! I fought hard, but I still lost…"
}
@@ -458,7 +458,7 @@
"1": "Hehehe! You might have beaten me, but you don't stand a chance against the boss!\n$If you get lost now, you won't have to face a sound whipping!",
"2": "Hehehe... So, I lost, too...",
"3": "Ahya! How could this be? For an Admin like me to lose to some random trainer...",
- "3_female": null
+ "3_female": "Ahya! How could this be? For an Admin like me to lose to some random trainer..."
}
},
"courtney": {
@@ -478,13 +478,13 @@
"1": "Ahahahaha! You're going to meddle in Team Aqua's affairs?\n$You're either absolutely fearless, simply ignorant, or both!\n$You're so cute, you're disgusting! I'll put you down",
"2": "What's this? Who's this spoiled brat?",
"3": "Cool your jets. Be patient. I'll crush you shortly.",
- "3_female": null
+ "3_female": "Cool your jets. Be patient. I'll crush you shortly."
},
"victory": {
"1": "Ahahahaha! We got meddled with unexpectedly! We're out of options.\n$We'll have to pull out. But this isn't the last you'll see of Team Aqua!\n$We have other plans! Don't you forget it!",
"2": "Ahhh?! Did I go too easy on you?!",
"3": "Uh. Are you telling me you've upped your game even more during the fight?\n$You're a brat with a bright future… My Pokémon and I don't have any strength left to fight…\n$Go on… Go and be destroyed by Archie.",
- "3_female": null
+ "3_female": "Uh. Are you telling me you've upped your game even more during the fight?\n$You're a brat with a bright future… My Pokémon and I don't have any strength left to fight…\n$Go on… Go and be destroyed by Archie."
}
},
"matt": {
@@ -497,7 +497,7 @@
"1": "Muwuhahaha! That battle was fun even though I lost!",
"2": "I can feel it! I can feel it, all right! The strength coming offa you!\n$More! I still want more! But looks like we're outta time...",
"3": "Oho! That's a loss I can be proud of!",
- "3_female": null
+ "3_female": "Oho! That's a loss I can be proud of!"
}
},
"mars": {
@@ -505,7 +505,7 @@
"1": "I'm Mars, one of Team Galactic's top Commanders.",
"2": "Team Galactic's vision for the future is unwavering. Opposition will be crushed without mercy!",
"3": "Feeling nervous? You should be!",
- "3_female": null
+ "3_female": "Feeling nervous? You should be!"
},
"victory": {
"1": "This can't be happening! How did I lose?!",
@@ -540,25 +540,25 @@
"zinzolin": {
"encounter": {
"1": "You could become a threat to Team Plasma, so we will eliminate you here and now!",
- "1_female": null,
+ "1_female": "You could become a threat to Team Plasma, so we will eliminate you here and now!",
"2": "You don't have the sense to know when to quit, it seems. It's an act of mercy on my part to bring an end to this now!",
"3": "You're an impressive Trainer to have made it this far. But it ends here.",
- "3_female": null
+ "3_female": "You're an impressive Trainer to have made it this far. But it ends here."
},
"victory": {
"1": "Ghetsis... I have failed you...",
"2": "It's bitter cold. I'm shivering. I'm suffering. Yet, we will stand victorious.",
"3": "Hmph. You're a smarter Trainer than I expected, but not smart enough.",
- "3_female": null
+ "3_female": "Hmph. You're a smarter Trainer than I expected, but not smart enough."
}
},
"rood": {
"encounter": {
"1": "You are a threat to Team Plasma. We cannot let you walk away from here and now!",
- "1_female": null,
+ "1_female": "You are a threat to Team Plasma. We cannot let you walk away from here and now!",
"2": "It seems you don't know when to give up. I'll make sure no one interferes with our plans!",
"3": "You are a remarkable Trainer to have made it this far. But this is where it ends.",
- "3_female": null
+ "3_female": "You are a remarkable Trainer to have made it this far. But this is where it ends."
},
"victory": {
"1": "Ghetsis... I have failed my mission...",
@@ -569,15 +569,15 @@
"xerosic": {
"encounter": {
"1": "Ah ha ha! It would be my pleasure. Come on, little Trainer! Let's see what you've got!",
- "1_female": null,
+ "1_female": "Ah ha ha! It would be my pleasure. Come on, little Trainer! Let's see what you've got!",
"2": "Hmm... You're more powerful than you look. I wonder how much energy there is inside you.",
- "2_female": null,
+ "2_female": "Hmm... You're more powerful than you look. I wonder how much energy there is inside you.",
"3": "I've been waiting for you! I need to do a little research on you! Come, let us begin!"
},
"victory": {
"1": "Ah, you're quite strong. Oh yes—very strong, indeed.",
"2": "Ding-ding-ding! You did it! To the victor go the spoils!",
- "2_female": null,
+ "2_female": "Ding-ding-ding! You did it! To the victor go the spoils!",
"3": "Wonderful! Amazing! You have tremendous skill and bravery!"
}
},
@@ -585,7 +585,7 @@
"encounter": {
"1": "I am Bryony, and it would be my pleasure to battle you. Show me what you've got.",
"2": "Impressive... You're more powerful than you appear. Let's see the true extent of your energy.",
- "2_female": null,
+ "2_female": "Impressive... You're more powerful than you appear. Let's see the true extent of your energy.",
"3": "I've anticipated your arrival. It's time for a little test. Shall we begin?"
},
"victory": {
@@ -598,11 +598,11 @@
"encounter": {
"1": "Prepare for trouble!",
"2": "We're pulling a big job here! Get lost, kid!",
- "2_female": null,
+ "2_female": "We're pulling a big job here! Get lost, kid!",
"3": "Hand over your Pokémon, or face the wrath of Team Rocket!",
"4": "You're about to experience the true terror of Team Rocket!",
"5": "Hey, kid! Me am a Team Rocket member kind of guy!",
- "5_female": null
+ "5_female": "Hey, kid! Me am a Team Rocket member kind of guy!"
},
"victory": {
"1": "Team Rocket blasting off again!",
@@ -624,7 +624,7 @@
"1": "Huh? I lost?!",
"2": "I can't believe I lost! I even skipped lunch for this",
"3": "No way! You're just a kid!",
- "3_female": null,
+ "3_female": "No way! You're just a kid!",
"4": "Urrrgh... I should've ducked into our hideout right away...",
"5": "You beat me... Do you think the boss will dock my pay for this?"
}
@@ -652,7 +652,7 @@
"3": "In the name of Team Galactic, I'll eliminate anyone who stands in our way!",
"4": "Get ready to lose!",
"5": "Hope you're ready for a cosmic beatdown!",
- "5_female": null
+ "5_female": "Hope you're ready for a cosmic beatdown!"
},
"victory": {
"1": "Shut down...",
@@ -682,7 +682,7 @@
"encounter": {
"1": "Your Pokémon are no match for the elegance of Team Flare.",
"2": "Hope you brought your sunglasses, because things are about to get bright!",
- "2_female": null,
+ "2_female": "Hope you brought your sunglasses, because things are about to get bright!",
"3": "Team Flare will cleanse the world of imperfection!",
"4": "Prepare to face the brilliance of Team Flare!",
"5": "Fashion is most important to us!"
@@ -699,6 +699,7 @@
"encounter": {
"1": "I'll fight you with all I have to wipe you out!",
"2": "I don't care if you're a kid or what. I'll send you flying if you threaten us!",
+ "2_female": "I don't care if you're a kid or what. I'll send you flying if you threaten us!",
"3": "I was told to turn away Trainers, whomever they might be!",
"4": "I'll show you the power of Aether Paradise!",
"5": "Now that you've learned of the darkness at the heart of Aether Paradise, we'll need you to conveniently disappear!"
@@ -715,11 +716,13 @@
"encounter": {
"1": "I, Branch Chief Faba, shall show you the harshness of the real world!",
"2": "The man who is called Aether Paradise's last line of defense is to battle a mere child?",
+ "2_female": "The man who is called Aether Paradise's last line of defense is to battle a mere child?",
"3": "I, Faba, am the Aether Branch Chief. The only one in the world, I'm irreplaceable."
},
"victory": {
"1": "Aiyee!",
"2": "H-h-how can this be?! How could this child...",
+ "2_female": "H-h-how can this be?! How could this child...",
"3": "This is why... This is why I can't bring myself to like children."
}
},
@@ -727,9 +730,12 @@
"encounter": {
"1": "We're not bad-we're just hard!",
"2": "You want some? That's how we say hello! Nice knowing you, punks!",
+ "2_female": "You want some? That's how we say hello! Nice knowing you, punks!",
"3": "We're just a bunch of guys and gals with a great interest in other people's Pokémon!",
"4": "Why you trying to act hard when we're already hard as bones out here, homie?",
- "5": "Team Skull represent! We can't pay the rent! Had a lot of fun, but our youth was misspent!"
+ "4_female": "Why you trying to act hard when we're already hard as bones out here, homie?",
+ "5": "Team Skull represent! We can't pay the rent! Had a lot of fun, but our youth was misspent!",
+ "5_female": "Team Skull represent! We can't pay the rent! Had a lot of fun, but our youth was misspent!"
},
"victory": {
"1": "Huh? Is it over already?",
@@ -742,11 +748,13 @@
"plumeria": {
"encounter": {
"1": " ...Hmph. You don't look like anything special to me.",
- "2": "It takes these dumb Grunts way too long to deal with you kids..",
+ "1_female": " ...Hmph. You don't look like anything special to me.",
+ "2": "It takes these dumb Grunts way too long to deal with you kids...",
"3": "Mess with anyone in Team Skull, and I'll show you how serious I can get."
},
"victory": {
"1": "Hmmph! You're pretty strong. I'll give you that.",
+ "1_female": "Hmmph! You're pretty strong. I'll give you that.",
"2": "Hmmph. Guess you are pretty tough. Now I understand why my Grunts waste so much time battling kids.",
"3": "Hmmph! I guess I just have to hold that loss."
}
@@ -755,6 +763,7 @@
"encounter": {
"1": "It looks like this is the end of the line for you!",
"2": "You are a trainer aren't you? I'm afraid that doesn't give you the right to interfere in our work.",
+ "2_female": "You are a trainer aren't you? I'm afraid that doesn't give you the right to interfere in our work.",
"3": "I'm from Macro Cosmos Insurance! Do you have a life insurance policy?"
},
"victory": {
@@ -772,6 +781,7 @@
"victory": {
"1": "*sigh* I wasn't able to win... Oleana...you really are a hopeless woman.",
"2": "Arghhh! This is inexcusable... What was I thinking... Any trainer who's made it this far would be no pushover..",
+ "2_female": "Arghhh! This is inexcusable... What was I thinking... Any trainer who's made it this far would be no pushover..",
"3": "*sigh* I am one tired Oleana..."
}
},
@@ -784,7 +794,7 @@
},
"defeat": {
"1": "Mark my words. Not being able to measure your own strength shows that you are still a child.",
- "1_female": null
+ "1_female": "Mark my words. Not being able to measure your own strength shows that you are still a child."
}
},
"rocket_boss_giovanni_2": {
@@ -845,7 +855,7 @@
"galactic_boss_cyrus_1": {
"encounter": {
"1": "You were compelled to come here by such vacuous sentimentality.\n$I will make you regret paying heed to your heart!",
- "1_female": null
+ "1_female": "You were compelled to come here by such vacuous sentimentality.\n$I will make you regret paying heed to your heart!"
},
"victory": {
"1": "Interesting. And quite curious."
@@ -995,7 +1005,7 @@
"misty": {
"encounter": {
"1": "My policy is an all out offensive with Water-type Pokémon!",
- "1_female": null,
+ "1_female": "My policy is an all out offensive with Water-type Pokémon!",
"2": "Hiya, I'll show you the strength of my aquatic Pokémon!",
"3": "My dream was to go on a journey and battle powerful trainers…\nWill you be a sufficient challenge?"
},
@@ -1013,14 +1023,14 @@
"lt_surge": {
"encounter": {
"1": "My Electric Pokémon saved me during the war! I'll show you how!",
- "1_female": null,
+ "1_female": "My Electric Pokémon saved me during the war! I'll show you how!",
"2": "Ten-hut! I'll shock you into surrender!",
"3": "I'll zap you just like I do to all my enemies in battle!"
},
"victory": {
"1": "Whoa! Your team's the real deal, kid!",
"2": "Aaargh, you're strong! Even my electric tricks lost against you.",
- "2_female": null,
+ "2_female": "Aaargh, you're strong! Even my electric tricks lost against you.",
"3": "That was an absolutely shocking loss!"
},
"defeat": {
@@ -1045,7 +1055,7 @@
"defeat": {
"1": "I was afraid I would doze off…",
"2": "Oh my, it seems my Grass Pokémon overwhelmed you.",
- "2_female": null,
+ "2_female": "Oh my, it seems my Grass Pokémon overwhelmed you.",
"3": "That battle was such a soothing experience.",
"4": "Oh… Is that all?"
}
@@ -1106,7 +1116,7 @@
"1": "I, the leader of Team Rocket, will make you feel a world of pain!",
"2": "My training here will be vital before I am to face my old associates again.",
"3": "I do not think you are prepared for the level of failure you are about to experience!",
- "3_female": null
+ "3_female": "I do not think you are prepared for the level of failure you are about to experience!"
},
"victory": {
"1": "WHAT! Me, lose?! There is nothing I wish to say to you!",
@@ -1139,7 +1149,7 @@
"brawly": {
"encounter": {
"1": "Oh man, a challenger!\nLet's see what you can do!",
- "1_female": null,
+ "1_female": "Oh man, a challenger!\nLet's see what you can do!",
"2": "You seem like a big splash.\nLet's battle!",
"3": "Time to create a storm!\nLet's go!"
},
@@ -1167,7 +1177,7 @@
},
"defeat": {
"1": "Recharge your batteries and challenge me again sometime!\nWahahahaha!",
- "1_female": null,
+ "1_female": "Recharge your batteries and challenge me again sometime!\nWahahahaha!",
"2": "I hope you found our battle electrifying!\nWahahahaha!",
"3": "Aren't you shocked I won?\nWahahahaha!"
}
@@ -1214,7 +1224,7 @@
},
"victory": {
"1": "You're the first Trainer I've seen with more grace than I.\nExcellently played.",
- "1_female": null,
+ "1_female": "You're the first Trainer I've seen with more grace than I.\nExcellently played.",
"2": "Oh, my Flying Pokémon have plummeted!\nVery well.",
"3": "Though I may have fallen, my Pokémon will continue to fly!"
},
@@ -1227,7 +1237,7 @@
"tate": {
"encounter": {
"1": "Hehehe…\nWere you surprised to see me without my sister?",
- "1_female": null,
+ "1_female": "Hehehe…\nWere you surprised to see me without my sister?",
"2": "I can see what you're thinking…\nYou want to battle!",
"3": "How can you defeat someone…\nWho knows your every move?"
},
@@ -1245,7 +1255,7 @@
"liza": {
"encounter": {
"1": "Fufufu…\nWere you surprised to see me without my brother?",
- "1_female": null,
+ "1_female": "Fufufu…\nWere you surprised to see me without my brother?",
"2": "I can determine what you desire…\nYou want to battle, don't you?",
"3": "How can you defeat someone…\nWho's one with their Pokémon?"
},
@@ -1317,10 +1327,10 @@
"nessa": {
"encounter": {
"1": "No matter what kind of plan your refined mind may be plotting, my partner and I will be sure to sink it.",
- "1_female": null,
+ "1_female": "No matter what kind of plan your refined mind may be plotting, my partner and I will be sure to sink it.",
"2": "I'm not here to chat. I'm here to win!",
"3": "This is a little gift from my Pokémon… I hope you can take it!",
- "3_female": null
+ "3_female": "This is a little gift from my Pokémon… I hope you can take it!"
},
"victory": {
"1": "You and your Pokémon are just too much…",
@@ -1341,7 +1351,7 @@
},
"victory": {
"1": "You… You're pretty good, huh?",
- "1_female": null,
+ "1_female": "You… You're pretty good, huh?",
"2": "If you find Gordie around, be sure to give him a right trashing, would you?",
"3": "I think you took breaking the ice a little too literally…"
},
@@ -1355,12 +1365,12 @@
"encounter": {
"1": "You look strong! Shoots! Let's start!",
"2": "I'm strong like the ocean's wide. You're gonna get swept away, fo' sho'.",
- "2_female": null,
+ "2_female": "I'm strong like the ocean's wide. You're gonna get swept away, fo' sho'.",
"3": "Oh ho, so I'm facing you! That's off the wall."
},
"victory": {
"1": "You totally rocked that! You're raising some wicked Pokémon. You got this Trainer thing down!",
- "1_female": null,
+ "1_female": "You totally rocked that! You're raising some wicked Pokémon. You got this Trainer thing down!",
"2": "You don't just look strong, you're strong fo' reals! Eh, I was swept away, too!",
"3": "You're strong as a gnarly wave!"
},
@@ -1373,7 +1383,7 @@
"shauntal": {
"encounter": {
"1": "Excuse me. You're a challenger, right?\nI'm the Elite Four's Ghost-type Pokémon user, Shauntal, and I shall be your opponent.",
- "1_female": null,
+ "1_female": "Excuse me. You're a challenger, right?\nI'm the Elite Four's Ghost-type Pokémon user, Shauntal, and I shall be your opponent.",
"2": "I absolutely love writing about Trainers who come here and the Pokémon they train.\nCould I use you and your Pokémon as a subject?",
"3": "Every person who works with Pokémon has a story to tell.\nWhat story is about to be told?"
},
@@ -1391,7 +1401,7 @@
"marshal": {
"encounter": {
"1": "My mentor, Alder, sees your potential as a Trainer and is taking an interest in you.\nIt is my intention to test you--to take you to the limits of your strength. Kiai!",
- "1_female": null,
+ "1_female": "My mentor, Alder, sees your potential as a Trainer and is taking an interest in you.\nIt is my intention to test you--to take you to the limits of your strength. Kiai!",
"2": "Victory, decisive victory, is my intention! Challenger, here I come!",
"3": "In myself, I seek to develop the strength of a fighter and shatter any weakness in myself!\nPrevailing with the force of my convictions!"
},
@@ -1411,7 +1421,7 @@
"1": "You remind me of an old friend. That makes me excited about this Pokémon battle!",
"2": "Pokémon battles have no meaning if you don't think why you battle.\n$Or better said, it makes battling together with Pokémon meaningless.",
"3": "My name's Cheren! I'm a Gym Leader and a teacher! Pleasure to meet you.",
- "3_female": null
+ "3_female": "My name's Cheren! I'm a Gym Leader and a teacher! Pleasure to meet you."
},
"victory": {
"1": "Thank you! I saw what was missing in me.",
@@ -1427,73 +1437,73 @@
"chili": {
"encounter": {
"1": "Yeeeeooow! Time to play with FIRE!! I'm the strongest of us brothers!",
- "1_female": null,
+ "1_female": "Yeeeeooow! Time to play with FIRE!! I'm the strongest of us brothers!",
"2": "Ta-da! The Fire-type scorcher Chili--that's me--will be your opponent!",
- "2_female": null,
+ "2_female": "Ta-da! The Fire-type scorcher Chili--that's me--will be your opponent!",
"3": "I'm going to show you what me and my blazing Fire types can do!",
- "3_female": null
+ "3_female": "I'm going to show you what me and my blazing Fire types can do!"
},
"victory": {
"1": "You got me. I am… burned… out…",
- "1_female": null,
+ "1_female": "You got me. I am… burned… out…",
"2": "Whoa ho! You're on fire!",
- "2_female": null,
+ "2_female": "Whoa ho! You're on fire!",
"3": "Augh! You got me!"
},
"defeat": {
"1": "I'm on fire! Play with me, and you'll get burned!",
- "1_female": null,
+ "1_female": "I'm on fire! Play with me, and you'll get burned!",
"2": "When you play with fire, you get burned!",
"3": "I mean, c'mon, your opponent was me! You didn't have a chance!",
- "3_female": null
+ "3_female": "I mean, c'mon, your opponent was me! You didn't have a chance!"
}
},
"cilan": {
"encounter": {
"1": "Nothing personal... No hard feelings... Me and my Grass-type Pokémon will...\n$Um... We're gonna battle come what may.",
- "1_female": null,
+ "1_female": "Nothing personal... No hard feelings... Me and my Grass-type Pokémon will...\n$Um... We're gonna battle come what may.",
"2": "So, um, if you're OK with me, I'll, um, put everything I've got into being, er, you know, your opponent.",
- "2_female": null,
+ "2_female": "So, um, if you're OK with me, I'll, um, put everything I've got into being, er, you know, your opponent.",
"3": "OK… So, um, I'm Cilan, I like Grass-type Pokémon.",
- "3_female": null
+ "3_female": "OK… So, um, I'm Cilan, I like Grass-type Pokémon."
},
"victory": {
"1": "Er… Is it over now?",
- "1_female": null,
+ "1_female": "Er… Is it over now?",
"2": "…What a surprise. You are very strong, aren't you? \n$I guess my brothers wouldn't have been able to defeat you either…",
- "2_female": null,
+ "2_female": "…What a surprise. You are very strong, aren't you? \n$I guess my brothers wouldn't have been able to defeat you either…",
"3": "…Huh. Looks like my timing was, um, off?"
},
"defeat": {
"1": "Huh? Did I win?",
- "1_female": null,
+ "1_female": "Huh? Did I win?",
"2": "I guess… \n$I suppose I won, because I've been competing with my brothers Chili and Cress, and we all were able to get tougher.",
- "2_female": null,
+ "2_female": "I guess… \n$I suppose I won, because I've been competing with my brothers Chili and Cress, and we all were able to get tougher.",
"3": "It…it was quite a thrilling experience…",
- "3_female": null
+ "3_female": "It…it was quite a thrilling experience…"
}
},
"roark": {
"encounter": {
"1": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!",
- "1_female": null,
+ "1_female": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!",
"2": "Here goes! These are my rocking Pokémon, my pride and joy!",
"3": "Rock-type Pokémon are simply the best!",
- "4": "I need to see your potential as a Trainer. And, I'll need to see the toughness of the Pokémon that battle with you!",
- "4_female": null
+ "4": "Every day, I toughened up my Pokémon by digging up Fossils nonstop.\n$Could I show you how tough I made them in a battle?",
+ "4_female": "Every day, I toughened up my Pokémon by digging up Fossils nonstop.\n$Could I show you how tough I made them in a battle?"
},
"victory": {
"1": "W-what? That can't be! My buffed-up Pokémon!",
"2": "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.",
- "2_female": null,
+ "2_female": "…We lost control there. Next time I'd like to challenge you to a Fossil-digging race underground.",
"3": "With skill like yours, it's natural for you to win.",
- "4": "Wh-what?! It can't be! Even that wasn't enough?",
- "5": "I blew it."
+ "4": "Wh-what?! It can't be! Even that wasn't enough?"
},
"defeat": {
"1": "See? I'm proud of my rocking battle style!",
"2": "Thanks! The battle gave me confidence that I may be able to beat my dad!",
- "3": "I feel like I just smashed through a really stubborn boulder!"
+ "3": "See? These are my rocking Pokémon, my pride and joy!",
+ "4": "I knew I would win!"
}
},
"morty": {
@@ -1508,7 +1518,7 @@
"victory": {
"1": "I'm not good enough yet…",
"2": "I see… Your journey has taken you to far-away places and you have witnessed much more than I.\n$I envy you for that…",
- "2_female": null,
+ "2_female": "I see… Your journey has taken you to far-away places and you have witnessed much more than I.\n$I envy you for that…",
"3": "How is this possible…",
"4": "I don't think our potentials are so different.\n$But you seem to have something more than that… So be it.",
"5": "Guess I need more training.",
@@ -1568,13 +1578,13 @@
},
"defeat": {
"1": "Heh heh! Don't mind me, just scooping up a W over here. I get it if you're upset, but don't go full Kieran on me, OK?",
- "1_female": null
+ "1_female": "Heh heh! Don't mind me, just scooping up a W over here. I get it if you're upset, but don't go full Kieran on me, OK?"
}
},
"ramos": {
"encounter": {
"1": "Did yeh enjoy the garden playground I made with all these sturdy plants o' mine?\n$Their strength is a sign o' my strength as a gardener and a Gym Leader! Yeh sure yer up to facing all that?",
- "1_female": null
+ "1_female": "Did yeh enjoy the garden playground I made with all these sturdy plants o' mine?\n$Their strength is a sign o' my strength as a gardener and a Gym Leader! Yeh sure yer up to facing all that?"
},
"victory": {
"1": "Yeh believe in yer Pokémon… And they believe in yeh, too… It was a fine battle, sprout."
@@ -1605,7 +1615,7 @@
"victory": {
"1": "I must say, I'm warmed up to you! I might even admire you a little.",
"2": "Wow! You're great! You've earned my respect! \n$I think your focus and will bowled us over totally. ",
- "2_female": null
+ "2_female": "Wow! You're great! You've earned my respect! \n$I think your focus and will bowled us over totally. "
},
"defeat": {
"1": "I sensed your will to win, but I don't lose!",
@@ -1618,7 +1628,7 @@
},
"victory": {
"1": "Amazing! You're very good, aren't you?",
- "1_female": null
+ "1_female": "Amazing! You're very good, aren't you?"
},
"defeat": {
"1": "Yes! My Pokémon and I are perfectly good!"
@@ -1660,7 +1670,7 @@
"clay": {
"encounter": {
"1": "Harrumph! Kept me waitin', didn't ya, kid? All right, time to see what ya can do!",
- "1_female": null
+ "1_female": "Harrumph! Kept me waitin', didn't ya, kid? All right, time to see what ya can do!"
},
"victory": {
"1": "Man oh man… It feels good to go all out and still be defeated!"
@@ -1675,7 +1685,7 @@
},
"victory": {
"1": "Vaultin' Veluza! Yer a lively one, aren't ya! A little TOO lively, if I do say so myself!",
- "1_female": null
+ "1_female": "Vaultin' Veluza! Yer a lively one, aren't ya! A little TOO lively, if I do say so myself!"
},
"defeat": {
"1": "You come back to see me again now, ya hear?"
@@ -1742,7 +1752,7 @@
},
"victory": {
"1": "Bravo. I realize now your authenticity and magnificence as a Pokémon Trainer. \n$I find much joy in having met you and your Pokémon. You have proven yourself worthy.",
- "1_female": null
+ "1_female": "Bravo. I realize now your authenticity and magnificence as a Pokémon Trainer. \n$I find much joy in having met you and your Pokémon. You have proven yourself worthy."
},
"defeat": {
"1": "A grand illusion!"
@@ -1751,14 +1761,14 @@
"lorelei": {
"encounter": {
"1": "No one can best me when it comes to icy Pokémon! Freezing moves are powerful!\n$Your Pokémon will be at my mercy when they are frozen solid! Hahaha! Are you ready?",
- "1_female": null
+ "1_female": "No one can best me when it comes to icy Pokémon! Freezing moves are powerful!\n$Your Pokémon will be at my mercy when they are frozen solid! Hahaha! Are you ready?"
},
"victory": {
"1": "How dare you!"
},
"defeat": {
"1": "There's nothing you can do once you're frozen.",
- "1_female": null
+ "1_female": "There's nothing you can do once you're frozen."
}
},
"will": {
@@ -1775,11 +1785,11 @@
"malva": {
"encounter": {
"1": "I feel like my heart might just burst into flames. \n$I'm burning up with my hatred for you, runt!",
- "1_female": null
+ "1_female": "I feel like my heart might just burst into flames. \n$I'm burning up with my hatred for you, runt!"
},
"victory": {
"1": "What news… So a new challenger has defeated Malva!",
- "1_female": null
+ "1_female": "What news… So a new challenger has defeated Malva!"
},
"defeat": {
"1": "I am delighted! Yes, delighted that I could squash you beneath my heel."
@@ -1802,7 +1812,7 @@
},
"victory": {
"1": "I certainly found an interesting Trainer to face!",
- "1_female": null
+ "1_female": "I certainly found an interesting Trainer to face!"
},
"defeat": {
"1": "Ahaha. What an interesting battle."
@@ -1814,11 +1824,11 @@
},
"victory": {
"1": "Not bad, kiddo.",
- "1_female": null
+ "1_female": "Not bad, kiddo."
},
"defeat": {
"1": "Nahahaha! You really are something else, kiddo!",
- "1_female": null
+ "1_female": "Nahahaha! You really are something else, kiddo!"
}
},
"bruno": {
@@ -1838,7 +1848,7 @@
},
"victory": {
"1": "Whoa, amazing! You're an expert on Pokémon!\nMy research isn't complete yet. OK, you win.",
- "1_female": null
+ "1_female": "Whoa, amazing! You're an expert on Pokémon!\nMy research isn't complete yet. OK, you win."
},
"defeat": {
"1": "Thanks! Thanks to our battle, I was also able to make progress in my research!"
@@ -1869,11 +1879,11 @@
"lenora": {
"encounter": {
"1": "Well then, challenger, I'm going to research how you battle with the Pokémon you've so lovingly raised!",
- "1_female": null
+ "1_female": "Well then, challenger, I'm going to research how you battle with the Pokémon you've so lovingly raised!"
},
"victory": {
"1": "My theory about you was correct. You're more than just talented… You're motivated! I salute you!",
- "1_female": null
+ "1_female": "My theory about you was correct. You're more than just talented… You're motivated! I salute you!"
},
"defeat": {
"1": "Ah ha ha! If you lose, make sure to analyze why, and use that knowledge in your next battle!"
@@ -1899,7 +1909,7 @@
},
"defeat": {
"1": "Hey, c'mon! Get serious! You gotta put more out there!",
- "1_female": null
+ "1_female": "Hey, c'mon! Get serious! You gotta put more out there!"
}
},
"olivia": {
@@ -1938,7 +1948,7 @@
"flint": {
"encounter": {
"1": "Hope you're warmed up, cause here comes the Big Bang!",
- "1_female": null
+ "1_female": "Hope you're warmed up, cause here comes the Big Bang!"
},
"victory": {
"1": "Incredible! Your moves are so hot, they make mine look lukewarm!"
@@ -1961,7 +1971,7 @@
"caitlin": {
"encounter": {
"1": "It's me who appeared when the flower opened up. You who have been waiting…\n$You look like a Pokémon Trainer with refined strength and deepened kindness. \n$What I look for in my opponent is superb strength… \n$Please unleash your power to the fullest!",
- "1_female": null
+ "1_female": "It's me who appeared when the flower opened up. You who have been waiting…\n$You look like a Pokémon Trainer with refined strength and deepened kindness. \n$What I look for in my opponent is superb strength… \n$Please unleash your power to the fullest!"
},
"victory": {
"1": "My Pokémon and I learned so much! I offer you my thanks."
@@ -1984,15 +1994,15 @@
"wikstrom": {
"encounter": {
"1": "Well met, young challenger! Verily am I the famed blade of hardened steel, Duke Wikstrom! \n$Let the battle begin! En garde!",
- "1_female": null
+ "1_female": "Well met, young challenger! Verily am I the famed blade of hardened steel, Duke Wikstrom! \n$Let the battle begin! En garde!"
},
"victory": {
"1": "Glorious! The trust that you share with your honorable Pokémon surpasses even mine!",
- "1_female": null
+ "1_female": "Glorious! The trust that you share with your honorable Pokémon surpasses even mine!"
},
"defeat": {
"1": "What manner of magic is this? My heart, it doth hammer ceaselessly in my breast! \n$Winning against such a worthy opponent doth give my soul wings--thus do I soar!",
- "1_female": null
+ "1_female": "What manner of magic is this? My heart, it doth hammer ceaselessly in my breast! \n$Winning against such a worthy opponent doth give my soul wings--thus do I soar!"
}
},
"acerola": {
@@ -2024,14 +2034,14 @@
},
"victory": {
"1": "You got me. You are magnificent!",
- "1_female": null,
+ "1_female": "You got me. You are magnificent!",
"2": "I never expected another trainer to beat me… I'm surprised.",
- "2_female": null
+ "2_female": "I never expected another trainer to beat me… I'm surprised."
},
"defeat": {
"1": "That was close. Want to try again?",
"2": "It's not that you are weak. Don't let it bother you.",
- "2_female": null
+ "2_female": "It's not that you are weak. Don't let it bother you."
}
},
"karen": {
@@ -2057,7 +2067,7 @@
},
"victory": {
"1": "The power of Grass has wilted… What an incredible Challenger!",
- "1_female": null
+ "1_female": "The power of Grass has wilted… What an incredible Challenger!"
},
"defeat": {
"1": "This'll really leave you in shock and awe."
@@ -2077,7 +2087,7 @@
"drasna": {
"encounter": {
"1": "You must be a strong Trainer. Yes, quite strong indeed…\n$That's just wonderful news! Facing opponents like you and your team will make my Pokémon grow like weeds!",
- "1_female": null
+ "1_female": "You must be a strong Trainer. Yes, quite strong indeed…\n$That's just wonderful news! Facing opponents like you and your team will make my Pokémon grow like weeds!"
},
"victory": {
"1": "Oh, dear me. That sure was a quick battle… I do hope you'll come back again sometime!"
@@ -2111,7 +2121,7 @@
"blue": {
"encounter": {
"1": "You must be pretty good to get this far.",
- "1_female": null
+ "1_female": "You must be pretty good to get this far."
},
"victory": {
"1": "I've only lost to him and now to you… Him? Hee, hee…"
@@ -2159,7 +2169,7 @@
},
"victory": {
"1": "This is the emergence of a new Champion.",
- "1_female": null
+ "1_female": "This is the emergence of a new Champion."
},
"defeat": {
"1": "I successfully defended my Championship."
@@ -2248,7 +2258,7 @@
},
"victory": {
"1": "Waaah! Waaah! You're so mean!",
- "1_female": null
+ "1_female": "Waaah! Waaah! You're so mean!"
},
"defeat": {
"1": "And that's that!"
@@ -2257,7 +2267,7 @@
"chuck": {
"encounter": {
"1": "Hah! You want to challenge me? Are you brave or just ignorant?",
- "1_female": null
+ "1_female": "Hah! You want to challenge me? Are you brave or just ignorant?"
},
"victory": {
"1": "You're strong! Would you please make me your apprentice?"
@@ -2269,7 +2279,7 @@
"katy": {
"encounter": {
"1": "Don't let your guard down unless you would like to find yourself knocked off your feet!",
- "1_female": null
+ "1_female": "Don't let your guard down unless you would like to find yourself knocked off your feet!"
},
"victory": {
"1": "All of my sweet little Pokémon dropped like flies!"
@@ -2303,7 +2313,7 @@
"maylene": {
"encounter": {
"1": "I've come to challenge you now, and I won't hold anything back. \n$Please prepare yourself for battle!",
- "1_female": null
+ "1_female": "I've come to challenge you now, and I won't hold anything back. \n$Please prepare yourself for battle!"
},
"victory": {
"1": "I admit defeat…"
@@ -2326,7 +2336,7 @@
"byron": {
"encounter": {
"1": "Trainer! You're young, just like my son, Roark. \n$With more young Trainers taking charge, the future of Pokémon is bright! \n$So, as a wall for young people, I'll take your challenge!",
- "1_female": null
+ "1_female": "Trainer! You're young, just like my son, Roark. \n$With more young Trainers taking charge, the future of Pokémon is bright! \n$So, as a wall for young people, I'll take your challenge!"
},
"victory": {
"1": "Hmm! My sturdy Pokémon--defeated!"
@@ -2349,7 +2359,7 @@
"volkner": {
"encounter": {
"1": "Since you've come this far, you must be quite strong…\n$I hope you're the Trainer who'll make me remember how fun it is to battle!",
- "1_female": null
+ "1_female": "Since you've come this far, you must be quite strong…\n$I hope you're the Trainer who'll make me remember how fun it is to battle!"
},
"victory": {
"1": "You've got me beat…\n$Your desire and the noble way your Pokémon battled for you… \n$I even felt thrilled during our match. That was a very good battle."
@@ -2452,7 +2462,7 @@
"valerie": {
"encounter": {
"1": "Oh, if it isn't a young Trainer… It is lovely to get to meet you like this. \n$Then I suppose you have earned yourself the right to a battle, as a reward for your efforts. \n$The elusive Fairy may appear frail as the breeze and delicate as a bloom, but it is strong.",
- "1_female": null
+ "1_female": "Oh, if it isn't a young Trainer… It is lovely to get to meet you like this. \n$Then I suppose you have earned yourself the right to a battle, as a reward for your efforts. \n$The elusive Fairy may appear frail as the breeze and delicate as a bloom, but it is strong."
},
"victory": {
"1": "I hope that you will find things worth smiling about tomorrow…"
@@ -2500,7 +2510,7 @@
},
"victory": {
"1": "Your pink is still lacking, but you're an excellent Trainer with excellent Pokémon.",
- "1_female": null
+ "1_female": "Your pink is still lacking, but you're an excellent Trainer with excellent Pokémon."
},
"defeat": {
"1": "Too bad for you, I guess."
@@ -2509,7 +2519,7 @@
"bede": {
"encounter": {
"1": "I suppose I should prove beyond doubt just how pathetic you are and how strong I am.",
- "1_female": null
+ "1_female": "I suppose I should prove beyond doubt just how pathetic you are and how strong I am."
},
"victory": {
"1": "I see… Well, that's fine. I wasn't really trying all that hard anyway."
@@ -2554,7 +2564,7 @@
"brassius": {
"encounter": {
"1": "I assume you are ready? Let our collaborative work of art begin!",
- "1_female": null
+ "1_female": "I assume you are ready? Let our collaborative work of art begin!"
},
"victory": {
"1": "Ahhh…vant-garde!"
@@ -2566,11 +2576,11 @@
"iono": {
"encounter": {
"1": "How're ya feelin' about this battle?\n$...\n$Let's get this show on the road! How strong is our challenger? \n$I 'unno! Let's find out together!",
- "1_female": null
+ "1_female": "How're ya feelin' about this battle?\n$...\n$Let's get this show on the road! How strong is our challenger? \n$I 'unno! Let's find out together!"
},
"victory": {
"1": "You're as flashy and bright as a 10,000,000-volt Thunderbolt, friendo!",
- "1_female": null
+ "1_female": "You're as flashy and bright as a 10,000,000-volt Thunderbolt, friendo!"
},
"defeat": {
"1": "Your eyeballs are MINE!"
@@ -2593,7 +2603,7 @@
},
"victory": {
"1": "You're cool, my friend—you move my SOUL!",
- "1_female": null
+ "1_female": "You're cool, my friend—you move my SOUL!"
},
"defeat": {
"1": "Later, baby!"
@@ -2627,9 +2637,9 @@
"nessa_elite": {
"encounter": {
"1": "The tides are turning in my favor. Ready to get swept away?",
- "1_female": null,
+ "1_female": "The tides are turning in my favor. Ready to get swept away?",
"2": "Let's make some waves with this battle! I hope you're prepared!",
- "2_female": null
+ "2_female": "Let's make some waves with this battle! I hope you're prepared!"
},
"victory": {
"1": "You navigated those waters perfectly... Well done!",
@@ -2657,7 +2667,7 @@
"allister_elite": {
"encounter": {
"1": "Shadows fall... Are you ready to face your fears?",
- "1_female": null,
+ "1_female": "Shadows fall... Are you ready to face your fears?",
"2": "Let's see if you can handle the darkness that I command."
},
"victory": {
@@ -2681,7 +2691,7 @@
"defeat": {
"1": "Another storm weathered, another victory claimed! Well fought!",
"2": "You got caught in my storm! Better luck next time!",
- "2_female": null
+ "2_female": "You got caught in my storm! Better luck next time!"
}
},
"alder": {
diff --git a/src/locales/en/menu-ui-handler.json b/src/locales/en/menu-ui-handler.json
index fccf9cd3002..0536fa12c2e 100644
--- a/src/locales/en/menu-ui-handler.json
+++ b/src/locales/en/menu-ui-handler.json
@@ -24,6 +24,7 @@
"linkGoogle": "Link Google",
"unlinkGoogle": "Unlink Google",
"cancel": "Cancel",
+ "donate": "Donate",
"losingProgressionWarning": "You will lose any progress since the beginning of the battle. Proceed?",
"noEggs": "You are not hatching\nany eggs at the moment!"
}
\ No newline at end of file
diff --git a/src/locales/en/modifier-type.json b/src/locales/en/modifier-type.json
index ed1ef900878..f73a3dcccae 100644
--- a/src/locales/en/modifier-type.json
+++ b/src/locales/en/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "Doubles the chance of an encounter being a double battle for {{battleCount}} battles."
},
- "TempBattleStatBoosterModifierType": {
- "description": "Increases the {{tempBattleStatName}} of all party members by 1 stage for 5 battles."
+ "TempStatStageBoosterModifierType": {
+ "description": "Increases the {{stat}} of all party members by 1 stage for 5 battles."
},
"AttackTypeBoosterModifierType": {
"description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%."
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "Increases all party members' level by {{levels}}."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "Increases the holder's base {{statName}} by 10%. The higher your IVs, the higher the stack limit."
+ "BaseStatBoosterModifierType": {
+ "description": "Increases the holder's base {{stat}} by 10%. The higher your IVs, the higher the stack limit."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "Restores 100% HP for all Pokémon."
@@ -183,6 +183,7 @@
"SOOTHE_BELL": { "name": "Soothe Bell" },
"SCOPE_LENS": { "name": "Scope Lens", "description": "It's a lens for scoping out weak points. It boosts the holder's critical-hit ratio."},
+ "DIRE_HIT": { "name": "Dire Hit", "extra": { "raises": "Critical Hit Ratio" } },
"LEEK": { "name": "Leek", "description": "This very long and stiff stalk of leek boosts the critical-hit ratio of Farfetch'd's moves."},
"EVIOLITE": { "name": "Eviolite", "description": "This mysterious evolutionary lump boosts the Defense and Sp. Def stats when held by a Pokémon that can still evolve." },
@@ -250,28 +251,14 @@
"METAL_POWDER": { "name": "Metal Powder", "description": "Extremely fine yet hard, this odd powder boosts Ditto's Defense stat." },
"QUICK_POWDER": { "name": "Quick Powder", "description": "Extremely fine yet hard, this odd powder boosts Ditto's Speed stat." }
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "X Attack",
"x_defense": "X Defense",
"x_sp_atk": "X Sp. Atk",
"x_sp_def": "X Sp. Def",
"x_speed": "X Speed",
- "x_accuracy": "X Accuracy",
- "dire_hit": "Dire Hit"
+ "x_accuracy": "X Accuracy"
},
-
- "TempBattleStatBoosterStatName": {
- "ATK": "Attack",
- "DEF": "Defense",
- "SPATK": "Sp. Atk",
- "SPDEF": "Sp. Def",
- "SPD": "Speed",
- "ACC": "Accuracy",
- "CRIT": "Critical Hit Ratio",
- "EVA": "Evasiveness",
- "DEFAULT": "???"
- },
-
"AttackTypeBoosterItem": {
"silk_scarf": "Silk Scarf",
"black_belt": "Black Belt",
@@ -450,6 +437,6 @@
"DRAGON_MEMORY": "Dragon Memory",
"DARK_MEMORY": "Dark Memory",
"FAIRY_MEMORY": "Fairy Memory",
- "BLANK_MEMORY": "Blank Memory"
+ "NORMAL_MEMORY": "Normal Memory"
}
}
diff --git a/src/locales/en/modifier.json b/src/locales/en/modifier.json
index 473be0e8bfa..47944c8adb7 100644
--- a/src/locales/en/modifier.json
+++ b/src/locales/en/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!",
"hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!",
- "pokemonResetNegativeStatStageApply": "{{pokemonNameWithAffix}}'s lowered stats were restored\nby its {{typeName}}!",
+ "resetNegativeStatStageApply": "{{pokemonNameWithAffix}}'s lowered stats were restored\nby its {{typeName}}!",
"moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!",
"turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!",
"contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!",
diff --git a/src/locales/en/move-trigger.json b/src/locales/en/move-trigger.json
index baddbaa34bf..e70fb9dcfb7 100644
--- a/src/locales/en/move-trigger.json
+++ b/src/locales/en/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}} cut its own HP to power up its move!",
"absorbedElectricity": "{{pokemonName}} absorbed electricity!",
"switchedStatChanges": "{{pokemonName}} switched stat changes with the target!",
+ "switchedTwoStatChanges": "{{pokemonName}} switched all changes to its {{firstStat}}\nand {{secondStat}} with its target!",
+ "switchedStat": "{{pokemonName}} switched {{stat}} with its target!",
+ "sharedGuard": "{{pokemonName}} shared its guard with the target!",
+ "sharedPower": "{{pokemonName}} shared its power with the target!",
"goingAllOutForAttack": "{{pokemonName}} is going all out for this attack!",
"regainedHealth": "{{pokemonName}} regained\nhealth!",
"keptGoingAndCrashed": "{{pokemonName}} kept going\nand crashed!",
@@ -61,5 +65,6 @@
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
"revivalBlessing": "{{pokemonName}} was revived!",
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
- "exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!"
-}
+ "exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
+ "safeguard": "{{targetName}} is protected by Safeguard!"
+}
\ No newline at end of file
diff --git a/src/locales/en/pokemon-info.json b/src/locales/en/pokemon-info.json
index 87d2f7ad17b..b79daaed621 100644
--- a/src/locales/en/pokemon-info.json
+++ b/src/locales/en/pokemon-info.json
@@ -1,7 +1,7 @@
{
"Stat": {
"HP": "Max. HP",
- "HPshortened": "MaxHP",
+ "HPshortened": "HP",
"ATK": "Attack",
"ATKshortened": "Atk",
"DEF": "Defense",
@@ -13,8 +13,7 @@
"SPD": "Speed",
"SPDshortened": "Spd",
"ACC": "Accuracy",
- "EVA": "Evasiveness",
- "HPStat": "HP"
+ "EVA": "Evasiveness"
},
"Type": {
"UNKNOWN": "Unknown",
@@ -38,4 +37,4 @@
"FAIRY": "Fairy",
"STELLAR": "Stellar"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/en/settings.json b/src/locales/en/settings.json
index 6528f0368fe..301ebea9b2b 100644
--- a/src/locales/en/settings.json
+++ b/src/locales/en/settings.json
@@ -100,7 +100,7 @@
"moveTouchControls": "Move Touch Controls",
"shopOverlayOpacity": "Shop Overlay Opacity",
"shopCursorTarget": "Shop Cursor Target",
- "items": "Items",
+ "rewards": "Rewards",
"reroll": "Reroll",
"shop": "Shop",
"checkTeam": "Check Team"
diff --git a/src/locales/es/ability.json b/src/locales/es/ability.json
index 807cc755c94..598694f441e 100644
--- a/src/locales/es/ability.json
+++ b/src/locales/es/ability.json
@@ -1,1242 +1,1242 @@
{
"stench": {
- "name": "Hedor",
+ "name": "Hedor",
"description": "Puede amedrentar a un Pokémon al atacarlo debido al mal olor que emana."
},
"drizzle": {
- "name": "Llovizna",
+ "name": "Llovizna",
"description": "Hace que llueva al entrar en combate."
},
"speedBoost": {
- "name": "Impulso",
+ "name": "Impulso",
"description": "Aumenta su Velocidad en cada turno."
},
"battleArmor": {
- "name": "Armadura Batalla",
+ "name": "Armadura Batalla",
"description": "La robusta coraza que lo protege bloquea los golpes críticos."
},
"sturdy": {
- "name": "Robustez",
+ "name": "Robustez",
"description": "El Pokémon no puede debilitarse de un solo golpe cuando tiene los PS al máximo. También evita los movimientos fulminantes."
},
"damp": {
- "name": "Humedad",
+ "name": "Humedad",
"description": "Aumenta la humedad del entorno y evita que se puedan utilizar movimientos explosivos, tales como Autodestrucción."
},
"limber": {
- "name": "Flexibilidad",
+ "name": "Flexibilidad",
"description": "Evita ser paralizado gracias a la flexibilidad de su cuerpo."
},
"sandVeil": {
- "name": "Velo Arena",
+ "name": "Velo Arena",
"description": "Aumenta su Evasión durante las tormentas de arena."
},
"static": {
- "name": "Elec. Estática",
+ "name": "Elec. Estática",
"description": "La electricidad estática que lo envuelve puede paralizar al Pokémon que lo ataque con un movimiento de contacto."
},
"voltAbsorb": {
- "name": "Absorbe Elec",
+ "name": "Absorbe Elec",
"description": "Si lo alcanza un movimiento de tipo Eléctrico, recupera PS en vez de sufrir daño."
},
"waterAbsorb": {
- "name": "Absorbe Agua",
+ "name": "Absorbe Agua",
"description": "Si lo alcanza un movimiento de tipo Agua, recupera PS en vez de sufrir daño."
},
"oblivious": {
- "name": "Despiste",
+ "name": "Despiste",
"description": "Su indiferencia evita que sea provocado, caiga presa del enamoramiento o sufra los efectos de Intimidación."
},
"cloudNine": {
- "name": "Aclimatación",
+ "name": "Aclimatación",
"description": "Anula todos los efectos del tiempo atmosférico."
},
"compoundEyes": {
- "name": "Ojo Compuesto",
+ "name": "Ojo Compuesto",
"description": "Aumenta la precisión de sus movimientos."
},
"insomnia": {
- "name": "Insomnio",
+ "name": "Insomnio",
"description": "Su resistencia al sueño le impide quedarse dormido."
},
"colorChange": {
- "name": "Cambio Color",
+ "name": "Cambio Color",
"description": "Adopta el tipo del último movimiento del que es blanco."
},
"immunity": {
- "name": "Inmunidad",
+ "name": "Inmunidad",
"description": "Su sistema inmunitario evita el envenenamiento."
},
"flashFire": {
- "name": "Absorbe Fuego",
+ "name": "Absorbe Fuego",
"description": "Si lo alcanza algún movimiento de tipo Fuego, potencia sus propios movimientos de dicho tipo."
},
"shieldDust": {
- "name": "Polvo Escudo",
+ "name": "Polvo Escudo",
"description": "El polvo de escamas que lo envuelve lo protege de los efectos secundarios de los ataques recibidos."
},
"ownTempo": {
- "name": "Ritmo Propio",
+ "name": "Ritmo Propio",
"description": "Como le gusta hacer las cosas a su manera, no le afecta la confusión ni sufre los efectos de Intimidación."
},
"suctionCups": {
- "name": "Ventosas",
+ "name": "Ventosas",
"description": "Sus ventosas se aferran al suelo, con lo cual anula movimientos y objetos que fuercen el cambio de Pokémon."
},
"intimidate": {
- "name": "Intimidación",
+ "name": "Intimidación",
"description": "Al entrar en combate, amilana al rival de tal manera que reduce su Ataque."
},
"shadowTag": {
- "name": "Sombra Trampa",
+ "name": "Sombra Trampa",
"description": "Pisa la sombra del rival para impedir que huya o lo cambien por otro."
},
"roughSkin": {
- "name": "Piel Tosca",
+ "name": "Piel Tosca",
"description": "Hiere con su piel áspera al Pokémon que lo ataque con un movimiento de contacto."
},
"wonderGuard": {
- "name": "Superguarda",
+ "name": "Superguarda",
"description": "Gracias a un poder misterioso, solo le hacen daño los movimientos supereficaces."
},
"levitate": {
- "name": "Levitación",
+ "name": "Levitación",
"description": "Su capacidad de flotar sobre el suelo le proporciona inmunidad frente a los movimientos de tipo Tierra."
},
"effectSpore": {
- "name": "Efecto Espora",
+ "name": "Efecto Espora",
"description": "Puede dormir, envenenar o paralizar al Pokémon que lo ataque con un movimiento de contacto."
},
"synchronize": {
- "name": "Sincronía",
+ "name": "Sincronía",
"description": "Contagia el envenenamiento, las quemaduras o la parálisis al Pokémon que le cause ese estado."
},
"clearBody": {
- "name": "Cuerpo Puro",
+ "name": "Cuerpo Puro",
"description": "Evita que se reduzcan sus características a causa de movimientos o habilidades de otros Pokémon."
},
"naturalCure": {
- "name": "Cura Natural",
+ "name": "Cura Natural",
"description": "Sus problemas de estado desaparecen cuando se retira del combate."
},
"lightningRod": {
- "name": "Pararrayos",
+ "name": "Pararrayos",
"description": "Atrae y neutraliza los movimientos de tipo Eléctrico, que además le aumentan el Ataque Especial."
},
"sereneGrace": {
- "name": "Dicha",
+ "name": "Dicha",
"description": "Aumenta la probabilidad de que los movimientos causen efectos secundarios."
},
"swiftSwim": {
- "name": "Nado Rápido",
+ "name": "Nado Rápido",
"description": "Aumenta su Velocidad cuando llueve."
},
"chlorophyll": {
- "name": "Clorofila",
+ "name": "Clorofila",
"description": "Aumenta su Velocidad cuando hace sol."
},
"illuminate": {
- "name": "Iluminación",
+ "name": "Iluminación",
"description": "Al iluminar el entorno, evita que su Precisión se reduzca."
},
"trace": {
- "name": "Calco",
+ "name": "Calco",
"description": "Copia la habilidad del rival al entrar en combate."
},
"hugePower": {
- "name": "Potencia",
+ "name": "Potencia",
"description": "Duplica la potencia de sus ataques físicos."
},
"poisonPoint": {
- "name": "Punto Tóxico",
+ "name": "Punto Tóxico",
"description": "Puede envenenar al Pokémon que lo ataque con un movimiento de contacto."
},
"innerFocus": {
- "name": "Fuerza Mental",
+ "name": "Fuerza Mental",
"description": "Gracias a su profunda concentración, no se amedrenta ante los ataques de otros Pokémon ni sufre los efectos de Intimidación."
},
"magmaArmor": {
- "name": "Escudo Magma",
+ "name": "Escudo Magma",
"description": "Gracias al magma candente que lo envuelve, no puede ser congelado."
},
"waterVeil": {
- "name": "Velo Agua",
+ "name": "Velo Agua",
"description": "Evita las quemaduras gracias a la capa de agua que lo envuelve."
},
"magnetPull": {
- "name": "Imán",
+ "name": "Imán",
"description": "Su magnetismo atrae a los Pokémon de tipo Acero y les impide huir o ser cambiados por otros."
},
"soundproof": {
- "name": "Insonorizar",
+ "name": "Insonorizar",
"description": "Su aislamiento acústico lo protege de movimientos que usan sonido."
},
"rainDish": {
- "name": "Cura Lluvia",
+ "name": "Cura Lluvia",
"description": "Recupera PS de forma gradual cuando llueve."
},
"sandStream": {
- "name": "Chorro Arena",
+ "name": "Chorro Arena",
"description": "Crea una tormenta de arena al entrar en combate."
},
"pressure": {
- "name": "Presión",
+ "name": "Presión",
"description": "Presiona al rival de tal manera que este consume más PP al usar sus movimientos."
},
"thickFat": {
- "name": "Sebo",
+ "name": "Sebo",
"description": "Gracias a la gruesa capa de grasa que lo protege, reduce a la mitad el daño que recibe de ataques de tipo Fuego o Hielo."
},
"earlyBird": {
- "name": "Madrugar",
+ "name": "Madrugar",
"description": "Si se duerme, tardará la mitad de tiempo en despertarse."
},
"flameBody": {
- "name": "Cuerpo Llama",
+ "name": "Cuerpo Llama",
"description": "Puede quemar al Pokémon que lo ataque con un movimiento de contacto."
},
"runAway": {
- "name": "Fuga",
+ "name": "Fuga",
"description": "Puede escapar de cualquier Pokémon salvaje."
},
"keenEye": {
- "name": "Vista Lince",
+ "name": "Vista Lince",
"description": "Su aguda vista evita que su Precisión se reduzca."
},
"hyperCutter": {
- "name": "Corte Fuerte",
+ "name": "Corte Fuerte",
"description": "Evita que otros Pokémon le reduzcan el Ataque."
},
"pickup": {
- "name": "Recogida",
- "description": "Puede recoger objetos que otros Pokémon hayan usado, o bien aquellos que encuentre en plena aventura."
+ "name": "Recogida",
+ "description": "Puede que recoja un objeto del enemigo tras cada batalla, al azar."
},
"truant": {
- "name": "Pereza",
+ "name": "Pereza",
"description": "Al ejecutar un movimiento, descansará en el turno siguiente."
},
"hustle": {
- "name": "Entusiasmo",
- "description": "Aumenta su Ataque, pero reduce su Precisión."
+ "name": "Entusiasmo",
+ "description": "Aumenta su ataque, pero reduce su precisión."
},
"cuteCharm": {
- "name": "Gran Encanto",
+ "name": "Gran Encanto",
"description": "Puede causar enamoramiento al Pokémon que lo ataque con un movimiento de contacto."
},
"plus": {
- "name": "Más",
- "description": "Aumenta su Ataque Especial si un Pokémon aliado tiene la habilidad Más o la habilidad Menos."
+ "name": "Más",
+ "description": "Aumenta su ataque especial si un Pokémon aliado tiene la habilidad Más o la habilidad Menos."
},
"minus": {
- "name": "Menos",
- "description": "Aumenta su Ataque Especial si un Pokémon aliado tiene la habilidad Más o la habilidad Menos."
+ "name": "Menos",
+ "description": "Aumenta su ataque especial si un Pokémon aliado tiene la habilidad Más o la habilidad Menos."
},
"forecast": {
- "name": "Predicción",
+ "name": "Predicción",
"description": "Cambia a tipo Agua, Fuego o Hielo en función del tiempo atmosférico."
},
"stickyHold": {
- "name": "Viscosidad",
+ "name": "Viscosidad",
"description": "Los objetos se quedan pegados a su cuerpo, por lo que no pueden robárselos."
},
"shedSkin": {
- "name": "Mudar",
+ "name": "Mudar",
"description": "Puede curar sus problemas de estado al mudar la piel."
},
"guts": {
- "name": "Agallas",
- "description": "Si sufre un problema de estado, se arma de valor y aumenta su Ataque."
+ "name": "Agallas",
+ "description": "Si sufre un problema de estado, se arma de valor y aumenta su ataque."
},
"marvelScale": {
- "name": "Escama Especial",
- "description": "Si sufre un problema de estado, sus escamas especiales reaccionan y aumenta su Defensa."
+ "name": "Escama Especial",
+ "description": "Si sufre un problema de estado, sus escamas especiales reaccionan y aumenta su defensa."
},
"liquidOoze": {
- "name": "Viscosecreción",
+ "name": "Viscosecreción",
"description": "Exuda una secreción viscosa y tóxica de intenso hedor que hiere a quienes intentan drenarle PS."
},
"overgrow": {
- "name": "Espesura",
+ "name": "Espesura",
"description": "Potencia sus movimientos de tipo Planta cuando le quedan pocos PS."
},
"blaze": {
- "name": "Mar Llamas",
+ "name": "Mar Llamas",
"description": "Potencia sus movimientos de tipo Fuego cuando le quedan pocos PS."
},
"torrent": {
- "name": "Torrente",
+ "name": "Torrente",
"description": "Potencia sus movimientos de tipo Agua cuando le quedan pocos PS."
},
"swarm": {
- "name": "Enjambre",
+ "name": "Enjambre",
"description": "Potencia sus movimientos de tipo Bicho cuando le quedan pocos PS."
},
"rockHead": {
- "name": "Cabeza Roca",
+ "name": "Cabeza Roca",
"description": "No pierde PS al usar movimientos que también hieren al usuario."
},
"drought": {
- "name": "Sequía",
+ "name": "Sequía",
"description": "El tiempo pasa a ser soleado al entrar en combate."
},
"arenaTrap": {
- "name": "Trampa Arena",
+ "name": "Trampa Arena",
"description": "Evita que el rival huya o sea cambiado por otro."
},
"vitalSpirit": {
- "name": "Espíritu Vital",
+ "name": "Espíritu Vital",
"description": "Su determinación le impide quedarse dormido."
},
"whiteSmoke": {
- "name": "Humo Blanco",
+ "name": "Humo Blanco",
"description": "El humo blanco que lo protege evita que otros Pokémon le reduzcan las características."
},
"purePower": {
- "name": "Energía Pura",
+ "name": "Energía Pura",
"description": "Duplica la potencia de sus ataques físicos gracias al yoga."
},
"shellArmor": {
- "name": "Caparazón",
+ "name": "Caparazón",
"description": "La robusta coraza que lo protege bloquea los golpes críticos."
},
"airLock": {
- "name": "Esclusa de Aire",
+ "name": "Esclusa de Aire",
"description": "Neutraliza todos los efectos del tiempo atmosférico."
},
"tangledFeet": {
- "name": "Tumbos",
- "description": "Aumenta su Evasión si está confuso."
+ "name": "Tumbos",
+ "description": "Aumenta su evasión si está confuso."
},
"motorDrive": {
- "name": "Electromotor",
- "description": "Si lo alcanza un movimiento de tipo Eléctrico, aumenta su Velocidad en vez de sufrir daño."
+ "name": "Electromotor",
+ "description": "Si lo alcanza un movimiento de tipo Eléctrico, aumenta su velocidad en vez de sufrir daño."
},
"rivalry": {
- "name": "Rivalidad",
+ "name": "Rivalidad",
"description": "Si el objetivo es del mismo sexo, su competitividad le lleva a infligir más daño. Si es del sexo contrario, en cambio, el daño será menor."
},
"steadfast": {
- "name": "Impasible",
- "description": "Cada vez que se amedrenta, aumenta su Velocidad debido a su voluntad inquebrantable."
+ "name": "Impasible",
+ "description": "Cada vez que se amedrenta, aumenta su velocidad debido a su voluntad inquebrantable."
},
"snowCloak": {
- "name": "Manto Níveo",
- "description": "Aumenta su Evasión cuando nieva."
+ "name": "Manto Níveo",
+ "description": "Aumenta su evasión cuando nieva."
},
"gluttony": {
- "name": "Gula",
+ "name": "Gula",
"description": "Cuando sus PS se ven reducidos a la mitad, engulle la baya que normalmente solo se comería cuando le quedasen pocos PS."
},
"angerPoint": {
- "name": "Irascible",
- "description": "Si recibe un golpe crítico, monta en cólera y su Ataque aumenta al máximo."
+ "name": "Irascible",
+ "description": "Si recibe un golpe crítico, monta en cólera y su ataque aumenta al máximo."
},
"unburden": {
- "name": "Liviano",
- "description": "Aumenta su Velocidad si usa o pierde el objeto que lleva."
+ "name": "Liviano",
+ "description": "Aumenta su velocidad si usa o pierde el objeto que lleva."
},
"heatproof": {
- "name": "Ignífugo",
+ "name": "Ignífugo",
"description": "Su cuerpo, resistente al calor, reduce a la mitad el daño recibido por movimientos de tipo Fuego."
},
"simple": {
- "name": "Simple",
+ "name": "Simple",
"description": "Duplica los cambios en las características."
},
"drySkin": {
- "name": "Piel Seca",
+ "name": "Piel Seca",
"description": "Pierde PS si hace sol y los recupera si llueve o recibe un movimiento de tipo Agua. Los movimientos de tipo Fuego, por su parte, le hacen más daño de lo normal."
},
"download": {
- "name": "Descarga",
- "description": "Compara la Defensa y la Defensa Especial del rival para ver cuál es inferior y aumenta su propio Ataque o Ataque Especial según sea lo más eficaz."
+ "name": "Descarga",
+ "description": "Compara la defensa y la defensa especial del rival para ver cuál es inferior y aumenta su propio ataque o ataque especial según sea lo más eficaz."
},
"ironFist": {
- "name": "Puño Férreo",
+ "name": "Puño Férreo",
"description": "Aumenta la potencia de los movimientos con los puños."
},
"poisonHeal": {
- "name": "Antídoto",
+ "name": "Antídoto",
"description": "Si resulta envenenado, recupera PS en vez de perderlos."
},
"adaptability": {
- "name": "Adaptable",
+ "name": "Adaptable",
"description": "Potencia aún más los movimientos cuyo tipo coincida con el suyo."
},
"skillLink": {
- "name": "Encadenado",
+ "name": "Encadenado",
"description": "Ejecuta siempre los movimientos de ataque múltiple con el número máximo de golpes."
},
"hydration": {
- "name": "Hidratación",
+ "name": "Hidratación",
"description": "Cura los problemas de estado si está lloviendo."
},
"solarPower": {
- "name": "Poder Solar",
- "description": "Si hace sol, aumenta su Ataque Especial, pero pierde PS en cada turno."
+ "name": "Poder Solar",
+ "description": "Si hace sol, aumenta su ataque especial, pero pierde PS en cada turno."
},
"quickFeet": {
- "name": "Pies Rápidos",
- "description": "Aumenta su Velocidad si sufre problemas de estado."
+ "name": "Pies Rápidos",
+ "description": "Aumenta su velocidad si sufre problemas de estado."
},
"normalize": {
- "name": "Normalidad",
+ "name": "Normalidad",
"description": "Hace que todos sus movimientos se vuelvan de tipo Normal y aumenta ligeramente su potencia."
},
"sniper": {
- "name": "Francotirador",
+ "name": "Francotirador",
"description": "Potencia los golpes críticos que asesta aún más de lo normal."
},
"magicGuard": {
- "name": "Muro Mágico",
+ "name": "Muro Mágico",
"description": "Solo recibe daño de ataques."
},
"noGuard": {
- "name": "Indefenso",
+ "name": "Indefenso",
"description": "Al quedar ambos expuestos, tanto sus movimientos como los del Pokémon que lo ataque acertarán siempre."
},
"stall": {
- "name": "Rezagado",
+ "name": "Rezagado",
"description": "Ejecuta su movimiento tras todos los demás."
},
"technician": {
- "name": "Experto",
+ "name": "Experto",
"description": "Aumenta la potencia de sus movimientos débiles."
},
"leafGuard": {
- "name": "Defensa Hoja",
+ "name": "Defensa Hoja",
"description": "Evita los problemas de estado si hace sol."
},
"klutz": {
- "name": "Zoquete",
+ "name": "Zoquete",
"description": "No puede usar objetos equipados."
},
"moldBreaker": {
- "name": "Rompemoldes",
+ "name": "Rompemoldes",
"description": "Sus movimientos no se ven afectados por la habilidad del objetivo."
},
"superLuck": {
- "name": "Afortunado",
+ "name": "Afortunado",
"description": "Su buena suerte aumenta la probabilidad de asestar golpes críticos."
},
"aftermath": {
- "name": "Detonación",
+ "name": "Detonación",
"description": "Daña al Pokémon que le ha dado el golpe de gracia con un movimiento de contacto."
},
"anticipation": {
- "name": "Anticipación",
+ "name": "Anticipación",
"description": "Prevé los movimientos peligrosos del rival."
},
"forewarn": {
- "name": "Alerta",
+ "name": "Alerta",
"description": "Revela uno de los movimientos del rival al entrar en combate."
},
"unaware": {
- "name": "Ignorante",
+ "name": "Ignorante",
"description": "Pasa por alto los cambios en las características de un Pokémon al atacarlo o recibir daño."
},
"tintedLens": {
- "name": "Cromolente",
+ "name": "Cromolente",
"description": "Potencia los movimientos que no son muy eficaces, que infligen ahora un daño normal."
},
"filter": {
- "name": "Filtro",
+ "name": "Filtro",
"description": "Mitiga el daño que le infligen los movimientos supereficaces."
},
"slowStart": {
- "name": "Inicio Lento",
- "description": "Reduce a la mitad su Ataque y su Velocidad durante cinco turnos."
+ "name": "Inicio Lento",
+ "description": "Reduce a la mitad su ataque y su velocidad durante cinco turnos."
},
"scrappy": {
- "name": "Intrépido",
+ "name": "Intrépido",
"description": "Alcanza a Pokémon de tipo Fantasma con movimientos de tipo Normal o Lucha. Además, no sufre los efectos de Intimidación."
},
"stormDrain": {
- "name": "Colector",
- "description": "Atrae y neutraliza los movimientos de tipo Agua, que además le aumentan el Ataque Especial."
+ "name": "Colector",
+ "description": "Atrae y neutraliza los movimientos de tipo Agua, que además le aumentan el ataque especial."
},
"iceBody": {
- "name": "Gélido",
+ "name": "Gélido",
"description": "Recupera PS de forma gradual cuando nieva."
},
"solidRock": {
- "name": "Roca Sólida",
+ "name": "Roca Sólida",
"description": "Mitiga el daño que le infligen los movimientos supereficaces."
},
"snowWarning": {
- "name": "Nevada",
+ "name": "Nevada",
"description": "Invoca una nevada al entrar en combate."
},
"honeyGather": {
- "name": "Recogemiel",
- "description": "The Pokémon gathers Honey after a battle. The Honey is then sold for money."
+ "name": "Recogemiel",
+ "description": "El Pokémon recoge miel tras cada batalla. La miel se vende inmediatamente por ₽."
},
"frisk": {
- "name": "Cacheo",
+ "name": "Cacheo",
"description": "Cuando entra en combate, el Pokémon puede comprobar la habilidad de un Pokémon rival."
},
"reckless": {
- "name": "Audaz",
+ "name": "Audaz",
"description": "Potencia los movimientos que también dañan al usuario."
},
"multitype": {
- "name": "Multitipo",
+ "name": "Multitipo",
"description": "Cambia su tipo al de la tabla que lleve."
},
"flowerGift": {
- "name": "Don Floral",
- "description": "Si hace sol, aumenta su Ataque y su Defensa Especial, así como los de sus aliados."
+ "name": "Don Floral",
+ "description": "Si hace sol, aumenta su ataque y su defensa Especial, así como los de sus aliados."
},
"badDreams": {
- "name": "Mal Sueño",
+ "name": "Mal Sueño",
"description": "Inflige daño a cualquier rival que esté dormido."
},
"pickpocket": {
- "name": "Hurto",
+ "name": "Hurto",
"description": "Roba el objeto del Pokémon que lo ataque con un movimiento de contacto."
},
"sheerForce": {
- "name": "Potencia Bruta",
+ "name": "Potencia Bruta",
"description": "Aumenta la potencia de sus movimientos en detrimento de los efectos secundarios, que se ven anulados."
},
"contrary": {
- "name": "Respondón",
+ "name": "Respondón",
"description": "Invierte los cambios en las características: bajan cuando les toca subir y suben cuando les toca bajar."
},
"unnerve": {
- "name": "Nerviosismo",
+ "name": "Nerviosismo",
"description": "Pone nervioso al rival y le impide comer bayas."
},
"defiant": {
- "name": "Competitivo",
- "description": "Aumenta mucho su Ataque cuando el rival le reduce cualquiera de sus características."
+ "name": "Competitivo",
+ "description": "Aumenta mucho su ataque cuando el rival le reduce cualquiera de sus características."
},
"defeatist": {
- "name": "Flaqueza",
- "description": "Cuando sus PS se ven reducidos a la mitad, se cansa tanto que su Ataque y su Ataque Especial también se ven reducidos a la mitad."
+ "name": "Flaqueza",
+ "description": "Cuando sus PS se ven reducidos a la mitad, se cansa tanto que su ataque y su ataque Especial también se ven reducidos a la mitad."
},
"cursedBody": {
- "name": "Cuerpo Maldito",
+ "name": "Cuerpo Maldito",
"description": "Puede anular el movimiento usado en su contra."
},
"healer": {
- "name": "Alma Cura",
+ "name": "Alma Cura",
"description": "A veces cura los problemas de estado de un aliado."
},
"friendGuard": {
- "name": "Compiescolta",
+ "name": "Compiescolta",
"description": "Reduce el daño que sufren los aliados."
},
"weakArmor": {
- "name": "Armadura Frágil",
- "description": "Al recibir daño de un ataque físico, se reduce su Defensa, pero aumenta mucho su Velocidad."
+ "name": "Armadura Frágil",
+ "description": "Al recibir daño de un ataque físico, se reduce su defensa, pero aumenta mucho su velocidad."
},
"heavyMetal": {
- "name": "Metal Pesado",
+ "name": "Metal Pesado",
"description": "Duplica su peso."
},
"lightMetal": {
- "name": "Metal Liviano",
+ "name": "Metal Liviano",
"description": "Reduce a la mitad su peso."
},
"multiscale": {
- "name": "Multiescamas",
+ "name": "Multiescamas",
"description": "Reduce el daño que sufre si sus PS están al máximo."
},
"toxicBoost": {
- "name": "Ímpetu Tóxico",
+ "name": "Ímpetu Tóxico",
"description": "Aumenta la potencia de sus ataques físicos cuando está envenenado."
},
"flareBoost": {
- "name": "Ímpetu Ardiente",
+ "name": "Ímpetu Ardiente",
"description": "Aumenta la potencia de sus ataques especiales cuando sufre quemaduras."
},
"harvest": {
- "name": "Cosecha",
+ "name": "Cosecha",
"description": "Puede reutilizar varias veces una misma baya."
},
"telepathy": {
- "name": "Telepatía",
+ "name": "Telepatía",
"description": "Elude los ataques de los aliados durante el combate."
},
"moody": {
- "name": "Veleta",
+ "name": "Veleta",
"description": "Aumenta mucho una característica en cada turno, pero reduce otra."
},
"overcoat": {
- "name": "Funda",
+ "name": "Funda",
"description": "No recibe daño de las tormentas de arena ni sufre los efectos causados por polvos o esporas."
},
"poisonTouch": {
- "name": "Toque Tóxico",
+ "name": "Toque Tóxico",
"description": "Puede envenenar al Pokémon al que ataque con un movimiento de contacto."
},
"regenerator": {
- "name": "Regeneración",
+ "name": "Regeneración",
"description": "Recupera unos pocos PS cuando se retira del combate."
},
"bigPecks": {
- "name": "Sacapecho",
- "description": "Impide que otros Pokémon le reduzcan la Defensa."
+ "name": "Sacapecho",
+ "description": "Impide que otros Pokémon le reduzcan la defensa."
},
"sandRush": {
- "name": "Ímpetu Arena",
- "description": "Aumenta su Velocidad durante las tormentas de arena."
+ "name": "Ímpetu Arena",
+ "description": "Aumenta su velocidad durante las tormentas de arena."
},
"wonderSkin": {
- "name": "Piel Milagro",
+ "name": "Piel Milagro",
"description": "Presenta una mayor resistencia ante los movimientos de estado."
},
"analytic": {
- "name": "Cálculo Final",
+ "name": "Cálculo Final",
"description": "Aumenta la potencia de su movimiento si es el último en atacar."
},
"illusion": {
- "name": "Ilusión",
+ "name": "Ilusión",
"description": "Adopta el aspecto del último Pokémon del equipo al entrar en combate para desconcertar al rival."
},
"imposter": {
- "name": "Impostor",
+ "name": "Impostor",
"description": "Se transforma en el Pokémon que tiene enfrente."
},
"infiltrator": {
- "name": "Allanamiento",
+ "name": "Allanamiento",
"description": "Ataca sorteando las barreras o el sustituto del objetivo."
},
"mummy": {
- "name": "Momia",
+ "name": "Momia",
"description": "Contagia la habilidad Momia al Pokémon que lo ataque con un movimiento de contacto."
},
"moxie": {
- "name": "Autoestima",
- "description": "Al debilitar a un objetivo, su confianza se refuerza de tal manera que aumenta su Ataque."
+ "name": "Autoestima",
+ "description": "Al debilitar a un objetivo, su confianza se refuerza de tal manera que aumenta su ataque."
},
"justified": {
- "name": "Justiciero",
- "description": "Si lo alcanza un movimiento de tipo Siniestro, aumenta el Ataque debido a su integridad."
+ "name": "Justiciero",
+ "description": "Si lo alcanza un movimiento de tipo Siniestro, aumenta el ataque debido a su integridad."
},
"rattled": {
- "name": "Cobardía",
- "description": "Si lo alcanza un ataque de tipo Siniestro, Bicho o Fantasma, o si sufre los efectos de Intimidación, el miedo hace que aumente su Velocidad."
+ "name": "Cobardía",
+ "description": "Si lo alcanza un ataque de tipo Siniestro, Bicho o Fantasma, o si sufre los efectos de Intimidación, el miedo hace que aumente su velocidad."
},
"magicBounce": {
- "name": "Espejo Mágico",
+ "name": "Espejo Mágico",
"description": "Puede devolver los movimientos de estado sin verse afectado por ellos."
},
"sapSipper": {
- "name": "Herbívoro",
- "description": "Si lo alcanza un movimiento de tipo Planta, aumenta su Ataque en vez de sufrir daño."
+ "name": "Herbívoro",
+ "description": "Si lo alcanza un movimiento de tipo Planta, aumenta su ataque en vez de sufrir daño."
},
"prankster": {
- "name": "Bromista",
+ "name": "Bromista",
"description": "Sus movimientos de estado tienen prioridad alta."
},
"sandForce": {
- "name": "Poder Arena",
+ "name": "Poder Arena",
"description": "Potencia los movimientos de tipo Tierra, Acero y Roca durante las tormentas de arena."
},
"ironBarbs": {
- "name": "Punta Acero",
+ "name": "Punta Acero",
"description": "Inflige daño con sus púas de acero al Pokémon que lo ataque con un movimiento de contacto."
},
"zenMode": {
- "name": "Modo Daruma",
+ "name": "Modo Daruma",
"description": "Cambia de forma si sus PS se ven reducidos a la mitad o menos."
},
"victoryStar": {
- "name": "Tinovictoria",
- "description": "Aumenta su Precisión y la de sus aliados."
+ "name": "Tinovictoria",
+ "description": "Aumenta su precisión y la de sus aliados."
},
"turboblaze": {
- "name": "Turbollama",
+ "name": "Turbollama",
"description": "Sus movimientos no se ven afectados por la habilidad del objetivo."
},
"teravolt": {
- "name": "Terravoltaje",
+ "name": "Terravoltaje",
"description": "Sus movimientos no se ven afectados por la habilidad del objetivo."
},
"aromaVeil": {
- "name": "Velo Aroma",
+ "name": "Velo Aroma",
"description": "Se protege a sí mismo y a sus aliados de efectos que impiden usar movimientos."
},
"flowerVeil": {
- "name": "Velo Flor",
+ "name": "Velo Flor",
"description": "Evita que los Pokémon de tipo Planta aliados sufran problemas de estado o que les reduzcan sus características."
},
"cheekPouch": {
- "name": "Carrillo",
+ "name": "Carrillo",
"description": "Recupera PS al comer cualquier baya."
},
"protean": {
- "name": "Mutatipo",
+ "name": "Mutatipo",
"description": "Al entrar en combate, cambia su tipo al del primer movimiento que va a usar."
},
"furCoat": {
- "name": "Pelaje Recio",
+ "name": "Pelaje Recio",
"description": "Reduce a la mitad el daño que recibe de ataques físicos."
},
"magician": {
- "name": "Prestidigitador",
+ "name": "Prestidigitador",
"description": "Roba el objeto del Pokémon al que alcance con un movimiento."
},
"bulletproof": {
- "name": "Antibalas",
+ "name": "Antibalas",
"description": "No le afectan las bombas ni algunos proyectiles."
},
"competitive": {
- "name": "Tenacidad",
- "description": "Aumenta mucho su Ataque Especial cuando el rival le reduce cualquiera de sus características."
+ "name": "Tenacidad",
+ "description": "Aumenta mucho su ataque especial cuando el rival le reduce cualquiera de sus características."
},
"strongJaw": {
- "name": "Mandíbula Fuerte",
+ "name": "Mandíbula Fuerte",
"description": "Su robusta mandíbula le confiere una mordedura mucho más potente."
},
"refrigerate": {
- "name": "Piel Helada",
+ "name": "Piel Helada",
"description": "Convierte los movimientos de tipo Normal en tipo Hielo y aumenta ligeramente su potencia."
},
"sweetVeil": {
- "name": "Velo Dulce",
+ "name": "Velo Dulce",
"description": "No cae dormido y evita también que sus aliados se duerman."
},
"stanceChange": {
- "name": "Cambio Táctico",
+ "name": "Cambio Táctico",
"description": "Adopta la Forma Filo al lanzar un ataque, o bien la Forma Escudo si usa el movimiento Escudo Real."
},
"galeWings": {
- "name": "Alas Vendaval",
+ "name": "Alas Vendaval",
"description": "Da prioridad a los movimientos de tipo Volador si sus PS están al máximo."
},
"megaLauncher": {
- "name": "Megadisparador",
+ "name": "Megadisparador",
"description": "Aumenta la potencia de algunos movimientos de pulsos y auras."
},
"grassPelt": {
- "name": "Manto Frondoso",
- "description": "Aumenta su Defensa si hay un campo de hierba en el terreno de combate."
+ "name": "Manto Frondoso",
+ "description": "Aumenta su defensa si hay un campo de hierba en el terreno de combate."
},
"symbiosis": {
- "name": "Simbiosis",
+ "name": "Simbiosis",
"description": "Pasa su objeto a un aliado cuando este use el suyo."
},
"toughClaws": {
- "name": "Garra Dura",
+ "name": "Garra Dura",
"description": "Aumenta la potencia de los movimientos de contacto."
},
"pixilate": {
- "name": "Piel Feérica",
+ "name": "Piel Feérica",
"description": "Convierte los movimientos de tipo Normal en tipo Hada y aumenta ligeramente su potencia."
},
"gooey": {
- "name": "Baba",
- "description": "Reduce la Velocidad del Pokémon que lo ataque con un movimiento de contacto."
+ "name": "Baba",
+ "description": "Reduce la velocidad del Pokémon que lo ataque con un movimiento de contacto."
},
"aerilate": {
- "name": "Piel Celeste",
+ "name": "Piel Celeste",
"description": "Convierte los movimientos de tipo Normal en tipo Volador y aumenta ligeramente su potencia."
},
"parentalBond": {
- "name": "Amor Filial",
+ "name": "Amor Filial",
"description": "Une fuerzas con su cría y ataca dos veces."
},
"darkAura": {
- "name": "Aura Oscura",
+ "name": "Aura Oscura",
"description": "Aumenta la potencia de los movimientos de tipo Siniestro de todos los Pokémon."
},
"fairyAura": {
- "name": "Aura Feérica",
+ "name": "Aura Feérica",
"description": "Aumenta la potencia de los movimientos de tipo Hada de todos los Pokémon."
},
"auraBreak": {
- "name": "Rompeaura",
+ "name": "Rompeaura",
"description": "Invierte los efectos de las habilidades de auras, por lo que reduce la potencia de ciertos movimientos en vez de aumentarla."
},
"primordialSea": {
- "name": "Mar del Albor",
+ "name": "Mar del Albor",
"description": "Altera el clima para anular los ataques de tipo Fuego."
},
"desolateLand": {
- "name": "Tierra del Ocaso",
+ "name": "Tierra del Ocaso",
"description": "Altera el clima para anular los ataques de tipo Agua."
},
"deltaStream": {
- "name": "Ráfaga Delta",
+ "name": "Ráfaga Delta",
"description": "Altera el clima para anular las vulnerabilidades del tipo Volador."
},
"stamina": {
- "name": "Firmeza",
- "description": "Aumenta su Defensa al recibir un ataque."
+ "name": "Firmeza",
+ "description": "Aumenta su defensa al recibir un ataque."
},
"wimpOut": {
- "name": "Huida",
+ "name": "Huida",
"description": "Se asusta y abandona el terreno de combate cuando sus PS se ven reducidos a la mitad."
},
"emergencyExit": {
- "name": "Retirada",
+ "name": "Retirada",
"description": "Abandona el terreno de combate cuando sus PS se ven reducidos a la mitad para evitar males mayores."
},
"waterCompaction": {
- "name": "Hidrorrefuerzo",
- "description": "Aumenta mucho su Defensa si lo alcanza un movimiento de tipo Agua."
+ "name": "Hidrorrefuerzo",
+ "description": "Aumenta mucho su defensa si lo alcanza un movimiento de tipo Agua."
},
"merciless": {
- "name": "Ensañamiento",
+ "name": "Ensañamiento",
"description": "Hace que sus movimientos asesten siempre un golpe crítico si el objetivo está envenenado."
},
"shieldsDown": {
- "name": "Escudo Limitado",
+ "name": "Escudo Limitado",
"description": "Rompe su coraza cuando sus PS se ven reducidos a la mitad y adopta una forma ofensiva."
},
"stakeout": {
- "name": "Vigilante",
+ "name": "Vigilante",
"description": "Si el objetivo de su ataque es sustituido por otro, duplica el daño que infligirá."
},
"waterBubble": {
- "name": "Pompa",
+ "name": "Pompa",
"description": "Reduce el daño que le provocan los movimientos de tipo Fuego y es inmune a las quemaduras."
},
"steelworker": {
- "name": "Acero Templado",
+ "name": "Acero Templado",
"description": "Potencia los movimientos de tipo Acero."
},
"berserk": {
- "name": "Cólera",
- "description": "Aumenta su Ataque Especial si sus PS se ven reducidos a la mitad debido a algún ataque."
+ "name": "Cólera",
+ "description": "Aumenta su ataque especial si sus PS se ven reducidos a la mitad debido a algún ataque."
},
"slushRush": {
- "name": "Quitanieves",
- "description": "Aumenta su Velocidad cuando nieva."
+ "name": "Quitanieves",
+ "description": "Aumenta su velocidad cuando nieva."
},
"longReach": {
- "name": "Remoto",
+ "name": "Remoto",
"description": "Puede usar cualquier movimiento sin entrar en contacto con su objetivo."
},
"liquidVoice": {
- "name": "Voz Fluida",
+ "name": "Voz Fluida",
"description": "Hace que todos sus movimientos que usan sonido pasen a ser de tipo Agua."
},
"triage": {
- "name": "Primer Auxilio",
+ "name": "Primer Auxilio",
"description": "Da prioridad a los movimientos que restauran PS."
},
"galvanize": {
- "name": "Piel Eléctrica",
+ "name": "Piel Eléctrica",
"description": "Convierte los movimientos de tipo Normal en tipo Eléctrico y aumenta ligeramente su potencia."
},
"surgeSurfer": {
- "name": "Cola Surf",
- "description": "Duplica su Velocidad si hay un campo eléctrico en el terreno de combate."
+ "name": "Cola Surf",
+ "description": "Duplica su velocidad si hay un campo eléctrico en el terreno de combate."
},
"schooling": {
- "name": "Banco",
+ "name": "Banco",
"description": "Forma bancos con sus congéneres cuando tiene muchos PS, lo cual le otorga más fuerza. Cuando le quedan pocos PS, el banco se dispersa."
},
"disguise": {
- "name": "Disfraz",
+ "name": "Disfraz",
"description": "Puede eludir un ataque valiéndose de la tela que le cubre el cuerpo una vez por combate."
},
"battleBond": {
- "name": "Fuerte Afecto",
- "description": "Al derrotar a un Pokémon, los vínculos con su Entrenador se refuerzan y aumentan su Ataque, su Ataque Especial y su Velocidad."
+ "name": "Fuerte Afecto",
+ "description": "Al derrotar a un Pokémon, los vínculos con su Entrenador se refuerzan y aumentan su ataque, su ataque especial y su velocidad."
},
"powerConstruct": {
- "name": "Agrupamiento",
+ "name": "Agrupamiento",
"description": "Cuando sus PS se ven reducidos a la mitad, las células se reagrupan y adopta su Forma Completa."
},
"corrosion": {
- "name": "Corrosión",
+ "name": "Corrosión",
"description": "Puede envenenar incluso a Pokémon de tipo Acero o Veneno."
},
"comatose": {
- "name": "Letargo Perenne",
+ "name": "Letargo Perenne",
"description": "No despierta jamás de su profundo letargo e incluso ataca dormido."
},
"queenlyMajesty": {
- "name": "Regia Presencia",
+ "name": "Regia Presencia",
"description": "Intimida al rival y le impide usar movimientos con prioridad contra él y sus aliados."
},
"innardsOut": {
- "name": "Revés",
+ "name": "Revés",
"description": "Al caer debilitado, inflige al atacante un daño equivalente a los PS que le quedaran antes de recibir el golpe de gracia."
},
"dancer": {
- "name": "Pareja de Baile",
+ "name": "Pareja de Baile",
"description": "Puede copiar inmediatamente cualquier movimiento de baile que haya usado otro Pokémon presente en el combate."
},
"battery": {
- "name": "Batería",
+ "name": "Batería",
"description": "Potencia los ataques especiales de los aliados."
},
"fluffy": {
- "name": "Peluche",
+ "name": "Peluche",
"description": "Reduce a la mitad el daño recibido por los movimientos de contacto, pero duplica el que le infligen los de tipo Fuego."
},
"dazzling": {
- "name": "Cuerpo Vívido",
+ "name": "Cuerpo Vívido",
"description": "Desconcierta al rival y le impide usar movimientos con prioridad contra él y sus aliados."
},
"soulHeart": {
- "name": "Coránima",
- "description": "Aumenta su Ataque Especial cada vez que un Pokémon cae debilitado."
+ "name": "Coránima",
+ "description": "Aumenta su ataque especial cada vez que un Pokémon cae debilitado."
},
"tanglingHair": {
- "name": "Rizos Rebeldes",
- "description": "Reduce la Velocidad del Pokémon que lo ataque con un movimiento de contacto."
+ "name": "Rizos Rebeldes",
+ "description": "Reduce la velocidad del Pokémon que lo ataque con un movimiento de contacto."
},
"receiver": {
- "name": "Receptor",
+ "name": "Receptor",
"description": "Adquiere la habilidad de un aliado debilitado."
},
"powerOfAlchemy": {
- "name": "Reacción Química",
+ "name": "Reacción Química",
"description": "Reacciona copiando la habilidad de un aliado debilitado."
},
"beastBoost": {
- "name": "Ultraimpulso",
+ "name": "Ultraimpulso",
"description": "Al derrotar a un Pokémon, aumenta su característica más fuerte."
},
"rksSystem": {
- "name": "Sistema Alfa",
+ "name": "Sistema Alfa",
"description": "Cambia su tipo según el disco que lleve instalado."
},
"electricSurge": {
- "name": "Electrogénesis",
+ "name": "Electrogénesis",
"description": "Crea un campo eléctrico al entrar en combate."
},
"psychicSurge": {
- "name": "Psicogénesis",
+ "name": "Psicogénesis",
"description": "Crea un campo psíquico al entrar en combate."
},
"mistySurge": {
- "name": "Nebulogénesis",
+ "name": "Nebulogénesis",
"description": "Crea un campo de niebla al entrar en combate."
},
"grassySurge": {
- "name": "Herbogénesis",
+ "name": "Herbogénesis",
"description": "Crea un campo de hierba al entrar en combate."
},
"fullMetalBody": {
- "name": "Guardia Metálica",
+ "name": "Guardia Metálica",
"description": "Evita que se reduzcan sus características a causa de movimientos o habilidades de otros Pokémon."
},
"shadowShield": {
- "name": "Guardia Espectro",
+ "name": "Guardia Espectro",
"description": "Reduce el daño que sufre si sus PS están al máximo."
},
"prismArmor": {
- "name": "Armadura Prisma",
+ "name": "Armadura Prisma",
"description": "Mitiga el daño que le infligen los movimientos supereficaces."
},
"neuroforce": {
- "name": "Fuerza Cerebral",
+ "name": "Fuerza Cerebral",
"description": "Potencia los ataques supereficaces."
},
"intrepidSword": {
- "name": "Espada Indómita",
- "description": "Aumenta su Ataque al entrar en combate por primera vez."
+ "name": "Espada Indómita",
+ "description": "Aumenta su ataque al entrar en combate por primera vez."
},
"dauntlessShield": {
- "name": "Escudo Recio",
- "description": "Aumenta su Defensa al entrar en combate por primera vez."
+ "name": "Escudo Recio",
+ "description": "Aumenta su defensa al entrar en combate por primera vez."
},
"libero": {
- "name": "Líbero",
+ "name": "Líbero",
"description": "Al entrar en combate, cambia su tipo al del primer movimiento que va a usar."
},
"ballFetch": {
- "name": "Recogebolas",
- "description": "Si no lleva equipado ningún objeto, recupera la Poké Ball del primer intento de captura fallido."
+ "name": "Recogebolas",
+ "description": "Recupera la Poké Ball del primer intento de captura fallido."
},
"cottonDown": {
- "name": "Pelusa",
- "description": "Al ser alcanzado por un ataque, suelta una pelusa de algodón que reduce la Velocidad de todos los demás Pokémon."
+ "name": "Pelusa",
+ "description": "Al ser alcanzado por un ataque, suelta una pelusa de algodón que reduce la velocidad de todos los demás Pokémon."
},
"propellerTail": {
- "name": "Hélice Caudal",
+ "name": "Hélice Caudal",
"description": "Ignora los efectos de las habilidades o los movimientos que permiten a un Pokémon centrar la atención sobre sí."
},
"mirrorArmor": {
- "name": "Coraza Reflejo",
+ "name": "Coraza Reflejo",
"description": "Refleja los efectos que reducen las características."
},
"gulpMissile": {
- "name": "Tragamisil",
+ "name": "Tragamisil",
"description": "Tras usar Surf o Buceo, emerge con una presa en la boca. Al recibir daño, ataca escupiéndola."
},
"stalwart": {
- "name": "Acérrimo",
+ "name": "Acérrimo",
"description": "Ignora los efectos de las habilidades o los movimientos que permiten a un Pokémon centrar la atención sobre sí."
},
"steamEngine": {
- "name": "Combustible",
- "description": "Si lo alcanza un movimiento de tipo Fuego o Agua, aumenta muchísimo su Velocidad."
+ "name": "Combustible",
+ "description": "Si lo alcanza un movimiento de tipo Fuego o Agua, aumenta muchísimo su velocidad."
},
"punkRock": {
- "name": "Punk Rock",
+ "name": "Punk Rock",
"description": "Potencia los movimientos que usan sonido y reduce a la mitad el daño que le infligen dichos movimientos."
},
"sandSpit": {
- "name": "Expulsarena",
+ "name": "Expulsarena",
"description": "Provoca una tormenta de arena al recibir un ataque."
},
"iceScales": {
- "name": "Escama de Hielo",
+ "name": "Escama de Hielo",
"description": "Las gélidas escamas que protegen su cuerpo reducen a la mitad el daño que le infligen los ataques especiales."
},
"ripen": {
- "name": "Maduración",
+ "name": "Maduración",
"description": "Hace madurar las bayas, por lo que duplica sus efectos."
},
"iceFace": {
- "name": "Cara de Hielo",
+ "name": "Cara de Hielo",
"description": "Absorbe el daño de un ataque físico con el hielo de la cabeza, tras lo cual cambia de forma. El hielo se regenerará la próxima vez que nieve."
},
"powerSpot": {
- "name": "Fuente Energía",
+ "name": "Fuente Energía",
"description": "Potencia los movimientos de los Pokémon adyacentes."
},
"mimicry": {
- "name": "Mimetismo",
+ "name": "Mimetismo",
"description": "Cambia su tipo según el campo que haya en el terreno de combate."
},
"screenCleaner": {
- "name": "Antibarrera",
+ "name": "Antibarrera",
"description": "Anula los efectos de Pantalla de Luz, Reflejo y Velo Aurora tanto de rivales como de aliados al entrar en combate."
},
"steelySpirit": {
- "name": "Alma Acerada",
+ "name": "Alma Acerada",
"description": "Potencia los movimientos de tipo Acero del Pokémon y sus aliados."
},
"perishBody": {
- "name": "Cuerpo Mortal",
+ "name": "Cuerpo Mortal",
"description": "Si lo alcanza un movimiento de contacto, se debilitará al cabo de 3 turnos, así como el atacante, a menos que abandonen el terreno de combate."
},
"wanderingSpirit": {
- "name": "Alma Errante",
+ "name": "Alma Errante",
"description": "Si lo alcanza un movimiento de contacto, intercambia su habilidad con la del atacante."
},
"gorillaTactics": {
- "name": "Monotema",
- "description": "Aumenta su Ataque, pero solo puede usar el primer movimiento escogido."
+ "name": "Monotema",
+ "description": "Aumenta su ataque, pero solo puede usar el primer movimiento escogido."
},
"neutralizingGas": {
- "name": "Gas Reactivo",
+ "name": "Gas Reactivo",
"description": "Anula los efectos de las habilidades de los demás Pokémon presentes mientras esté en el terreno de combate."
},
"pastelVeil": {
- "name": "Velo Pastel",
+ "name": "Velo Pastel",
"description": "Se protege a sí mismo y a sus aliados del envenenamiento."
},
"hungerSwitch": {
- "name": "Mutapetito",
+ "name": "Mutapetito",
"description": "Alterna entre su Forma Saciada y Forma Voraz al final de cada turno."
},
"quickDraw": {
- "name": "Mano Rápida",
+ "name": "Mano Rápida",
"description": "A veces, puede atacar el primero."
},
"unseenFist": {
- "name": "Puño Invisible",
+ "name": "Puño Invisible",
"description": "Si usa un movimiento de contacto, puede infligir daño al objetivo aunque este se proteja."
},
"curiousMedicine": {
- "name": "Medicina Extraña",
+ "name": "Medicina Extraña",
"description": "Al entrar en combate, rezuma una substancia medicinal por la caracola que revierte los cambios en las características de los aliados."
},
"transistor": {
- "name": "Transistor",
+ "name": "Transistor",
"description": "Potencia los movimientos de tipo Eléctrico."
},
"dragonsMaw": {
- "name": "Mandíbula Dragón",
+ "name": "Mandíbula Dragón",
"description": "Potencia los movimientos de tipo Dragón."
},
"chillingNeigh": {
- "name": "Relincho Blanco",
- "description": "Al derrotar a un objetivo, emite un relincho gélido y aumenta su Ataque."
+ "name": "Relincho Blanco",
+ "description": "Al derrotar a un objetivo, emite un relincho gélido y aumenta su ataque."
},
"grimNeigh": {
- "name": "Relincho Negro",
- "description": "Al derrotar a un objetivo, emite un relincho aterrador y aumenta su Ataque Especial."
+ "name": "Relincho Negro",
+ "description": "Al derrotar a un objetivo, emite un relincho aterrador y aumenta su ataque especial."
},
"asOneGlastrier": {
- "name": "Unidad Ecuestre",
+ "name": "Unidad Ecuestre",
"description": "El Pokémon tiene dos habilidades: Relincho Negro de Spectrier y Nerviosismo de Calyrex."
},
"asOneSpectrier": {
- "name": "Unidad Ecuestre",
+ "name": "Unidad Ecuestre",
"description": "El Pokémon tiene dos habilidades: Relincho Negro de Spectrier y Nerviosismo de Calyrex."
},
"lingeringAroma": {
- "name": "Olor Persistente",
+ "name": "Olor Persistente",
"description": "Contagia la habilidad Olor Persistente al Pokémon que lo ataque con un movimiento de contacto."
},
"seedSower": {
- "name": "Disemillar",
+ "name": "Disemillar",
"description": "Crea un campo de hierba al recibir un ataque."
},
"thermalExchange": {
- "name": "Termoconversión",
- "description": "Evita las quemaduras y, si lo alcanza un movimiento de tipo Fuego, aumenta su Ataque."
+ "name": "Termoconversión",
+ "description": "Evita las quemaduras y, si lo alcanza un movimiento de tipo Fuego, aumenta su ataque."
},
"angerShell": {
- "name": "Coraza Ira",
- "description": "Cuando un ataque reduce sus PS a la mitad, un arrebato de cólera reduce su Defensa y su Defensa Especial, pero aumenta su Ataque, su Ataque Especial y su Velocidad."
+ "name": "Coraza Ira",
+ "description": "Cuando un ataque reduce sus PS a la mitad, un arrebato de cólera reduce su defensa y su defensa especial, pero aumenta su ataque, su ataque especial y su velocidad."
},
"purifyingSalt": {
- "name": "Sal Purificadora",
+ "name": "Sal Purificadora",
"description": "Su sal pura lo protege de los problemas de estado y reduce a la mitad el daño que recibe de ataques de tipo Fantasma."
},
"wellBakedBody": {
- "name": "Cuerpo Horneado",
- "description": "Si lo alcanza un movimiento de tipo Fuego, aumenta mucho su Defensa en vez de sufrir daño."
+ "name": "Cuerpo Horneado",
+ "description": "Si lo alcanza un movimiento de tipo Fuego, aumenta mucho su defensa en vez de sufrir daño."
},
"windRider": {
- "name": "Surcavientos",
- "description": "Si sopla un Viento Afín o lo alcanza un movimiento que usa viento, aumenta su Ataque. Tampoco recibe daño de este último."
+ "name": "Surcavientos",
+ "description": "Si sopla un Viento Afín o lo alcanza un movimiento que usa viento, aumenta su ataque. Tampoco recibe daño de este último."
},
"guardDog": {
- "name": "Perro Guardián",
- "description": "Aumenta su Ataque si sufre los efectos de Intimidación. También anula movimientos y objetos que fuercen el cambio de Pokémon."
+ "name": "Perro Guardián",
+ "description": "Aumenta su ataque si sufre los efectos de Intimidación. También anula movimientos y objetos que fuercen el cambio de Pokémon."
},
"rockyPayload": {
- "name": "Transportarrocas",
+ "name": "Transportarrocas",
"description": "Potencia los movimientos de tipo Roca."
},
"windPower": {
- "name": "Energía Eólica",
+ "name": "Energía Eólica",
"description": "Su cuerpo se carga de electricidad si lo alcanza un movimiento que usa viento, lo que potencia su siguiente movimiento de tipo Eléctrico."
},
"zeroToHero": {
- "name": "Cambio Heroico",
+ "name": "Cambio Heroico",
"description": "Adopta la Forma Heroica cuando se retira del combate."
},
"commander": {
- "name": "Comandar",
+ "name": "Comandar",
"description": "Si al entrar en combate coincide con un Dondozo aliado, se cuela en el interior de su boca para tomar el control."
},
"electromorphosis": {
- "name": "Dinamo",
+ "name": "Dinamo",
"description": "Su cuerpo se carga de electricidad al recibir daño, lo que potencia su siguiente movimiento de tipo Eléctrico."
},
"protosynthesis": {
- "name": "Paleosíntesis",
+ "name": "Paleosíntesis",
"description": "Si hace sol o lleva un tanque de Energía Potenciadora, aumenta su característica más alta."
},
"quarkDrive": {
- "name": "Carga Cuark",
+ "name": "Carga Cuark",
"description": "Si hay un campo eléctrico en el terreno de combate o lleva un tanque de Energía Potenciadora, aumenta su característica más alta."
},
"goodAsGold": {
- "name": "Cuerpo Áureo",
+ "name": "Cuerpo Áureo",
"description": "Su robusto cuerpo de oro inoxidable lo hace inmune frente a movimientos de estado de otros Pokémon."
},
"vesselOfRuin": {
- "name": "Caldero Debacle",
- "description": "Reduce el Ataque Especial de todos los demás Pokémon con el poder de su caldero maldito."
+ "name": "Caldero Debacle",
+ "description": "Reduce el ataque especial de todos los demás Pokémon con el poder de su caldero maldito."
},
"swordOfRuin": {
- "name": "Espada Debacle",
- "description": "Reduce la Defensa de todos los demás Pokémon con el poder de su espada maldita."
+ "name": "Espada Debacle",
+ "description": "Reduce la defensa de todos los demás Pokémon con el poder de su espada maldita."
},
"tabletsOfRuin": {
- "name": "Tablilla Debacle",
- "description": "Reduce el Ataque de todos los demás Pokémon con el poder de sus tablillas malditas."
+ "name": "Tablilla Debacle",
+ "description": "Reduce el ataque de todos los demás Pokémon con el poder de sus tablillas malditas."
},
"beadsOfRuin": {
- "name": "Abalorio Debacle",
- "description": "Reduce la Defensa Especial de todos los demás Pokémon con el poder de sus abalorios malditos."
+ "name": "Abalorio Debacle",
+ "description": "Reduce la defensa especial de todos los demás Pokémon con el poder de sus abalorios malditos."
},
"orichalcumPulse": {
- "name": "Latido Oricalco",
- "description": "El tiempo pasa a ser soleado cuando entra en combate. Si hace mucho sol, su Ataque aumenta gracias a su pulso primigenio."
+ "name": "Latido Oricalco",
+ "description": "El tiempo pasa a ser soleado cuando entra en combate. Si hace mucho sol, su ataque aumenta gracias a su pulso primigenio."
},
"hadronEngine": {
- "name": "Motor Hadrónico",
- "description": "Crea un campo eléctrico al entrar en combate. Si hay un campo eléctrico, su Ataque Especial aumenta gracias a su motor futurista."
+ "name": "Motor Hadrónico",
+ "description": "Crea un campo eléctrico al entrar en combate. Si hay un campo eléctrico, su ataque especial aumenta gracias a su motor futurista."
},
"opportunist": {
- "name": "Oportunista",
+ "name": "Oportunista",
"description": "Copia las mejoras en las características del rival, aprovechándose de la situación."
},
"cudChew": {
- "name": "Rumia",
+ "name": "Rumia",
"description": "Cuando ingiere una baya, la regurgita al final del siguiente turno y se la come por segunda vez."
},
"sharpness": {
- "name": "Cortante",
+ "name": "Cortante",
"description": "Aumenta la potencia de los movimientos cortantes."
},
"supremeOverlord": {
- "name": "General Supremo",
- "description": "Al entrar en combate, su Ataque y su Ataque Especial aumentan un poco por cada miembro del equipo que haya sido derrotado hasta el momento."
+ "name": "General Supremo",
+ "description": "Al entrar en combate, su ataque y su ataque especial aumentan un poco por cada miembro del equipo que haya sido derrotado hasta el momento."
},
"costar": {
- "name": "Unísono",
+ "name": "Unísono",
"description": "Al entrar en combate, copia los cambios en las características de su aliado."
},
"toxicDebris": {
- "name": "Capa Tóxica",
+ "name": "Capa Tóxica",
"description": "Al recibir daño de un ataque físico, lanza una trampa de púas tóxicas a los pies del rival."
},
"armorTail": {
- "name": "Cola Armadura",
+ "name": "Cola Armadura",
"description": "La extraña cola que le envuelve la cabeza impide al rival usar movimientos con prioridad contra él y sus aliados."
},
"earthEater": {
- "name": "Geofagia",
+ "name": "Geofagia",
"description": "Si lo alcanza un movimiento de tipo Tierra, recupera PS en vez de sufrir daño."
},
"myceliumMight": {
- "name": "Poder Fúngico",
+ "name": "Poder Fúngico",
"description": "El Pokémon siempre actúa con lentitud cuando usa movimientos de estado, pero estos no se ven afectados por la habilidad del objetivo."
},
"mindsEye": {
- "name": "Ojo Mental",
- "description": "Alcanza a Pokémon de tipo Fantasma con movimientos de tipo Normal o Lucha. Su Precisión no se puede reducir e ignora los cambios en la Evasión del objetivo."
+ "name": "Ojo Mental",
+ "description": "Alcanza a Pokémon de tipo Fantasma con movimientos de tipo Normal o Lucha. Su precisión no se puede reducir e ignora los cambios en la evasión del objetivo."
},
"supersweetSyrup": {
- "name": "Néctar Dulce",
- "description": "Al entrar en combate por primera vez, esparce un aroma dulzón a néctar que reduce la Evasión del rival."
+ "name": "Néctar Dulce",
+ "description": "Al entrar en combate por primera vez, esparce un aroma dulzón a néctar que reduce la evasión del rival."
},
"hospitality": {
- "name": "Hospitalidad",
+ "name": "Hospitalidad",
"description": "Al entrar en combate, restaura algunos PS de su aliado como muestra de hospitalidad."
},
"toxicChain": {
- "name": "Cadena Tóxica",
+ "name": "Cadena Tóxica",
"description": "Gracias al poder de su cadena impregnada de toxinas, puede envenenar gravemente al Pokémon al que ataque."
},
"embodyAspectTeal": {
- "name": "Evocarrecuerdos",
- "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la Máscara Cimiento y aumenta su Defensa."
+ "name": "Evocarrecuerdos",
+ "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la máscara turquesa y aumenta su velocidad."
},
"embodyAspectWellspring": {
- "name": "Evocarrecuerdos",
- "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la Máscara Cimiento y aumenta su Defensa."
+ "name": "Evocarrecuerdos",
+ "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la máscara fuente y aumenta su defensa especial."
},
"embodyAspectHearthflame": {
- "name": "Evocarrecuerdos",
- "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la Máscara Cimiento y aumenta su Defensa."
+ "name": "Evocarrecuerdos",
+ "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la máscara horno y aumenta su ataque."
},
"embodyAspectCornerstone": {
- "name": "Evocarrecuerdos",
- "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la Máscara Cimiento y aumenta su Defensa."
+ "name": "Evocarrecuerdos",
+ "description": "Al evocar viejos recuerdos, el Pokémon hace brillar la máscara cimiento y aumenta su defensa."
},
"teraShift": {
- "name": "Teracambio",
+ "name": "Teracambio",
"description": "Al entrar en combate, adopta la Forma Teracristal tras absorber la energía de su alrededor."
},
"teraShell": {
- "name": "Teracaparazón",
+ "name": "Teracaparazón",
"description": "Su caparazón encierra energía de todos los tipos. Gracias a ello, si sus PS están al máximo, el movimiento que lo alcance no será muy eficaz."
},
"teraformZero": {
- "name": "Teraformación 0",
+ "name": "Teraformación 0",
"description": "Cuando Terapagos adopta la Forma Astral, anula todos los efectos del tiempo atmosférico y de los campos que haya en el terreno gracias a su poder oculto."
},
"poisonPuppeteer": {
- "name": "Títere Tóxico",
+ "name": "Títere Tóxico",
"description": "Los rivales que Pecharunt envenene con sus movimientos también sufrirán confusión."
}
}
diff --git a/src/locales/es/achv.json b/src/locales/es/achv.json
index c94b8858233..14501dbdb6b 100644
--- a/src/locales/es/achv.json
+++ b/src/locales/es/achv.json
@@ -91,7 +91,7 @@
"name": "Campeón Liga Master",
"name_female": "Campeona Liga Master"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "Trabajo en Equipo",
"description": "Haz relevo a otro miembro del equipo con al menos una estadística al máximo."
},
@@ -175,4 +175,4 @@
"name": "Espejo ojepsE",
"description": "Completa el reto de Combate Inverso.\n.osrevnI etabmoC ed oter le atelpmoC"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/es/arena-flyout.json b/src/locales/es/arena-flyout.json
index b2881b5de76..e3ec1dc6d4a 100644
--- a/src/locales/es/arena-flyout.json
+++ b/src/locales/es/arena-flyout.json
@@ -1,21 +1,21 @@
{
- "activeBattleEffects": "Efectos de Terreno Activos",
+ "activeBattleEffects": "Efectos de terreno activos",
"player": "Jugador",
"neutral": "Neutral",
"enemy": "Enemigo",
"sunny": "Sol",
"rain": "Lluvia",
- "sandstorm": "Tormenta de Arena",
+ "sandstorm": "Tormenta de arena",
"hail": "Granizo",
"snow": "Nieve",
"fog": "Niebla",
"heavyRain": "Diluvio",
- "harshSun": "Sol Abrasador",
+ "harshSun": "Sol abrasador",
"strongWinds": "Turbulencias",
- "misty": "Campo de Niebla",
- "electric": "Campo Eléctrico",
- "grassy": "Campo de Hierba",
- "psychic": "Campo Psíquico",
+ "misty": "Campo de niebla",
+ "electric": "Campo eléctrico",
+ "grassy": "Campo de hierba",
+ "psychic": "Campo psíquico",
"mudSport": "Chapoteo Lodo",
"waterSport": "Hidrochorro",
"spikes": "Púas",
@@ -37,4 +37,4 @@
"craftyShield": "Truco Defensa",
"tailwind": "Viento Afín",
"happyHour": "Paga Extra"
-}
\ No newline at end of file
+}
diff --git a/src/locales/es/arena-tag.json b/src/locales/es/arena-tag.json
index 9e26dfeeb6e..0f63b62e784 100644
--- a/src/locales/es/arena-tag.json
+++ b/src/locales/es/arena-tag.json
@@ -1 +1,57 @@
-{}
\ No newline at end of file
+{
+ "yourTeam": "tu equipo",
+ "opposingTeam": "el equipo rival",
+ "arenaOnRemove": "Los efectos de {{moveName}} desaparecieron.",
+ "arenaOnRemovePlayer": "Los efectos de {{moveName}}\ndesaparecieron en tu bando.",
+ "arenaOnRemoveEnemy": "Los efectos de {{moveName}}\ndesaparecieron en el bando rival.",
+ "mistOnAdd": "¡Neblina de {{pokemonNameWithAffix}}\nha cubierto a su equipo!",
+ "mistApply": "¡La neblina evita los cambios de estadísticas!",
+ "reflectOnAdd": "¡Reflejo redujo el daño físico!",
+ "reflectOnAddPlayer": "¡Reflejo redujo el daño físico en tu bando!",
+ "reflectOnAddEnemy": "Reflejo redujo el daño físico en el bando rival.",
+ "lightScreenOnAdd": "¡Pantalla de Luz redujo el daño físico!",
+ "lightScreenOnAddPlayer": "¡Pantalla de Luz redujo el daño físico en tu bando!",
+ "lightScreenOnAddEnemy": "¡Pantalla de Luz redujo el daño físico en el bando enemigo!",
+ "auroraVeilOnAdd": "¡Velo Aurora redujo el daño físico!",
+ "auroraVeilOnAddPlayer": "¡Velo Aurora redujo el daño físico en tu bando!",
+ "auroraVeilOnAddEnemy": "¡Velo Aurora redujo el daño físico en el bando rival!",
+ "conditionalProtectOnAdd": "¡{{moveName}} protege a su bando!",
+ "conditionalProtectOnAddPlayer": "¡{{moveName}} protege a tu bando!",
+ "conditionalProtectOnAddEnemy": "¡{{moveName}} protege al bando rival!",
+ "conditionalProtectApply": "¡{{pokemonNameWithAffix}} ha sido protegido por {{moveName}}!",
+ "matBlockOnAdd": "¡{{pokemonNameWithAffix}} va a usar un tatami para bloquear ataques!",
+ "noCritOnAddPlayer": "¡{{moveName}} protege a tu bando de golpes críticos!",
+ "noCritOnAddEnemy": "¡{{moveName}} protege al bando rival de golpes críticos!",
+ "noCritOnRemove": "¡Los efectos de {{moveName}} de {{pokemonNameWithAffix}} se han disipado!",
+ "wishTagOnAdd": "¡El deseo de {{pokemonNameWithAffix}} se ha hecho realidad!",
+ "mudSportOnAdd": "¡Se han debilitado los ataques de tipo Eléctrico!",
+ "mudSportOnRemove": "Chapoteo Lodo ha dejado de surtir efecto.",
+ "waterSportOnAdd": "¡Se han debilitado los ataques\nde tipo Fuego!",
+ "waterSportOnRemove": "Hidrochorro ha dejado de surtir efecto.",
+ "spikesOnAdd": "¡El equipo de {{opponentDesc}} ha sido rodeado por {{moveName}}!",
+ "spikesActivateTrap": "¡Las púas han herido a {{pokemonNameWithAffix}}!",
+ "toxicSpikesOnAdd": "¡El equipo de {{opponentDesc}} ha sido rodeado por {{moveName}}!",
+ "toxicSpikesActivateTrapPoison": "¡{{pokemonNameWithAffix}} ha sido herido por {{moveName}}!",
+ "stealthRockOnAdd": "¡El equipo de {{opponentDesc}} ha sido rodeado por piedras puntiagudas!",
+ "stealthRockActivateTrap": "¡Unas piedras puntiagudas han dañado a {{pokemonNameWithAffix}}!",
+ "stickyWebOnAdd": "¡Una {{moveName}} se extiende a los pies del bando rival!",
+ "stickyWebActivateTrap": "¡{{pokemonName}} ha caído en una red viscosa!",
+ "trickRoomOnAdd": "¡{{pokemonNameWithAffix}} ha alterado las dimensiones!",
+ "trickRoomOnRemove": "Se han restaurado las dimensiones alteradas.",
+ "gravityOnAdd": "¡La gravedad se ha incrementado!",
+ "gravityOnRemove": "La gravedad ha vuelto a su estado normal.",
+ "tailwindOnAdd": "¡Sopla un viento afín!",
+ "tailwindOnAddPlayer": "¡El viento sopla a favor de tu bando!",
+ "tailwindOnAddEnemy": "¡El viento sopla a favor del bando rival!",
+ "tailwindOnRemove": "Ha dejado de soplar el viento afín.",
+ "tailwindOnRemovePlayer": "Ha dejado de soplar el viento que favorecía a tu equipo.",
+ "tailwindOnRemoveEnemy": "Ha dejado de soplar el viento que favorecía al bando rival.",
+ "happyHourOnAdd": "¡La felicidad se respira en el aire!",
+ "happyHourOnRemove": "La felicidad ya no se respira en el aire.",
+ "safeguardOnAdd": "¡Todos los Pokémon están protegidos por Velo Sagrado!",
+ "safeguardOnAddPlayer": "¡Tu equipo se ha protegido con Velo Sagrado!",
+ "safeguardOnAddEnemy": "¡El equipo enemigo se ha protegido con Velo Sagrado!",
+ "safeguardOnRemove": "¡Velo Sagrado dejó de hacer efecto!",
+ "safeguardOnRemovePlayer": "El efecto de Velo Sagrado en tu equipo se ha disipado.",
+ "safeguardOnRemoveEnemy": "El efecto de Velo Sagrado en el equipo enemigo se ha disipado."
+}
\ No newline at end of file
diff --git a/src/locales/es/battler-tags.json b/src/locales/es/battler-tags.json
index 9e26dfeeb6e..d917b6c74b5 100644
--- a/src/locales/es/battler-tags.json
+++ b/src/locales/es/battler-tags.json
@@ -1 +1,71 @@
-{}
\ No newline at end of file
+{
+ "trappedDesc": "trampa",
+ "flinchedDesc": "retroceso",
+ "confusedDesc": "confusión",
+ "infatuatedDesc": "enamoramiento",
+ "seedDesc": "drenado",
+ "nightmareDesc": "pesadillas",
+ "ingrainDesc": "raíces",
+ "drowsyDesc": "sueño",
+ "rechargingLapse": "¡{{pokemonNameWithAffix}} necesita\nrecuperarse de su ataque!",
+ "trappedOnAdd": "¡{{pokemonNameWithAffix}} no puede escapar!",
+ "trappedOnRemove": "¡{{pokemonNameWithAffix}} se ha\nliberado de {{moveName}}!",
+ "flinchedLapse": "¡{{pokemonNameWithAffix}} se amedrentó!",
+ "confusedOnAdd": "¡{{pokemonNameWithAffix}} se encuentra confuso!",
+ "confusedOnRemove": "¡{{pokemonNameWithAffix}} ya no está confuso!",
+ "confusedOnOverlap": "¡{{pokemonNameWithAffix}} ya está confuso!",
+ "confusedLapse": "¡{{pokemonNameWithAffix}} está confuso!",
+ "confusedLapseHurtItself": "¡Está tan confuso que se ha herido a sí mismo!",
+ "destinyBondLapseIsBoss": "Mismo Destino no afecta a {{pokemonNameWithAffix}}.",
+ "destinyBondLapse": "¡{{pokemonNameWithAffix2}} ha sufrido\nel mismo destino que {{pokemonNameWithAffix}}!",
+ "infatuatedOnAdd": "¡{{pokemonNameWithAffix}} se ha enamorado\nde {{sourcePokemonName}}!",
+ "infatuatedOnOverlap": "¡{{pokemonNameWithAffix}} ya está enamorado!",
+ "infatuatedLapse": "¡{{pokemonNameWithAffix}} se ha enamorado\ndebido a {{sourcePokemonName}}!",
+ "infatuatedLapseImmobilize": "¡El enamoramiento impide que\n{{pokemonNameWithAffix}} reaccione!",
+ "infatuatedOnRemove": "{{pokemonNameWithAffix}} ya no está enamorado.",
+ "seededOnAdd": "¡{{pokemonNameWithAffix}} ha sido infectado!",
+ "seededLapse": "¡Las drenadoras han restado salud a {{pokemonNameWithAffix}}!",
+ "seededLapseShed": "¡{{pokemonNameWithAffix}} ha absorbido el lodo líquido!",
+ "nightmareOnAdd": "¡{{pokemonNameWithAffix}} se ha sumido en una pesadilla!",
+ "nightmareOnOverlap": "¡{{pokemonNameWithAffix}} ya está teniendo pesadillas!",
+ "nightmareLapse": "¡{{pokemonNameWithAffix}} sufre pesadillas!",
+ "encoreOnAdd": "¡{{pokemonNameWithAffix}} sufre los efectos de Otra Vez!",
+ "encoreOnRemove": "¡{{pokemonNameWithAffix}} ya no sufre los efectos de Otra Vez!",
+ "helpingHandOnAdd": "¡{{pokemonNameWithAffix}} se prepara\npara ayudar a {{pokemonName}}!",
+ "ingrainLapse": "¡{{pokemonNameWithAffix}} ha absorbido\nnutrientes a través de sus raíces!",
+ "ingrainOnTrap": "¡{{pokemonNameWithAffix}} ha echado raíces!",
+ "aquaRingOnAdd": "¡{{pokemonNameWithAffix}} se ha rodeado de un manto de agua!",
+ "aquaRingLapse": "¡{{pokemonName}} restauró sus PS con {{moveName}}!",
+ "drowsyOnAdd": "¡{{pokemonNameWithAffix}} empieza a tener sueño!",
+ "damagingTrapLapse": "¡{{moveName}} hiere a {{pokemonNameWithAffix}}!",
+ "bindOnTrap": "¡{{moveName}} de {{sourcePokemonName}} oprime a {{pokemonNameWithAffix}}!",
+ "wrapOnTrap": "¡{{sourcePokemonName}} ha atrapado a {{pokemonNameWithAffix}} con una constricción!",
+ "vortexOnTrap": "¡{{pokemonNameWithAffix}} no puede salir del torbellino!",
+ "clampOnTrap": "¡{{sourcePokemonNameWithAffix}} ha atenazado a \n{{pokemonName}}!",
+ "sandTombOnTrap": "¡{{pokemonNameWithAffix}} ha sido atrapado por {{moveName}}!",
+ "magmaStormOnTrap": "¡La lluvia ígnea cae sobre {{pokemonNameWithAffix}}!",
+ "snapTrapOnTrap": "¡{{pokemonNameWithAffix}} cayó en un cepo!",
+ "thunderCageOnTrap": "¡{{sourcePokemonNameWithAffix}} ha enjaulado a {{pokemonNameWithAffix}}!",
+ "infestationOnTrap": "¡{{pokemonNameWithAffix}} es presa del acoso de {{sourcePokemonNameWithAffix}}!",
+ "protectedOnAdd": "{{pokemonNameWithAffix}}\nse está protegiendo.",
+ "protectedLapse": "¡{{pokemonNameWithAffix}}\nse ha protegido!",
+ "enduringOnAdd": "{{pokemonNameWithAffix}} se prepara para resistir los ataques...",
+ "enduringLapse": "¡{{pokemonNameWithAffix}} ha encajado el golpe!",
+ "sturdyLapse": "¡{{pokemonNameWithAffix}} ha encajado el golpe!",
+ "perishSongLapse": "La cuenta atrás de Canto Mortal de\n{{pokemonNameWithAffix}} ha bajado a {{turnCount}}.",
+ "centerOfAttentionOnAdd": "¡{{pokemonNameWithAffix}} es el centro de atención!",
+ "truantLapse": "{{pokemonNameWithAffix}} está holgazaneando...",
+ "slowStartOnAdd": "¡{{pokemonNameWithAffix}} no está dando todo de sí!",
+ "slowStartOnRemove": "¡{{pokemonNameWithAffix}} ya puede darlo todo!",
+ "highestStatBoostOnAdd": "¡{{pokemonNameWithAffix}} ha reforzado su {{statName}}!",
+ "highestStatBoostOnRemove": "¡Los efectos de {{abilityName}}\nde {{pokemonNameWithAffix}} han desaparecido!",
+ "magnetRisenOnAdd": "¡{{pokemonNameWithAffix}} levita gracias a un campo electromagnético!",
+ "magnetRisenOnRemove": "¡El campo electromagnético de {{pokemonNameWithAffix}} se ha disipado!",
+ "critBoostOnAdd": "¡{{pokemonNameWithAffix}} se está preparando para luchar!",
+ "critBoostOnRemove": "{{pokemonNameWithAffix}} se ha relajado.",
+ "saltCuredOnAdd": "¡{{pokemonNameWithAffix}} está en salazón!",
+ "saltCuredLapse": "¡{{moveName}} ha herido a {{pokemonNameWithAffix}}!",
+ "cursedOnAdd": "¡{{pokemonNameWithAffix}} sacrifica algunos PS y maldice a {{pokemonName}}!",
+ "cursedLapse": "¡{{pokemonNameWithAffix}} es víctima de una maldición!",
+ "stockpilingOnAdd": "¡{{pokemonNameWithAffix}} ha reservado energía por {{stockpiledCount}}ª vez!"
+}
diff --git a/src/locales/es/bgm-name.json b/src/locales/es/bgm-name.json
index be617b79567..f0e0ab7e852 100644
--- a/src/locales/es/bgm-name.json
+++ b/src/locales/es/bgm-name.json
@@ -1,95 +1,99 @@
{
"music": "Música: ",
"missing_entries": "{{name}}",
- "battle_kanto_champion": "B2W2 - ¡Vs Campeón de Kanto!",
- "battle_johto_champion": "B2W2 - ¡Vs Campeón de Johto!",
- "battle_hoenn_champion_g5": "B2W2 - ¡Vs Campeón de Hoenn!",
- "battle_hoenn_champion_g6": "ORAS - ¡Vs Campeón de Hoenn!",
- "battle_sinnoh_champion": "B2W2 - ¡Vs Campeón de Sinnoh!",
- "battle_champion_alder": "BW - ¡Vs Campeón de Teselia!",
- "battle_champion_iris": "B2W2 - ¡Vs Campeón de Teselia!",
- "battle_kalos_champion": "XY - ¡Vs Campeón de Kalos!",
- "battle_alola_champion": "USUM - ¡Vs Campeón de Alola!",
- "battle_galar_champion": "SWSH - ¡Vs Campeón de Galar!",
- "battle_champion_geeta": "SV - ¡Vs Campeona Ságita!",
- "battle_champion_nemona": "SV - ¡Vs Campeona Mencía!",
- "battle_champion_kieran": "SV - ¡Vs Campeón Cass!",
- "battle_hoenn_elite": "ORAS - ¡Vs Alto Mando!",
- "battle_unova_elite": "BW - ¡Vs Alto Mando!",
- "battle_kalos_elite": "XY - ¡Vs Alto Mando!",
- "battle_alola_elite": "SM - ¡Vs Alto Mando!",
- "battle_galar_elite": "SWSH - Torneo de Finalistas",
- "battle_paldea_elite": "SV - ¡Vs Alto Mando!",
- "battle_bb_elite": "SV - ¡Vs Alto Mando de la Academia Arándano!",
+ "battle_kanto_champion": "B2W2 - ¡Vs. Campeón de Kanto!",
+ "battle_johto_champion": "B2W2 - ¡Vs. Campeón de Johto!",
+ "battle_hoenn_champion_g5": "B2W2 - ¡Vs. Campeón de Hoenn!",
+ "battle_hoenn_champion_g6": "ORAS - ¡Vs. Campeón de Hoenn!",
+ "battle_sinnoh_champion": "B2W2 - ¡Vs. Campeón de Sinnoh!",
+ "battle_champion_alder": "BW - ¡Vs. Campeón de Teselia!",
+ "battle_champion_iris": "B2W2 - ¡Vs. Campeón de Teselia!",
+ "battle_kalos_champion": "XY - ¡Vs. Campeón de Kalos!",
+ "battle_alola_champion": "USUM - ¡Vs. Campeón de Alola!",
+ "battle_galar_champion": "SWSH - ¡Vs. Campeón de Galar!",
+ "battle_champion_geeta": "SV - ¡Vs. Campeona Ságita!",
+ "battle_champion_nemona": "SV - ¡Vs. Campeona Mencía!",
+ "battle_champion_kieran": "SV - ¡Vs. Campeón Cass!",
+ "battle_hoenn_elite": "ORAS - ¡Vs. Alto Mando!",
+ "battle_unova_elite": "BW - ¡Vs. Alto Mando!",
+ "battle_kalos_elite": "XY - ¡Vs. Alto Mando!",
+ "battle_alola_elite": "SM - ¡Vs. Alto Mando!",
+ "battle_galar_elite": "SWSH - Torneo de finalistas",
+ "battle_paldea_elite": "SV - ¡Vs. Alto Mando!",
+ "battle_bb_elite": "SV - ¡Vs. Alto Mando de la Academia Arándano!",
"battle_final_encounter": "PMD RTDX - Dominio de Rayquaza",
- "battle_final": "BW - ¡Vs Ghechis!",
- "battle_kanto_gym": "B2W2 - ¡Vs Líder de Kanto!",
- "battle_johto_gym": "B2W2 - ¡Vs Líder de Johto!",
- "battle_hoenn_gym": "B2W2 - ¡Vs Líder de Hoenn!",
- "battle_sinnoh_gym": "B2W2 - ¡Vs Líder de Sinnoh!",
- "battle_unova_gym": "BW - ¡Vs Líder de Teselia!",
- "battle_kalos_gym": "XY - ¡Vs Líder de Kalos!",
- "battle_galar_gym": "SWSH - ¡Vs Líder de Galar!",
- "battle_paldea_gym": "SV - ¡Vs Líder de Paldea!",
- "battle_legendary_kanto": "XY - ¡Vs Legendarios de Kanto!",
- "battle_legendary_raikou": "HGSS - ¡Vs Raikou!",
- "battle_legendary_entei": "HGSS - ¡Vs Entei!",
- "battle_legendary_suicune": "HGSS - ¡Vs Suicune!",
- "battle_legendary_lugia": "HGSS - ¡Vs Lugia!",
- "battle_legendary_ho_oh": "HGSS - ¡Vs Ho-oh!",
- "battle_legendary_regis_g5": "B2W2 - ¡Vs Regis!",
- "battle_legendary_regis_g6": "ORAS - ¡Vs Regis!",
- "battle_legendary_gro_kyo": "ORAS - ¡Vs Groudon/Kyogre!",
- "battle_legendary_rayquaza": "ORAS - ¡Vs Rayquaza!",
- "battle_legendary_deoxys": "ORAS - ¡Vs Deoxys!",
- "battle_legendary_lake_trio": "ORAS - ¡Vs Trío del Lago!",
- "battle_legendary_sinnoh": "ORAS - ¡Vs Legendarios de Sinnoh!",
- "battle_legendary_dia_pal": "ORAS - ¡Vs Dialga/Palkia!",
- "battle_legendary_giratina": "ORAS - ¡Vs Giratina!",
- "battle_legendary_arceus": "HGSS - ¡Vs Arceus!",
- "battle_legendary_unova": "BW - ¡Vs Legendarios de Teselia!",
- "battle_legendary_kyurem": "BW - ¡Vs Kyurem!",
- "battle_legendary_res_zek": "BW - ¡Vs Reshiram/Zekrom!",
- "battle_legendary_xern_yvel": "XY - ¡Vs Xerneas/Yveltal!",
- "battle_legendary_tapu": "SM - ¡Vs Tapus!",
- "battle_legendary_sol_lun": "SM - ¡Vs Solgaleo/Lunala!",
- "battle_legendary_ub": "SM - ¡Vs Ultraentes!",
- "battle_legendary_dusk_dawn": "USUM - ¡Vs Necrozma Melena Crepuscular/Alas del Alba!",
- "battle_legendary_ultra_nec": "USUM - ¡Vs Ultra-Necrozma!",
- "battle_legendary_zac_zam": "SWSH - ¡Vs Zacian/Zamazenta!",
- "battle_legendary_glas_spec": "SWSH - ¡Vs Glastrier/Spectrier!",
- "battle_legendary_calyrex": "SWSH - ¡Vs Calyrex!",
- "battle_legendary_birds_galar": "SWSH - ¡Vs Aves Legendarias de Galar!",
- "battle_legendary_ruinous": "SV - ¡Vs Tesoros Funestos!",
- "battle_legendary_kor_mir": "SV Depths of Area Zero Battle",
- "battle_legendary_loyal_three": "SV - ¡Vs Compatrones!",
- "battle_legendary_ogerpon": "SV - ¡Vs Ogerpon!",
- "battle_legendary_terapagos": "SV - ¡Vs Terapagos!",
- "battle_legendary_pecharunt": "SV - ¡Vs Pecharunt!",
- "battle_rival": "BW - ¡Vs Rival!",
+ "battle_final": "BW - ¡Vs. Ghechis!",
+ "battle_kanto_gym": "B2W2 - ¡Vs. Líder de Kanto!",
+ "battle_johto_gym": "B2W2 - ¡Vs. Líder de Johto!",
+ "battle_hoenn_gym": "B2W2 - ¡Vs. Líder de Hoenn!",
+ "battle_sinnoh_gym": "B2W2 - ¡Vs. Líder de Sinnoh!",
+ "battle_unova_gym": "BW - ¡Vs. Líder de Teselia!",
+ "battle_kalos_gym": "XY - ¡Vs. Líder de Kalos!",
+ "battle_galar_gym": "SWSH - ¡Vs. Líder de Galar!",
+ "battle_paldea_gym": "SV - ¡Vs. Líder de Paldea!",
+ "battle_legendary_kanto": "XY - ¡Vs. Legendarios de Kanto!",
+ "battle_legendary_raikou": "HGSS - ¡Vs. Raikou!",
+ "battle_legendary_entei": "HGSS - ¡Vs. Entei!",
+ "battle_legendary_suicune": "HGSS - ¡Vs. Suicune!",
+ "battle_legendary_lugia": "HGSS - ¡Vs. Lugia!",
+ "battle_legendary_ho_oh": "HGSS - ¡Vs. Ho-oh!",
+ "battle_legendary_regis_g5": "B2W2 - ¡Vs. Regis!",
+ "battle_legendary_regis_g6": "ORAS - ¡Vs. Regis!",
+ "battle_legendary_gro_kyo": "ORAS - ¡Vs. Groudon/Kyogre!",
+ "battle_legendary_rayquaza": "ORAS - ¡Vs. Rayquaza!",
+ "battle_legendary_deoxys": "ORAS - ¡Vs. Deoxys!",
+ "battle_legendary_lake_trio": "ORAS - ¡Vs. trío del Lago!",
+ "battle_legendary_sinnoh": "ORAS - ¡Vs. legendarios de Sinnoh!",
+ "battle_legendary_dia_pal": "ORAS - ¡Vs. Dialga/Palkia!",
+ "battle_legendary_origin_forme": "LA - ¡Vs. Dialga & Palkia, Forma Origen!",
+ "battle_legendary_giratina": "ORAS - ¡Vs. Giratina!",
+ "battle_legendary_arceus": "HGSS - ¡Vs. Arceus!",
+ "battle_legendary_unova": "BW - ¡Vs. legendarios de Teselia!",
+ "battle_legendary_kyurem": "BW - ¡Vs. Kyurem!",
+ "battle_legendary_res_zek": "BW - ¡Vs. Reshiram/Zekrom!",
+ "battle_legendary_xern_yvel": "XY - ¡Vs. Xerneas/Yveltal!",
+ "battle_legendary_tapu": "SM - ¡Vs. Tapus!",
+ "battle_legendary_sol_lun": "SM - ¡Vs. Solgaleo/Lunala!",
+ "battle_legendary_ub": "SM - ¡Vs. Ultraentes!",
+ "battle_legendary_dusk_dawn": "USUM - ¡Vs. Necrozma Melena Crepuscular/Alas del Alba!",
+ "battle_legendary_ultra_nec": "USUM - ¡Vs. Ultra-Necrozma!",
+ "battle_legendary_zac_zam": "SWSH - ¡Vs. Zacian/Zamazenta!",
+ "battle_legendary_glas_spec": "SWSH - ¡Vs. Glastrier/Spectrier!",
+ "battle_legendary_calyrex": "SWSH - ¡Vs. Calyrex!",
+ "battle_legendary_riders": "SWSH - ¡Vs. Calyrex Jinete!",
+ "battle_legendary_birds_galar": "SWSH - ¡Vs. Aves Legendarias de Galar!",
+ "battle_legendary_ruinous": "SV - ¡Vs. Tesoros Funestos!",
+ "battle_legendary_kor_mir": "SV - ¡Batalla en el área Zero!",
+ "battle_legendary_loyal_three": "SV - ¡Vs. Compatrones!",
+ "battle_legendary_ogerpon": "SV - ¡Vs. Ogerpon!",
+ "battle_legendary_terapagos": "SV - ¡Vs. Terapagos!",
+ "battle_legendary_pecharunt": "SV - ¡Vs. Pecharunt!",
+ "battle_rival": "BW - ¡Vs. Rival!",
"battle_rival_2": "BW - ¡Vs N!",
- "battle_rival_3": "BW - ¡Vs N (Liga Pokémon)!",
- "battle_trainer": "BW - ¡Vs Entrenador!",
- "battle_wild": "BW - ¡Vs Pokémon Salvaje!",
- "battle_wild_strong": "BW - ¡Vs Pokémon Salvaje Raro!",
- "end_summit": "PMD RTDX - Techo del Cielo",
- "battle_rocket_grunt": "HGSS Team Rocket Battle",
- "battle_aqua_magma_grunt": "ORAS Team Aqua & Magma Battle",
- "battle_galactic_grunt": "BDSP Team Galactic Battle",
+ "battle_rival_3": "BW - ¡Vs. N (Liga Pokémon)!",
+ "battle_trainer": "BW - ¡Vs. entrenador!",
+ "battle_wild": "BW - ¡Vs. Pokémon salvaje!",
+ "battle_wild_strong": "BW - ¡Vs. Pokémon salvaje raro!",
+ "end_summit": "PMD RTDX - Techo del cielo",
+ "battle_rocket_grunt": "HGSS - ¡Vs. Team Rocket!",
+ "battle_aqua_magma_grunt": "ORAS - ¡Vs. Equipo Aqua & Magma!",
+ "battle_galactic_grunt": "BDSP - ¡Vs. Equipo Galaxia!",
"battle_plasma_grunt": "BW - ¡Vs Equipo Plasma!",
- "battle_flare_grunt": "XY Team Flare Battle",
- "battle_aether_grunt": "SM Aether Foundation Battle",
- "battle_skull_grunt": "SM Team Skull Battle",
- "battle_macro_grunt": "SWSH Trainer Battle",
- "battle_galactic_admin": "BDSP Team Galactic Admin Battle",
- "battle_skull_admin": "SM Team Skull Admin Battle",
- "battle_oleana": "SWSH Oleana Battle",
- "battle_rocket_boss": "USUM Giovanni Battle",
- "battle_aqua_magma_boss": "ORAS Archie & Maxie Battle",
- "battle_galactic_boss": "BDSP Cyrus Battle",
- "battle_plasma_boss": "B2W2 Ghetsis Battle",
- "battle_flare_boss": "XY Lysandre Battle",
-
+ "battle_flare_grunt": "XY - ¡Vs. Team Flare!",
+ "battle_aether_grunt": "SM - ¡Vs. Fundación Æther!",
+ "battle_skull_grunt": "SM - ¡Vs. Team Skull!",
+ "battle_macro_grunt": "SWSH - ¡Vs. entrenador!",
+ "battle_galactic_admin": "BDSP - ¡Vs. Comandante del Equipo Galaxia!",
+ "battle_skull_admin": "SM - ¡Vs. Comandante del Team Skull!",
+ "battle_oleana": "SWSH - ¡Vs. Olivia!",
+ "battle_rocket_boss": "USUM - ¡Vs. Giovanni!",
+ "battle_aqua_magma_boss": "ORAS - ¡Vs. Aquiles & Magno!",
+ "battle_galactic_boss": "BDSP - ¡Vs. Helio!",
+ "battle_plasma_boss": "B2W2 - ¡Vs. Ghechis Armonia!",
+ "battle_flare_boss": "XY - ¡Vs. Lysson!",
+ "battle_aether_boss": "SM - ¡Vs. Samina!",
+ "battle_skull_boss": "SM - ¡Vs. Guzmán!",
+ "battle_macro_boss": "SWSH - ¡Vs. Rose!",
"abyss": "PMD EoS - Cráter Oscuro",
"badlands": "PMD EoS - Valle Desolado",
"beach": "PMD EoS - Risco Calado",
@@ -105,40 +109,40 @@
"graveyard": "PMD EoS - Bosque Misterio",
"ice_cave": "PMD EoS - Gran Iceberg",
"island": "PMD EoS - Costa Escarpada",
- "jungle": "Lmz - Jungle",
- "laboratory": "Firel - Laboratory",
+ "jungle": "Lmz - Jungla",
+ "laboratory": "Firel - Laboratorio",
"lake": "PMD EoS - Cueva Cristal",
"meadow": "PMD EoS - Bosque de la Cumbre del Cielo",
- "metropolis": "Firel - Metropolis",
+ "metropolis": "Firel - Metrópolis",
"mountain": "PMD EoS - Monte Cuerno",
"plains": "PMD EoS - Pradera de la Cumbre del Cielo",
"power_plant": "PMD EoS - Pradera Destello",
"ruins": "PMD EoS - Sima Hermética",
- "sea": "Andr06 - Marine Mystique",
- "seabed": "Firel - Seabed",
- "slum": "Andr06 - Sneaky Snom",
+ "sea": "Andr06 - Misticismo marino",
+ "seabed": "Firel - Lecho del mar",
+ "slum": "Andr06 - Snom sigiloso",
"snowy_forest": "PMD EoS - Campo nevado de la Cumbre del Cielo",
- "space": "Firel - Aether",
+ "space": "Firel - Æther ",
"swamp": "PMD EoS - Mar Circundante",
"tall_grass": "PMD EoS - Bosque Niebla",
"temple": "PMD EoS - Cueva Regia",
"town": "PMD EoS - Tema del territorio aleatorio 3",
"volcano": "PMD EoS - Cueva Vapor",
"wasteland": "PMD EoS - Corazón Tierra Oculta",
- "encounter_ace_trainer": "BW - Desafío Combate (Entrenador Guay)",
- "encounter_backpacker": "BW - Desafío Combate (Mochilero)",
- "encounter_clerk": "BW - Desafío Combate (Empresario)",
- "encounter_cyclist": "BW - Desafío Combate (Ciclista)",
- "encounter_lass": "BW - Desafío Combate (Chica)",
- "encounter_parasol_lady": "BW - Desafío Combate (Dama parasol)",
- "encounter_pokefan": "BW - Desafío Combate (Pokéfan)",
- "encounter_psychic": "BW - Desafío Combate (Médium)",
- "encounter_rich": "BW - Desafío Combate (Aristócrata)",
- "encounter_rival": "BW - Desafío Combate (Cheren)",
- "encounter_roughneck": "BW - Desafío Combate (Calvo)",
- "encounter_scientist": "BW - Desafío Combate (Científico)",
- "encounter_twins": "BW - Desafío Combate (Gemelas)",
- "encounter_youngster": "BW - Desafío Combate (Joven)",
+ "encounter_ace_trainer": "BW - ¡Vs. entrenador guay!",
+ "encounter_backpacker": "BW - ¡Vs. mochilero!",
+ "encounter_clerk": "BW - ¡Vs. empresario!",
+ "encounter_cyclist": "BW - ¡Vs. ciclista!",
+ "encounter_lass": "BW - ¡Vs. chica joven!",
+ "encounter_parasol_lady": "BW - ¡Vs. dama parasol!",
+ "encounter_pokefan": "BW - ¡Vs. poké-fan!",
+ "encounter_psychic": "BW -¡Vs. médium!",
+ "encounter_rich": "BW - ¡Vs. aristócrata!",
+ "encounter_rival": "BW - ¡Vs. Cheren!",
+ "encounter_roughneck": "BW - ¡Vs. tío chungo!",
+ "encounter_scientist": "BW - ¡Vs. científico!",
+ "encounter_twins": "BW - ¡Vs. gemelas!",
+ "encounter_youngster": "BW - ¡Vs. chico joven!",
"heal": "BW - Cura Pokémon",
"menu": "PMD EoS - ¡Bienvenidos al mundo de los Pokémon!",
"title": "PMD EoS - Tema del menú principal"
diff --git a/src/locales/es/game-mode.json b/src/locales/es/game-mode.json
index e7925900253..0dbccb45e1f 100644
--- a/src/locales/es/game-mode.json
+++ b/src/locales/es/game-mode.json
@@ -1,8 +1,8 @@
{
- "classic": "Clásica",
- "endless": "Infinita",
- "endlessSpliced": "Infinita (Fusión)",
- "dailyRun": "Diaria",
- "unknown": "Desconicido",
+ "classic": "Clásico",
+ "endless": "Infinito",
+ "endlessSpliced": "Infinito (Fusión)",
+ "dailyRun": "Diario",
+ "unknown": "Desconocido",
"challenge": "Desafío"
-}
\ No newline at end of file
+}
diff --git a/src/locales/es/modifier-type.json b/src/locales/es/modifier-type.json
index 95325788bf4..e18cb19244d 100644
--- a/src/locales/es/modifier-type.json
+++ b/src/locales/es/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "Duplica la posibilidad de que un encuentro sea una combate doble durante {{battleCount}} combates."
},
- "TempBattleStatBoosterModifierType": {
- "description": "Aumenta la est. {{tempBattleStatName}} de todos los miembros del equipo en 1 nivel durante 5 combates."
+ "TempStatStageBoosterModifierType": {
+ "description": "Aumenta la est. {{stat}} de todos los miembros del equipo en 1 nivel durante 5 combates."
},
"AttackTypeBoosterModifierType": {
"description": "Aumenta la potencia de los movimientos de tipo {{moveType}} de un Pokémon en un 20%."
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "Aumenta el nivel de todos los miembros del equipo en {{levels}}."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "Aumenta la est. {{statName}} base del portador en un 10%.\nCuanto mayores sean tus IVs, mayor será el límite de acumulación."
+ "BaseStatBoosterModifierType": {
+ "description": "Aumenta la est. {{stat}} base del portador en un 10%.\nCuanto mayores sean tus IVs, mayor será el límite de acumulación."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "Restaura el 100% de los PS de todos los Pokémon."
@@ -248,6 +248,12 @@
"name": "Periscopio",
"description": "Aumenta la probabilidad de asestar un golpe crítico."
},
+ "DIRE_HIT": {
+ "name": "Crítico X",
+ "extra": {
+ "raises": "Critical Hit Ratio"
+ }
+ },
"LEEK": {
"name": "Puerro",
"description": "Puerro muy largo y duro que aumenta la probabilidad de asestar un golpe crítico. Debe llevarlo Farfetch'd."
@@ -411,25 +417,13 @@
"description": "Polvo muy fino, pero a la vez poderoso, que aumenta la Velocidad. Debe llevarlo Ditto."
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "Ataque X",
"x_defense": "Defensa X",
"x_sp_atk": "Ataq. Esp. X",
"x_sp_def": "Def. Esp. X",
"x_speed": "Velocidad X",
- "x_accuracy": "Precisión X",
- "dire_hit": "Crítico X"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "Ataque",
- "DEF": "Defensa",
- "SPATK": "Ataq. Esp.",
- "SPDEF": "Def. Esp.",
- "SPD": "Velocidad",
- "ACC": "Precisión",
- "CRIT": "Tasa de crítico",
- "EVA": "Evasión",
- "DEFAULT": "???"
+ "x_accuracy": "Precisión X"
},
"AttackTypeBoosterItem": {
"silk_scarf": "Pañuelo seda",
@@ -604,6 +598,6 @@
"DRAGON_MEMORY": "Disco dragón",
"DARK_MEMORY": "Disco siniestro",
"FAIRY_MEMORY": "Disco hada",
- "BLANK_MEMORY": "Disco en blanco"
+ "NORMAL_MEMORY": "Disco normal"
}
}
diff --git a/src/locales/es/modifier.json b/src/locales/es/modifier.json
index 593b3df2f0f..a94e41a4574 100644
--- a/src/locales/es/modifier.json
+++ b/src/locales/es/modifier.json
@@ -1,3 +1,12 @@
{
- "bypassSpeedChanceApply": "¡Gracias {{itemName}} {{pokemonName}} puede tener prioridad!"
-}
\ No newline at end of file
+ "surviveDamageApply": "{{pokemonNameWithAffix}} ha usado {{typeName}} y ha logrado resistir!",
+ "turnHealApply": "{{pokemonNameWithAffix}} ha recuperado unos pocos PS gracias a {{typeName}}!",
+ "hitHealApply": "{{pokemonNameWithAffix}} ha recuperado unos pocos PS gracias a {{typeName}}!",
+ "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} ha sido revivido gracias a su {{typeName}}!",
+ "pokemonResetNegativeStatStageApply": "Las estadísticas bajadas de {{pokemonNameWithAffix}} fueron restauradas gracias a {{typeName}}!",
+ "moneyInterestApply": "Recibiste intereses de ₽{{moneyAmount}}\ngracias a {{typeName}}!",
+ "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} fue absorbido\npor {{pokemonName}}'s {{typeName}}!",
+ "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} fue robado por {{pokemonName}}'s {{typeName}}!",
+ "enemyTurnHealApply": "¡{{pokemonNameWithAffix}}\nrecuperó algunos PS!",
+ "bypassSpeedChanceApply": "¡Gracias a su {{itemName}}, {{pokemonName}} puede tener prioridad!"
+}
diff --git a/src/locales/es/move-trigger.json b/src/locales/es/move-trigger.json
index b570f029377..f92b7950a07 100644
--- a/src/locales/es/move-trigger.json
+++ b/src/locales/es/move-trigger.json
@@ -1,4 +1,8 @@
{
+ "switchedTwoStatChanges": "{{pokemonName}} ha intercambiado los cambios en {{firstStat}} y {{secondStat}} con los del objetivo!",
+ "switchedStat": "{{pokemonName}} cambia su {{stat}} por la de su objetivo!",
+ "sharedGuard": "{{pokemonName}} suma su capacidad defensiva a la del objetivo y la reparte equitativamente!",
+ "sharedPower": "{{pokemonName}} suma su capacidad ofensiva a la del objetivo y la reparte equitativamente!",
"isChargingPower": "¡{{pokemonName}} está acumulando energía!",
"burnedItselfOut": "¡El fuego interior de {{pokemonName}} se ha extinguido!",
"startedHeatingUpBeak": "¡{{pokemonName}} empieza\na calentar su pico!",
@@ -7,5 +11,6 @@
"usedUpAllElectricity": "¡{{pokemonName}} ha descargado toda su electricidad!",
"stoleItem": "¡{{pokemonName}} robó el objeto\n{{itemName}} de {{targetName}}!",
"statEliminated": "¡Los cambios en estadísticas fueron eliminados!",
- "revivalBlessing": "¡{{pokemonName}} ha revivido!"
-}
\ No newline at end of file
+ "revivalBlessing": "¡{{pokemonName}} ha revivido!",
+ "safeguard": "¡{{targetName}} está protegido por Velo Sagrado!"
+}
diff --git a/src/locales/es/party-ui-handler.json b/src/locales/es/party-ui-handler.json
index 65552a1e1d5..0e59aee6fd1 100644
--- a/src/locales/es/party-ui-handler.json
+++ b/src/locales/es/party-ui-handler.json
@@ -1,4 +1,9 @@
{
+ "SEND_OUT": "Enviar",
+ "SUMMARY": "Resumen",
+ "CANCEL": "Cancelar",
+ "RELEASE": "Liberar",
+ "APPLY": "Aplicar",
"TEACH": "Enseñar",
"SPLICE": "Fusionar",
"UNSPLICE": "Separar",
@@ -7,23 +12,23 @@
"TRANSFER": "Transferir",
"ALL": "Todo",
"PASS_BATON": "Relevo",
- "UNPAUSE_EVOLUTION": "Reanudar Evolución",
+ "UNPAUSE_EVOLUTION": "Reanudar evolución",
"REVIVE": "Revivir",
"RENAME": "Rename",
"choosePokemon": "Elige a un Pokémon.",
"doWhatWithThisPokemon": "¿Que quieres hacer con este Pokémon?",
- "noEnergy": "¡A {{pokemonName}} no le quedan\nfuerzas para luchar!",
- "hasEnergy": "¡A {{pokemonName}} le quedan\nfuerzas para luchar!",
- "cantBeUsed": "¡{{pokemonName}} no puede usarse\nen este desafío!",
- "tooManyItems": "¡{{pokemonName}} tiene demasiados\nde este objeto!",
+ "noEnergy": "¡A {{pokemonName}} no le\nquedan fuerzas para luchar!",
+ "hasEnergy": "¡A {{pokemonName}} le\nquedan fuerzas para luchar!",
+ "cantBeUsed": "¡{{pokemonName}} no puede usarse en este desafío!",
+ "tooManyItems": "¡{{pokemonName}} tiene\ndemasiado de este objeto!",
"anyEffect": "No tendría ningún efecto.",
- "unpausedEvolutions": "Se reanudó las evoluciones de {{pokemonName}}.",
+ "unpausedEvolutions": "Se reanudaron las evoluciones de {{pokemonName}}.",
"unspliceConfirmation": "¿Seguro que quiere separar a {{fusionName}}\nde {{pokemonName}}? {{fusionName}} se perderá.",
"wasReverted": "{{fusionName}} se revirtió a {{pokemonName}}.",
"releaseConfirmation": "¿Quieres liberar a {{pokemonName}}?",
"releaseInBattle": "¡No puedes liberar un Pokémon que está en batalla!",
"selectAMove": "Selecciona un movimiento.",
- "changeQuantity": "Selecciona un objeto equipado para transferir.\nUsa < y > para cambiar la cantidad.",
+ "changeQuantity": "Selecciona un ítem para transferir.\nUsa < y > para calibrar.",
"selectAnotherPokemonToSplice": "Selecciona otro Pokémon para fusionar.",
"cancel": "Salir",
"able": "Apto",
@@ -36,7 +41,7 @@
"thisIsWhereWePart": "¡Aquí es donde nos despedimos, {{pokemonName}}!",
"illMissYou": "¡Te echaré de menos, {{pokemonName}}!",
"illNeverForgetYou": "¡Nunca te olvidaré, {{pokemonName}}!",
- "untilWeMeetAgain": "¡Hasta que nos volvamos a encontrar, {{pokemonName}}!",
+ "untilWeMeetAgain": "¡Hasta que nos volvamos a\nencontrar, {{pokemonName}}!",
"sayonara": "¡Sayonara, {{pokemonName}}!",
"smellYaLater": "¡Nos vemos luego, {{pokemonName}}!"
-}
\ No newline at end of file
+}
diff --git a/src/locales/es/pokemon-form-battle.json b/src/locales/es/pokemon-form-battle.json
index 7af01f8f093..d6eed9e93cc 100644
--- a/src/locales/es/pokemon-form-battle.json
+++ b/src/locales/es/pokemon-form-battle.json
@@ -4,5 +4,11 @@
"mega-y": "Mega {{pokemonName}} Y",
"primal": "{{pokemonName}} Primigenio",
"gigantamax": "G-Max {{pokemonName}}",
- "eternamax": "E-Max {{pokemonName}}"
-}
\ No newline at end of file
+ "eternamax": "E-Max {{pokemonName}}",
+ "megaChange": "¡{{preName}} ha mega-evolucionado a {{pokemonName}}!",
+ "gigantamaxChange": "¡{{preName}} ha gigamaxizado a {{pokemonName}}!",
+ "eternamaxChange": "¡{{preName}} ha eternamaxizado a {{pokemonName}}!",
+ "revertChange": "¡{{pokemonName}} ha revertido a su forma original!",
+ "formChange": "¡{{preName}} ha cambiado de forma!",
+ "disguiseChange": "¡El disfraz ha actuado como señuelo!"
+}
diff --git a/src/locales/es/pokemon-form.json b/src/locales/es/pokemon-form.json
index c46521d78da..2f70038ad2d 100644
--- a/src/locales/es/pokemon-form.json
+++ b/src/locales/es/pokemon-form.json
@@ -7,6 +7,7 @@
"pikachuToughCosplay": "Enmascarada",
"pikachuPartner": "Compañero",
"eeveePartner": "Compañero",
+ "pichuSpiky": "Picoreja",
"unownA": "A",
"unownB": "B",
"unownC": "C",
@@ -49,6 +50,8 @@
"rotomFrost": "Frío",
"rotomFan": "Ventilador",
"rotomMow": "Corte",
+ "giratinaAltered": "Modificada",
+ "shayminLand": "Tierra",
"basculinRedStriped": "Raya Roja",
"basculinBlueStriped": "Raya Azul",
"basculinWhiteStriped": "Raya Blanca",
@@ -56,6 +59,10 @@
"deerlingSummer": "Verano",
"deerlingAutumn": "Otoño",
"deerlingWinter": "Invierno",
+ "tornadusIncarnate": "Avatar",
+ "thundurusIncarnate": "Avatar",
+ "landorusIncarnate": "Avatar",
+ "keldeoOrdinary": "Habitual",
"meloettaAria": "Lírica",
"meloettaPirouette": "Danza",
"froakieBattleBond": "Fuerte Afecto",
@@ -87,12 +94,12 @@
"furfrouHeart": "Corazón",
"furfrouStar": "Estrella",
"furfrouDiamond": "Diamante",
- "furfrouDebutante": "Debutante",
- "furfrouMatron": "Matrón",
- "furfrouDandy": "Dandi",
- "furfrouLaReine": "La Reine",
+ "furfrouDebutante": "Señorita",
+ "furfrouMatron": "Dama",
+ "furfrouDandy": "Caballero",
+ "furfrouLaReine": "Aristócrata",
"furfrouKabuki": "Kabuki",
- "furfrouPharaoh": "Faraón",
+ "furfrouPharaoh": "Faraónico",
"pumpkabooSmall": "Pequeño",
"pumpkabooLarge": "Grande",
"pumpkabooSuper": "Enorme",
@@ -127,11 +134,15 @@
"magearnaOriginal": "Vetusto",
"marshadowZenith": "Cénit",
"sinisteaPhony": "Falsificada",
- "sinisteaAntique": "Auténtica",
+ "sinisteaAntique": "Genuina",
"eiscueNoIce": "Cara Deshielo",
"indeedeeMale": "Macho",
"indeedeeFemale": "Hembra",
+ "morpekoFullBelly": "Saciada",
+ "zacianHeroOfManyBattles": "Guerrero avezado",
+ "zamazentaHeroOfManyBattles": "Guerrero avezado",
"zarudeDada": "Papá",
+ "enamorusIncarnate": "Avatar",
"squawkabillyGreenPlumage": "Plumaje Verde",
"squawkabillyBluePlumage": "Plumaje Azul",
"squawkabillyYellowPlumage": "Plumaje Amarillo",
@@ -141,9 +152,19 @@
"tatsugiriStretchy": "Estirada",
"gimmighoulChest": "Cofre",
"gimmighoulRoaming": "Andante",
- "poltchageistCounterfeit": "Imitación",
- "poltchageistArtisan": "Original",
+ "koraidonApexBuild": "Forma Plena",
+ "koraidonLimitedBuild": "Forma Limitada",
+ "koraidonSprintingBuild": "Forma Carrera",
+ "koraidonSwimmingBuild": "Forma Nado",
+ "koraidonGlidingBuild": "Forma Planeo",
+ "miraidonUltimateMode": "Modo Pleno",
+ "miraidonLowPowerMode": "Modo Limitado",
+ "miraidonDriveMode": "Modo Conducción",
+ "miraidonAquaticMode": "Modo Flote",
+ "miraidonGlideMode": "Modo Planeo",
+ "poltchageistCounterfeit": "Fraudulenta",
+ "poltchageistArtisan": "Opulenta",
"paldeaTaurosCombat": "Combatiente",
"paldeaTaurosBlaze": "Ardiente",
"paldeaTaurosAqua": "Acuático"
-}
\ No newline at end of file
+}
diff --git a/src/locales/es/settings.json b/src/locales/es/settings.json
index 9c16fbb0fd6..dc441d48eb8 100644
--- a/src/locales/es/settings.json
+++ b/src/locales/es/settings.json
@@ -100,7 +100,7 @@
"moveTouchControls": "Controles táctiles",
"shopOverlayOpacity": "Opacidad de la fase de compra",
"shopCursorTarget": "Cursor de la tienda",
- "items": "Objetos",
+ "rewards": "Objetos",
"reroll": "Actualizar",
"shop": "Tienda",
"checkTeam": "Ver equipo"
diff --git a/src/locales/es/splash-messages.json b/src/locales/es/splash-messages.json
index 90ce3593b89..b1d4820b06e 100644
--- a/src/locales/es/splash-messages.json
+++ b/src/locales/es/splash-messages.json
@@ -3,12 +3,12 @@
"joinTheDiscord": "¡Únete al Discord!",
"infiniteLevels": "¡Niveles infinitos!",
"everythingStacks": "¡Todo se acumula!",
- "optionalSaveScumming": "¡Trampas guardando (¡opcionales!)!",
+ "optionalSaveScumming": "¡Trampas de guardado opcionales!",
"biomes": "¡35 biomas!",
"openSource": "¡Código abierto!",
"playWithSpeed": "¡Juega a velocidad 5x!",
- "liveBugTesting": "¡Arreglo de bugs sobre la marcha!",
- "heavyInfluence": "¡Influencia Alta en RoR2!",
+ "liveBugTesting": "¡Testeo de bugs en directo!",
+ "heavyInfluence": "¡Mucha Influencia de RoR2!",
"pokemonRiskAndPokemonRain": "¡Pokémon Risk y Pokémon Rain!",
"nowWithMoreSalt": "¡Con un 33% más de polémica!",
"infiniteFusionAtHome": "¡Infinite Fusion en casa!",
@@ -17,16 +17,16 @@
"mubstitute": "¡Mubstituto!",
"thatsCrazy": "¡De locos!",
"oranceJuice": "¡Zumo de narancia!",
- "questionableBalancing": "¡Balance cuestionable!",
+ "questionableBalancing": "¡Cambios en balance cuestionables!",
"coolShaders": "¡Shaders impresionantes!",
"aiFree": "¡Libre de IA!",
"suddenDifficultySpikes": "¡Saltos de dificultad repentinos!",
"basedOnAnUnfinishedFlashGame": "¡Basado en un juego Flash inacabado!",
- "moreAddictiveThanIntended": "¡Más adictivo de la cuenta!",
+ "moreAddictiveThanIntended": "¡Más adictivo de lo previsto!",
"mostlyConsistentSeeds": "¡Semillas CASI consistentes!",
"achievementPointsDontDoAnything": "¡Los Puntos de Logro no hacen nada!",
"youDoNotStartAtLevel": "¡No empiezas al nivel 2000!",
- "dontTalkAboutTheManaphyEggIncident": "¡No hablen del incidente del Huevo Manaphy!",
+ "dontTalkAboutTheManaphyEggIncident": "¡No se habla del Incidente Manaphy!",
"alsoTryPokengine": "¡Prueba también Pokéngine!",
"alsoTryEmeraldRogue": "¡Prueba también Emerald Rogue!",
"alsoTryRadicalRed": "¡Prueba también Radical Red!",
diff --git a/src/locales/es/terrain.json b/src/locales/es/terrain.json
index 9e26dfeeb6e..912f5186180 100644
--- a/src/locales/es/terrain.json
+++ b/src/locales/es/terrain.json
@@ -1 +1,16 @@
-{}
\ No newline at end of file
+{
+ "misty": "Niebla",
+ "mistyStartMessage": "¡La niebla ha envuelto el terreno de combate!",
+ "mistyClearMessage": "La niebla se ha disipado.",
+ "mistyBlockMessage": "¡El campo de niebla ha protegido a {{pokemonNameWithAffix}} ",
+ "electric": "Eléctrico",
+ "electricStartMessage": "¡Se ha formado un campo de corriente eléctrica en el terreno\nde combate!",
+ "electricClearMessage": "El campo de corriente eléctrica ha desaparecido.\t",
+ "grassy": "Hierba",
+ "grassyStartMessage": "¡El terreno de combate se ha cubierto de hierba!",
+ "grassyClearMessage": "La hierba ha desaparecido.",
+ "psychic": "Psíquico",
+ "psychicStartMessage": "¡El terreno de combate se ha vuelto muy extraño!",
+ "psychicClearMessage": "Ha desaparecido la extraña sensación que se percibía en el terreno\nde combate.",
+ "defaultBlockMessage": "¡El campo {{terrainName}} ha protegido a {{pokemonNameWithAffix}} "
+}
diff --git a/src/locales/es/trainer-names.json b/src/locales/es/trainer-names.json
index c6758366db7..ce09a0c9037 100644
--- a/src/locales/es/trainer-names.json
+++ b/src/locales/es/trainer-names.json
@@ -1,7 +1,7 @@
{
"brock": "Brock",
"misty": "Misty",
- "lt_surge": "Tt. Surge",
+ "lt_surge": "Teniente Surge",
"erika": "Erika",
"janine": "Sachiko",
"sabrina": "Sabrina",
@@ -23,7 +23,7 @@
"winona": "Alana",
"tate": "Vito",
"liza": "Leti",
- "juan": "Galán",
+ "juan": "Galano",
"roark": "Roco",
"gardenia": "Gardenia",
"maylene": "Brega",
@@ -34,7 +34,7 @@
"volkner": "Lectro",
"cilan": "Millo",
"chili": "Zeo",
- "cress": "Maiz",
+ "cress": "Maíz",
"cheren": "Cheren",
"lenora": "Aloe",
"roxie": "Hiedra",
@@ -57,7 +57,7 @@
"nessa": "Cathy",
"kabu": "Naboru",
"bea": "Judith",
- "allister": "Allistair",
+ "allister": "Alistair",
"opal": "Sally",
"bede": "Berto",
"gordie": "Morris",
@@ -123,30 +123,28 @@
"leon": "Lionel",
"rival": "Finn",
"rival_female": "Ivy",
- "archer": "Archer",
- "ariana": "Ariana",
- "proton": "Proton",
+ "archer": "Atlas",
+ "ariana": "Atenea",
+ "proton": "Protón",
"petrel": "Petrel",
- "tabitha": "Tabitha",
- "courtney": "Courtney",
- "shelly": "Shelly",
- "matt": "Matt",
- "mars": "Mars",
- "jupiter": "Jupiter",
- "saturn": "Saturn",
- "zinzolin": "Zinzolin",
- "rood": "Rood",
- "xerosic": "Xerosic",
- "bryony": "Bryony",
+ "tabitha": "Tatiano",
+ "courtney": "Carola",
+ "shelly": "Silvina",
+ "matt": "Matías",
+ "mars": "Venus",
+ "jupiter": "Ceres",
+ "saturn": "Saturno",
+ "zinzolin": "Menek",
+ "rood": "Ruga",
+ "xerosic": "Xero",
+ "bryony": "Begonia",
+ "maxie": "Magno",
+ "archie": "Aquiles",
+ "cyrus": "Helio",
+ "ghetsis": "Ghechis",
+ "lysandre": "Lysson",
"faba": "Fabio",
-
- "maxie": "Maxie",
- "archie": "Archie",
- "cyrus": "Cyrus",
- "ghetsis": "Ghetsis",
- "lysandre": "Lysandre",
"lusamine": "Samina",
-
"blue_red_double": "Azul y Rojo",
"red_blue_double": "Rojo y Azul",
"tate_liza_double": "Vito y Leti",
diff --git a/src/locales/fr/achv.json b/src/locales/fr/achv.json
index 60655ae22cf..3e95f9326ca 100644
--- a/src/locales/fr/achv.json
+++ b/src/locales/fr/achv.json
@@ -92,7 +92,7 @@
"name": "Master Maitre de la Ligue",
"name_female": "Master Maitresse de la Ligue"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "Travail d’équipe",
"description": "Utiliser Relais avec au moins une statistique montée à fond."
},
diff --git a/src/locales/fr/arena-tag.json b/src/locales/fr/arena-tag.json
index 16355816ae4..c3c705290fa 100644
--- a/src/locales/fr/arena-tag.json
+++ b/src/locales/fr/arena-tag.json
@@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "Le vent arrière soufflant\nsur votre équipe s’arrête !",
"tailwindOnRemoveEnemy": "Le vent arrière soufflant\nsur l’équipe ennemie s’arrête !",
"happyHourOnAdd": "L’ambiance est euphorique !",
- "happyHourOnRemove": "L’ambiance se calme !"
+ "happyHourOnRemove": "L’ambiance se calme !",
+ "safeguardOnAdd": "Un voile mystérieux recouvre\ntout le terrain !",
+ "safeguardOnAddPlayer": "Un voile mystérieux recouvre\nvotre équipe !",
+ "safeguardOnAddEnemy": "Un voile mystérieux recouvre\nl’équipe ennemie !",
+ "safeguardOnRemove": "Le terrain n’est plus protégé\npar le voile mystérieux !",
+ "safeguardOnRemovePlayer": "Votre équipe n’est plus protégée\npar le voile mystérieux !",
+ "safeguardOnRemoveEnemy": "L’équipe ennemie n’est plus protégée\npar le voile mystérieux !"
}
\ No newline at end of file
diff --git a/src/locales/fr/battle.json b/src/locales/fr/battle.json
index 1c108c89ded..b8da3a953ae 100644
--- a/src/locales/fr/battle.json
+++ b/src/locales/fr/battle.json
@@ -94,5 +94,6 @@
"retryBattle": "Voulez-vous réessayer depuis le début du combat ?",
"unlockedSomething": "{{unlockedThing}}\na été débloqué.",
"congratulations": "Félicitations !",
- "beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !"
-}
\ No newline at end of file
+ "beatModeFirstTime": "{{speciesName}} a battu le mode {{gameMode}} pour la première fois !\nVous avez reçu {{newModifier}} !",
+ "eggSkipPrompt": "Aller directement au résumé des Œufs éclos ?"
+}
diff --git a/src/locales/fr/dialogue-misc.json b/src/locales/fr/dialogue-misc.json
index d932d283d37..c8c781002b9 100644
--- a/src/locales/fr/dialogue-misc.json
+++ b/src/locales/fr/dialogue-misc.json
@@ -1,4 +1,6 @@
{
- "ending": "@c{smile}Oh ? T’as gagné ?@d{96} @c{smile_eclosed}J’aurais dû le savoir.\nMais de voilà de retour.\n$@c{smile}C’est terminé.@d{64} T’as brisé ce cycle infernal.\n$@c{serious_smile_fists}T’as aussi accompli ton rêve non ?\nTu n’as pas connu la moindre défaite.\n$@c{neutral}Je suis le seul à me souvenir de ce que t’as fait.@d{96}\nJe pense que ça ira, non ?\n$@c{serious_smile_fists}Ta légende vivra à jamais dans nos cœurs.\n$@c{smile_eclosed}Bref, j’en ai un peu marre de ce endroit, pas toi ? Rentrons à la maison.\n$@c{serious_smile_fists}On se fera un p’tit combat une fois rentrés ?\nSi t’es d’accord.",
- "ending_female": "@c{shock}T’es revenu ?@d{32} Ça veut dire…@d{96} que t’as gagné ?!\n@c{smile_ehalf}J’aurais dû le savoir.\n$@c{smile_eclosed}Bien sûr… J’ai toujours eu ce sentiment.\n@c{smile}C’est fini maitenant hein ? T’as brisé ce cycle.\n$@c{smile_ehalf}T’as aussi accompli ton rêve non ?\nTu n’as pas connu la moindre défaite.\n$Je serai la seule à me souvenir de ce que t’as fait.\n@c{angry_mopen}Je tâcherai de ne pas oublier !\n$@c{smile_wave_wink}J’déconne !@d{64} @c{smile}Jamais j’oublierai.@d{32}\nTa légende vivra à jamais dans nos cœurs.\n$@c{smile_wave}Bon,@d{64} il se fait tard…@d{96} je crois ?\nDifficile à dire ici.\n$Rentrons, @c{smile_wave_wink}et demain on se fera un p’tit combat, comme au bon vieux temps ?"
-}
\ No newline at end of file
+ "ending": "@c{shock}T’es revenu ?@d{32} Ça veut dire…@d{96} que t’as gagné ?!\n@c{smile_ehalf}J’aurais dû m’en douter.\n$@c{smile_eclosed}Bien sûr… J’ai toujours eu ce sentiment.\n@c{smile}C’est fini maintenant hein ? T’as brisé ce cycle.\n$@c{smile_ehalf}T’as aussi accompli ton rêve non ?\nTu n’as pas connu la moindre défaite.\n$Je serai la seule à me souvenir de ce que t’as fait.\n@c{angry_mopen}Je tâcherai de ne pas oublier !\n$@c{smile_wave_wink}J’déconne !@d{64} @c{smile}Jamais j’oublierai.@d{32}\nTa légende vivra à jamais dans nos cœurs.\n$@c{smile_wave}Bon,@d{64} il se fait tard…@d{96} je crois ?\nDifficile à dire ici.\n$Rentrons, @c{smile_wave_wink}et demain on se fera un p’tit combat, comme au bon vieux temps ?",
+ "ending_female": "@c{smile}Oh ? T’as gagné ?@d{96} @c{smile_eclosed}J’aurais dû m’en douter.\nMais te voilà enfin de retour.\n$@c{smile}C’est terminé.@d{64} T’as brisé ce cycle infernal.\n$@c{serious_smile_fists}T’as aussi accompli ton rêve non ?\nTu n’as pas connu la moindre défaite.\n$@c{neutral}Je suis le seul à me souvenir de ce que t’as fait.@d{96}\nJe pense que ça ira, non ?\n$@c{serious_smile_fists}Ta légende vivra à jamais dans nos cœurs.\n$@c{smile_eclosed}Bref, j’en ai un peu marre de ce endroit, pas toi ? Rentrons à la maison.\n$@c{serious_smile_fists}On se fera un p’tit combat une fois rentrés ?\nSi t’es d’accord.",
+ "ending_endless": "Félicitations ! Vous avez atteint la fin actuelle.\nPlus de contenu à venir bientôt !",
+ "ending_name": "Les devs"
+}
diff --git a/src/locales/fr/dialogue.json b/src/locales/fr/dialogue.json
index c9bb3c417c7..d9d13a8f1e8 100644
--- a/src/locales/fr/dialogue.json
+++ b/src/locales/fr/dialogue.json
@@ -9,7 +9,7 @@
"6": "Allez, c’est parti !",
"7": "Attention, me voilà !\nTu vas voir comment j’suis fort !",
"8": "Coucou… Tu veux voir mes bô Pokémon ?",
- "9": "Trève de mondanités. Ramène-toi quand tu le sens !",
+ "9": "Trêve de mondanités. Ramène-toi quand tu le sens !",
"10": "Baisse pas ta garde si tu veux pas pleurer d’avoir perdu face à un gamin.",
"11": "J’ai tout donné pour élever mes Pokémon. Attention à toi si tu leur fait du mal !",
"12": "Incroyable que t’y sois parvenu ! Mais la suite va pas être une partie de plaisir.",
@@ -68,7 +68,7 @@
"3": "Hum, t’es pas trop laxiste avec tes Pokémon ?\nTrop les chouchouter n’est pas bon."
},
"victory": {
- "1": "Il est primordial de nourir et développer toutes les caractéristiques de chaque Pokémon.",
+ "1": "Il est primordial de nourrir et développer toutes les caractéristiques de chaque Pokémon.",
"2": "Contrairement à moi, ces Pokémon ont un bon fond.",
"3": "Trop d’éloges peut ruiner les Pokémon et les gens."
},
@@ -229,7 +229,7 @@
"encounter": {
"1": "Ne te mets pas en travers de la Team Galaxie !",
"2": "Sois témoin de la puissance de notre technologie et du futur qui se profile !",
- "3": "Au nom de la Team Galaxie, j’éliminerai quiconque se mettera sur notre route !",
+ "3": "Au nom de la Team Galaxie, j’éliminerai quiconque se mettra sur notre route !",
"4": "Prépare ta défaite !",
"5": "J’espère que t’es prêt à te prendre une raclée de l’espace !",
"5_female": "J’espère que t’es prête à te prendre une raclée de l’espace !"
@@ -244,7 +244,7 @@
},
"plasma_grunt": {
"encounter": {
- "1": "Pas de quatiers à ceux qui ne suivent pas notre idéal !",
+ "1": "Pas de quartiers à quiconque ne suit pas notre idéal !",
"2": "Si je gagne, tu relâches tous tes Pokémon !",
"3": "Si tu te mets en travers de la Team Plasma, je m’occuperai de toi personnellement !",
"4": "La Team Plasma va libérer les Pokémon de tous les humains égoïstes dans ton genre !",
@@ -275,6 +275,96 @@
"5": "J’appelle pas ça perdre, j’appelle ça échouer avec panache !"
}
},
+ "aether_grunt": {
+ "encounter": {
+ "1": "Je vais te mettre ta raclée !",
+ "2": "J’en ai rien à faire que tu sois une gosse. Tu vas tutoyer les étoiles si tu nous menaces !",
+ "2_female": "J’en ai rien à faire que tu sois une gosse. Tu vas tutoyer les étoiles si tu nous menaces !",
+ "3": "J’ai pour ordre de ne laisser passer aucun Dresseur, peu importe qui c’est !",
+ "4": "Je vais te montrer le pouvoir du Paradis Æther !",
+ "5": "Maintenant que t’es au courant de ce qu’il se passe au cœur du Paradis Æther, fais-moi une faveur et disparait !"
+ },
+ "victory": {
+ "1": "C’est plutôt toi qui devrait m’apprendre à en mettre…",
+ "2": "Pardon ? J’ai pas compris…",
+ "3": "Peu importe les ordres, jamais j’aurais pu te retenir en fait…",
+ "4": "Mhh… Il semblerait que j’ai perdu.",
+ "5": "C’est plutôt moi qui va disparaitre je crois."
+ }
+ },
+ "faba": {
+ "encounter": {
+ "1": "Moi, Directeur Saubohne, je vais te montrer de quel bois je me chauffe !",
+ "2": "Donc là, l’homme supposé être la dernière ligne défense du Paradis Æther doit affronter un mioche ?",
+ "2_female": "Donc là, l’homme supposé être la dernière ligne défense du Paradis Æther doit affronter un mioche ?",
+ "3": "S’il n’y a qu’un seul nom à retenir au sein de la Fondation Æther, c’est le mien : Saubohne !"
+ },
+ "victory": {
+ "1": "Gloups !",
+ "2": "Malheur ! J’ai perdu face à un simple enfant ?!",
+ "2_female": "Malheur ! J’ai perdu face à une simple enfant ?!",
+ "3": "J’ai HORREUR des enfants !"
+ }
+ },
+ "skull_grunt": {
+ "encounter": {
+ "1": "Oush oush ! On est pas méchants, sauf si tu viens nous allumer la mèche-han !",
+ "2": "Ce manque de respect, j’hallucine ! T’es allé trop loin, le mioche !",
+ "2_female": "Ce manque de respect, j’hallucine ! T’es allée trop loin, la mioche !",
+ "3": "On est juste des gars et des meufs normaux, on voit un Pokémon on le prend !",
+ "4": "Pourquoi tu te la joue comme ça ? C'est avec tes dents que t’vas jouer frérot.",
+ "4_female": "Pourquoi tu te la joue comme ça ? C'est avec tes dents que t’vas jouer ma reus.",
+ "5": "Cousin, écoute-nous bien ! ♪\nSe taper dessus, ça sert à rien ! ♪\n$Tu t’incrustes chez nous, ça s’fait pas ! ♪\n$Mais on est sympa, on a un plan pour toi ! ♪",
+ "5_female": "Cousine, écoute-nous bien ! ♪\nSe taper dessus, ça sert à rien ! ♪\n$Tu t’incrustes chez nous, ça s’fait pas ! ♪\n$Mais on est sympa, on a un plan pour toi ! ♪"
+ },
+ "victory": {
+ "1": "Hein ? C’est déjà terminé ?",
+ "2": "… Ça craint grave ! On s’tire !",
+ "3": "Ouais de toute on en avait pas b’soin de ton Pokémon… Ah ah…",
+ "4": "Ouh là, c’est bon, j’en demandais pas tant…",
+ "5": "On pèse plus que des Pokémon, t’entends ?\nAlors tu vas nous respecter, oush !"
+ }
+ },
+ "plumeria": {
+ "encounter": {
+ "1": "Tsk. T’es un gamin tout ce qu’il y a de plus banal, en fait.",
+ "1_female": "Tsk. T’es une gamine tout ce qu’il y a de plus banal, en fait.",
+ "2": "Abrutis de sbires. Trop incompétents pour arriver à se débarasser de gamins…",
+ "3": "Si tu touches encore à un cheveu de mes lascars, tu vas pas comprendre c’qui t’arrive !"
+ },
+ "victory": {
+ "1": "Tsk. T’es pas mauvais. J’te l’accorde.",
+ "1_female": "Tsk. T’es pas mauvaise. J’te l’accorde.",
+ "2": "Tsk. J’dois reconnaitre que t’en as dans le ventre.\n$Maintenant, j’comprends pourquoi mes gars n’arrêtent pas de se faire battre par toi.",
+ "3": "Tsk. J’crois que j'ai plus qu’à assumer ma défaite."
+ }
+ },
+ "macro_grunt": {
+ "encounter": {
+ "1": "Hop hop hop ! Terminus !",
+ "2": "T’es un Dresseur n’est-ce pas ?\n$J’ai bien peur ce que ne soit pas une excuse suffisante pour nous interrompre dans notre travail.",
+ "2_female": "T’es une Dresseuse n’est-ce pas ?\n$J’ai bien peur ce que ne soit pas une excuse suffisante pour nous interrompre dans notre travail.",
+ "3": "Je travaille à Macro Cosmos Assurances !\nBesoin d’une assurance-vie ?"
+ },
+ "victory": {
+ "1": "Je n’ai d’autre choix que respectueusement me retirer.",
+ "2": "Mon argent de poche…\nPlus qu’à manger des pâtes pour la fin du mois…",
+ "3": "Chez Macro Cosmos, rien n’est comparable à notre dévotion au travail !"
+ }
+ },
+ "oleana": {
+ "encounter": {
+ "1": "Je ne laisserai personne interférer avec les projets du président Shehroz.",
+ "2": "Je vois que vous avez su vous défaire de mes subalternes.\n$Mais assez joué. Il est temps de rentrer chez vous, maintenant.",
+ "3": "Je gagnerai en votre nom, monsieur le président."
+ },
+ "victory": {
+ "1": "*soupir* Comment ai-je fait pour perdre ainsi… ?\nJe ne suis vraiment pas à la hauteur…",
+ "2": "Ah ! Quelle erreur… Je n’aurais pas dû sous-estimer un Dresseur de ton calibre…",
+ "2_female": "Ah ! Quelle erreur… Je n’aurais pas dû sous-estimer une Dresseuse de ton calibre…",
+ "3": "*soupir* Je suis fatiguée parton…"
+ }
+ },
"rocket_boss_giovanni_1": {
"encounter": {
"1": "Bien. Je dois admettre que je suis impressionné de te voir ici !"
@@ -468,7 +558,7 @@
"4": "Voir un tel jardin rempli de fleurs est si apaisant…"
},
"victory": {
- "1": "Bien joué, c’est mértié.",
+ "1": "Bien joué, c’est mérité.",
"2": "Dommage, on s’amusait si bien…",
"3": "Oh non, le combat est terminé…",
"4": "Aaah, ça fait du bien !\nMerci, j’en avais besoin."
@@ -505,15 +595,15 @@
},
"rival": {
"encounter": {
- "1": "@c{smile}Ah, je te cherchais ! Je savais que t’étais pressé de partir, mais je m’attendais quand même à un au revoir…\n$@c{smile_eclosed}T’as finalement décidé de réaliser ton rêve ?\nJ’ai peine à y croire.\n$@c{serious_smile_fists}Vu que t’es là, ça te dis un petit combat ?\nJe voudrais quand même m’assurer que t’es prêt.\n$@c{serious_mopen_fists}Surtout ne te retiens pas et donne-moi tout ce que t’as !"
+ "1": "@c{smile}Ah, je te cherchais ! Je savais que t’étais pressée de partir, mais je m’attendais quand même à un au revoir…\n$@c{smile_eclosed}T’as finalement décidé de réaliser ton rêve ?\nJ’ai peine à y croire.\n$@c{serious_smile_fists}Vu que t’es là, ça te dis un petit combat ?\nJe voudrais quand même m’assurer que t’es prête.\n$@c{serious_mopen_fists}Surtout ne te retiens pas et donne-moi tout ce que t’as !"
},
"victory": {
- "1": "@c{shock}Wah… Tu m’as vraiment lavé.\nT’es vraiment un débutant ?\n$@c{smile}T’as peut-être eu de la chance, mais…\nPeut-être que t’arriveras jusqu’au bout du chemin.\n$D’ailleurs, le prof m’a demandé de te filer ces objets.\nIls ont l’air sympas.\n$@c{serious_smile_fists}Bonne chance à toi !"
+ "1": "@c{shock}Wah… Tu m’as vraiment lavé.\nT’es vraiment une débutante ?\n$@c{smile}T’as peut-être eu de la chance, mais…\nPeut-être que t’arriveras jusqu’au bout du chemin.\n$D’ailleurs, le prof m’a demandé de te filer ces objets.\nIls ont l’air sympas.\n$@c{serious_smile_fists}Bonne chance à toi !"
}
},
"rival_female": {
"encounter": {
- "1": "@c{smile_wave}Ah, te voilà ! Je t’ai cherché partout !\n@c{angry_mopen}On oublie de dire au revoir à sa meilleure amie ?\n$@c{smile_ehalf}T’as décidé de réaliser ton rêve, hein ?\nCe jour est donc vraiment arrivé…\n$@c{smile}Je veux bien te pardonner de m’avoir oubliée,\nà une conditon. @c{smile_wave_wink}Que tu m’affronte !\n$@c{angry_mopen}Donne tout ! Ce serait dommage que ton aventure finisse avant d’avoir commencé, hein ?"
+ "1": "@c{smile_wave}Ah, te voilà ! Je t’ai cherché partout !\n@c{angry_mopen}On oublie de dire au revoir à sa meilleure amie ?\n$@c{smile_ehalf}T’as décidé de réaliser ton rêve, hein ?\nCe jour est donc vraiment arrivé…\n$@c{smile}Je veux bien te pardonner de m’avoir oubliée,\nà une condition. @c{smile_wave_wink}Que tu m’affronte !\n$@c{angry_mopen}Donne tout ! Ce serait dommage que ton aventure finisse avant d’avoir commencé, hein ?"
},
"victory": {
"1": "@c{shock}Tu viens de commencer et t’es déjà si fort ?!@d{96}\n@c{angry}T’as triché non ? Avoue !\n$@c{smile_wave_wink}J’déconne !@d{64} @c{smile_eclosed}J’ai perdu dans les règles…\nJ’ai le sentiment que tu vas très bien t’en sortir.\n$@c{smile}D’ailleurs, le prof veut que je te donne ces quelques objets. Ils te seront utiles, pour sûr !\n$@c{smile_wave}Fais de ton mieux, comme toujours !\nJe crois fort en toi !"
@@ -521,10 +611,10 @@
},
"rival_2": {
"encounter": {
- "1": "@c{smile}Hé, toi aussi t’es là ?\n@c{smile_eclosed}Toujours invaincu, hein… ?\n$@c{serious_mopen_fists}Je sais que j’ai l’air de t’avoir suivi ici, mais c’est pas complètement vrai.\n$@c{serious_smile_fists}Pour être honnête, ça me démangeait d’avoir une revanche depuis que tu m’as battu.\n$Je me suis beaucoup entrainé, alors sois sure que je vais pas retenir mes coups cette fois.\n$@c{serious_mopen_fists}Et comme la dernière fois, ne te retiens pas !\nC’est parti !"
+ "1": "@c{smile}Hé, toi aussi t’es là ?\n@c{smile_eclosed}Toujours invaincue, hein… ?\n$@c{serious_mopen_fists}Je sais que j’ai l’air de t’avoir suivie ici, mais c’est pas complètement vrai.\n$@c{serious_smile_fists}Pour être honnête, ça me démangeait d’avoir une revanche depuis que tu m’as battu.\n$Je me suis beaucoup entrainé, alors sois sure que je vais pas retenir mes coups cette fois.\n$@c{serious_mopen_fists}Et comme la dernière fois, ne te retiens pas !\nC’est parti !"
},
"victory": {
- "1": "@c{neutral_eclosed}Oh. Je crois que j’ai trop pris la confiance.\n$@c{smile}Pas grave, c’est OK. Je me doutais que ça arriverait.\n@c{serious_mopen_fists}Je vais juste devoir encore plus m’entrainer !\n\n$@c{smile}Ah, et pas que t’aies réellement besoin d’aide, mais j’ai ça en trop sur moi qui pourrait t’intéresser.\n\n$@c{serious_smile_fists}Mais n’espère plus en avoir d’autres !\nJe peux pas passer mon temps à aider mon adversaire.\n$@c{smile}Bref, prends soin de toi et profite bien de l’évènement !"
+ "1": "@c{neutral_eclosed}Oh. Je crois que j’ai trop pris la confiance.\n$@c{smile}Pas grave, c’est OK. Je me doutais que ça arriverait.\n@c{serious_mopen_fists}Je vais juste devoir encore plus m’entrainer !\n\n$@c{smile}Ah, et pas que t’aies réellement besoin d’aide, mais j’ai ça en trop sur moi qui pourrait t’intéresser.\n\n$@c{serious_smile_fists}Mais n’espère plus en avoir d’autres !\nJe peux pas passer mon temps à aider mon adversaire.\n$@c{smile}Bref, prends soin de toi !"
}
},
"rival_2_female": {
@@ -532,7 +622,7 @@
"1": "@c{smile_wave}Hé, sympa de te croiser ici. T’as toujours l’air invaincu. @c{angry_mopen}Eh… Pas mal !\n$@c{angry_mopen}Je sais à quoi tu penses et non, je t’espionne pas.\n@c{smile_eclosed}C’est juste que j’étais aussi dans le coin.\n$@c{smile_ehalf}Heureuse pour toi, mais je veux juste te rappeler que c’est pas grave de perdre parfois.\n$@c{smile}On apprend de nos erreurs, souvent plus que si on ne connaissait que le succès.\n$@c{angry_mopen}Dans tous les cas je me suis bien entrainée pour cette revanche, t’as intérêt à tout donner !"
},
"victory": {
- "1": "@c{neutral}Je… J’étais pas encore supposée perdre…\n$@c{smile}Bon. Ça veut juste dire que je vais devoir encore plus m’entrainer !\n$@c{smile_wave}J’ai aussi ça en rab pour toi !\n@c{smile_wave_wink}Inutile de me remercier ~.\n$@c{angry_mopen}C’étaient les derniers, terminé les cadeaux après ceux-là !\n$@c{smile_wave}Allez, tiens le coup et profite bien de l’évènement !"
+ "1": "@c{neutral}Je… J’étais pas encore supposée perdre…\n$@c{smile}Bon. Ça veut juste dire que je vais devoir encore plus m’entrainer !\n$@c{smile_wave}J’ai aussi ça en rab pour toi !\n@c{smile_wave_wink}Inutile de me remercier ~.\n$@c{angry_mopen}C’étaient les derniers, terminé les cadeaux après ceux-là !\n$@c{smile_wave}Allez, tiens le coup !"
},
"defeat": {
"1": "Je suppose que c’est parfois normal de perdre…"
@@ -540,7 +630,7 @@
},
"rival_3": {
"encounter": {
- "1": "@c{smile}Hé, mais qui voilà ! Ça fait un bail.\n@c{neutral}T’es… toujours invaincu ? Incroyable.\n$@c{neutral_eclosed}Tout est devenu un peu… étrange.\nC’est plus pareil sans toi au village.\n$@c{serious}Je sais que c’est égoïste, mais j’ai besoin d’expier ça.\n@c{neutral_eclosed}Je crois que tout ça te dépasse.\n$@c{serious}Ne jamais perdre, c’est juste irréaliste.\nGrandir, c’est parfois aussi savoir perdre.\n$@c{neutral_eclosed}T’as un beau parcours, mais il y a encore tellement à venir et ça va pas s’arranger. @c{neutral}T’es prêt pour ça ?\n$@c{serious_mopen_fists}Si tu l’es, alors prouve-le."
+ "1": "@c{smile}Hé, mais qui voilà ! Ça fait un bail.\n@c{neutral}T’es… toujours invaincue ? Incroyable.\n$@c{neutral_eclosed}Tout est devenu un peu… étrange.\nC’est plus pareil sans toi au village.\n$@c{serious}Je sais que c’est égoïste, mais j’ai besoin d’expier ça.\n@c{neutral_eclosed}Je crois que tout ça te dépasse.\n$@c{serious}Ne jamais perdre, c’est juste irréaliste.\nGrandir, c’est parfois aussi savoir perdre.\n$@c{neutral_eclosed}T’as un beau parcours, mais il y a encore tellement à venir et ça va pas s’arranger. @c{neutral}T’es prête pour ça ?\n$@c{serious_mopen_fists}Si tu l’es, alors prouve-le."
},
"victory": {
"1": "@c{angry_mhalf}C’est lunaire… J’ai presque fait que m’entrainer…\nAlors pourquoi il y a encore un tel écart entre nous ?"
@@ -559,7 +649,7 @@
},
"rival_4": {
"encounter": {
- "1": "@c{neutral}Hé.\n$Je vais pas y aller par quatre chemins avec toi.\n@c{neutral_eclosed}Je suis là pour gagner. Simple, basique.\n$@c{serious_mhalf_fists}J’ai appris à maximiser tout mon potentiel en m’entrainant d’arrachepied.\n$@c{smile}C’est fou tout le temps que tu peux te dégager si tu dors pas en sacrifiant ta vie sociale.\n$@c{serious_mopen_fists}Plus rien n’a d’importance désormais, pas tant que j’aurai pas gagné.\n$@c{neutral_eclosed}J’ai atteint un stade où je ne peux plus perdre.\n@c{smile_eclosed}Je présume que ta philosophie était pas si fausse finalement.\n$@c{angry_mhalf}La défaite, c’est pour les faibles, et je ne suis plus un faible.\n$@c{serious_mopen_fists}Tiens-toi prêt."
+ "1": "@c{neutral}Hé.\n$Je vais pas y aller par quatre chemins avec toi.\n@c{neutral_eclosed}Je suis là pour gagner. Simple, basique.\n$@c{serious_mhalf_fists}J’ai appris à maximiser tout mon potentiel en m’entrainant d’arrachepied.\n$@c{smile}C’est fou tout le temps que tu peux te dégager si tu dors pas en sacrifiant ta vie sociale.\n$@c{serious_mopen_fists}Plus rien n’a d’importance désormais, pas tant que j’aurai pas gagné.\n$@c{neutral_eclosed}J’ai atteint un stade où je ne peux plus perdre.\n@c{smile_eclosed}Je présume que ta philosophie était pas si fausse finalement.\n$@c{angry_mhalf}La défaite, c’est pour les faibles, et je ne suis plus un faible.\n$@c{serious_mopen_fists}Tiens-toi prête."
},
"victory": {
"1": "@c{neutral}Que…@d{64} Qui es-tu ?"
@@ -597,7 +687,7 @@
},
"rival_6": {
"encounter": {
- "1": "@c{smile_eclosed}Nous y revoilà.\n$@c{neutral}J’ai eu du temps pour réfléchir à tout ça.\nIl y a une raison à pourquoi tout semble étrange.\n$@c{neutral_eclosed}Ton rêve, ma volonté de te battre…\nFont partie de quelque chose de plus grand.\n$@c{serious}C’est même pas à propos de moi, ni de toi… Mais du monde, @c{serious_mhalf_fists}et te repousser dans tes limites est ma mission.\n$@c{neutral_eclosed}J’ignore si je serai capable de l’accomplir, mais je ferai tout ce qui est en mon pouvoir.\n$@c{neutral}Cet endroit est terrifiant… Et pourtant il m’a l’air familier, comme si j’y avais déjà mis les pieds.\n$@c{serious_mhalf_fists}Tu ressens la même chose, pas vrai ?\n$@c{serious}… et c’est comme si quelque chose ici me parlait.\n$Comme si c’était tout ce que ce monde avait toujours connu.\n$Ces précieux moments ensemble semblent si proches ne sont rien de plus qu’un lointain souvenir.\n$@c{neutral_eclosed}D’ailleurs, qui peut dire aujourd’hui qu’ils ont pu être réels ?\n$@c{serious_mopen_fists}Il faut que tu persévères. Si tu t’arrêtes, ça n’aura jamais de fin et t’es le seul à en être capable.\n$@c{serious_smile_fists}Difficile de comprendre le sens de tout ça, je sais juste que c’est la réalité.\n$@c{serious_mopen_fists}Si tu ne parviens pas à me battre ici et maintenant, tu n’as aucune chance."
+ "1": "@c{smile_eclosed}Nous y revoilà.\n$@c{neutral}J’ai eu du temps pour réfléchir à tout ça.\nIl y a une raison à pourquoi tout semble étrange.\n$@c{neutral_eclosed}Ton rêve, ma volonté de te battre…\nFont partie de quelque chose de plus grand.\n$@c{serious}C’est même pas à propos de moi, ni de toi… Mais du monde, @c{serious_mhalf_fists}et te repousser dans tes limites est ma mission.\n$@c{neutral_eclosed}J’ignore si je serai capable de l’accomplir, mais je ferai tout ce qui est en mon pouvoir.\n$@c{neutral}Cet endroit est terrifiant… Et pourtant il m’a l’air familier, comme si j’y avais déjà mis les pieds.\n$@c{serious_mhalf_fists}Tu ressens la même chose, pas vrai ?\n$@c{serious}… et c’est comme si quelque chose ici me parlait.\n$Comme si c’était tout ce que ce monde avait toujours connu.\n$Ces précieux moments ensemble semblent si proches ne sont rien de plus qu’un lointain souvenir.\n$@c{neutral_eclosed}D’ailleurs, qui peut dire aujourd’hui qu’ils ont pu être réels ?\n$@c{serious_mopen_fists}Il faut que tu persévères. Si tu t’arrêtes, ça n’aura jamais de fin et t’es la seule à en être capable.\n$@c{serious_smile_fists}Difficile de comprendre le sens de tout ça, je sais juste que c’est la réalité.\n$@c{serious_mopen_fists}Si tu ne parviens pas à me battre ici et maintenant, tu n’as aucune chance."
},
"victory": {
"1": "@c{smile_eclosed}J’ai fait ce que j’avais à faire.\n$Promets-moi juste une chose.\n@c{smile}Après avoir réparé ce monde… Rentre à la maison."
diff --git a/src/locales/fr/menu-ui-handler.json b/src/locales/fr/menu-ui-handler.json
index 807b34f1315..b8627bf91b5 100644
--- a/src/locales/fr/menu-ui-handler.json
+++ b/src/locales/fr/menu-ui-handler.json
@@ -25,5 +25,6 @@
"unlinkGoogle": "Délier Google",
"cancel": "Retour",
"losingProgressionWarning": "Vous allez perdre votre progression depuis le début du combat. Continuer ?",
- "noEggs": "Vous ne faites actuellement\néclore aucun Œuf !"
-}
\ No newline at end of file
+ "noEggs": "Vous ne faites actuellement\néclore aucun Œuf !",
+ "donate": "Faire un don"
+}
diff --git a/src/locales/fr/modifier-type.json b/src/locales/fr/modifier-type.json
index 6d5cfb098ef..509a8b11112 100644
--- a/src/locales/fr/modifier-type.json
+++ b/src/locales/fr/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "Double les chances de tomber sur un combat double pendant {{battleCount}} combats."
},
- "TempBattleStatBoosterModifierType": {
- "description": "Augmente d’un cran {{tempBattleStatName}} pour toute l’équipe pendant 5 combats."
+ "TempStatStageBoosterModifierType": {
+ "description": "Augmente d’un cran {{stat}} pour toute l’équipe pendant 5 combats."
},
"AttackTypeBoosterModifierType": {
"description": "Augmente de 20% la puissance des capacités de type {{moveType}} d’un Pokémon."
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "Fait monter toute l’équipe de {{levels}} niveau·x."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "Augmente de 10% {{statName}} de base de son porteur. Plus les IV sont hauts, plus il peut en porter."
+ "BaseStatBoosterModifierType": {
+ "description": "Augmente de 10% {{stat}} de base de son porteur. Plus les IV sont hauts, plus il peut en porter."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "Restaure tous les PV de toute l’équipe."
@@ -183,6 +183,7 @@
"SOOTHE_BELL": { "name": "Grelot Zen" },
"SCOPE_LENS": { "name": "Lentilscope", "description": "Une lentille qui augmente d’un cran le taux de critiques du porteur." },
+ "DIRE_HIT": { "name": "Muscle +", "extra": { "raises": "Taux de critique" } },
"LEEK": { "name": "Poireau", "description": "À faire tenir à Canarticho ou Palarticho. Un poireau très long et solide qui augmente de 2 crans le taux de critiques." },
"EVIOLITE": { "name": "Évoluroc", "description": "Augmente de 50% la Défense et Déf. Spé. si le porteur peut évoluer, 25% aux fusions dont une moitié le peut encore." },
@@ -250,28 +251,14 @@
"METAL_POWDER": { "name": "Poudre Métal", "description": "À faire tenir à Métamorph. Cette poudre étrange, très fine mais résistante, double sa Défense." },
"QUICK_POWDER": { "name": "Poudre Vite", "description": "À faire tenir à Métamorph. Cette poudre étrange, très fine mais résistante, double sa Vitesse." }
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "Attaque +",
"x_defense": "Défense +",
"x_sp_atk": "Atq. Spé. +",
"x_sp_def": "Déf. Spé. +",
"x_speed": "Vitesse +",
- "x_accuracy": "Précision +",
- "dire_hit": "Muscle +"
+ "x_accuracy": "Précision +"
},
-
- "TempBattleStatBoosterStatName": {
- "ATK": "l’Attaque",
- "DEF": "la Défense",
- "SPATK": "l’Atq. Spé.",
- "SPDEF": "la Déf. Spé.",
- "SPD": "la Vitesse",
- "ACC": "la précision",
- "CRIT": "le taux de critique",
- "EVA": "l’esquive",
- "DEFAULT": "???"
- },
-
"AttackTypeBoosterItem": {
"silk_scarf": "Mouchoir Soie",
"black_belt": "Ceinture Noire",
@@ -450,6 +437,6 @@
"DRAGON_MEMORY": "ROM Dragon",
"DARK_MEMORY": "ROM Ténèbres",
"FAIRY_MEMORY": "ROM Fée",
- "BLANK_MEMORY": "ROM Vierge"
+ "NORMAL_MEMORY": "ROM Normal"
}
}
diff --git a/src/locales/fr/modifier.json b/src/locales/fr/modifier.json
index 8a15c9e5ddf..0ec228a22c2 100644
--- a/src/locales/fr/modifier.json
+++ b/src/locales/fr/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "Les PV de {{pokemonNameWithAffix}}\nsont un peu restaurés par les {{typeName}} !",
"hitHealApply": "Les PV de {{pokemonNameWithAffix}}\nsont un peu restaurés par le {{typeName}} !",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} a repris connaissance\navec sa {{typeName}} et est prêt à se battre de nouveau !",
- "pokemonResetNegativeStatStageApply": "Les stats baissées de {{pokemonNameWithAffix}}\nsont restaurées par l’{{typeName}} !",
+ "resetNegativeStatStageApply": "Les stats baissées de {{pokemonNameWithAffix}}\nsont restaurées par l’{{typeName}} !",
"moneyInterestApply": "La {{typeName}} vous rapporte\n{{moneyAmount}} ₽ d’intérêts !",
"turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} est absorbé·e\npar le {{typeName}} de {{pokemonName}} !",
"contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} est volé·e\npar l’{{typeName}} de {{pokemonName}} !",
diff --git a/src/locales/fr/move-trigger.json b/src/locales/fr/move-trigger.json
index 43cf09d5bf6..b9bc929c619 100644
--- a/src/locales/fr/move-trigger.json
+++ b/src/locales/fr/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}} sacrifie des PV\net augmente la puissance ses capacités !",
"absorbedElectricity": "{{pokemonName}} absorbe de l’électricité !",
"switchedStatChanges": "{{pokemonName}} permute\nles changements de stats avec ceux de sa cible !",
+ "switchedTwoStatChanges": "{{pokemonName}} permute les changements de {{firstStat} et de {{secondStat}} avec ceux de sa cible !",
+ "switchedStat": "{{pokemonName}} et sa cible échangent leur {{stat}} !",
+ "sharedGuard": "{{pokemonName}} additionne sa garde à celle de sa cible et redistribue le tout équitablement !",
+ "sharedPower": "{{pokemonName}} additionne sa force à celle de sa cible et redistribue le tout équitablement !",
"goingAllOutForAttack": "{{pokemonName}} a pris\ncette capacité au sérieux !",
"regainedHealth": "{{pokemonName}}\nrécupère des PV !",
"keptGoingAndCrashed": "{{pokemonName}}\ns’écrase au sol !",
@@ -61,5 +65,6 @@
"suppressAbilities": "Le talent de {{pokemonName}}\na été rendu inactif !",
"revivalBlessing": "{{pokemonName}} a repris connaissance\net est prêt à se battre de nouveau !",
"swapArenaTags": "Les effets affectant chaque côté du terrain\nont été échangés par {{pokemonName}} !",
- "exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !"
+ "exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !",
+ "safeguard": "{{targetName}} est protégé\npar la capacité Rune Protect !"
}
\ No newline at end of file
diff --git a/src/locales/fr/pokemon-info.json b/src/locales/fr/pokemon-info.json
index 1e55f332432..1160ec95b75 100644
--- a/src/locales/fr/pokemon-info.json
+++ b/src/locales/fr/pokemon-info.json
@@ -13,7 +13,8 @@
"SPD": "Vitesse",
"SPDshortened": "Vit",
"ACC": "Précison",
- "EVA": "Esquive"
+ "EVA": "Esquive",
+ "HPStat": "PV"
},
"Type": {
"UNKNOWN": "Inconnu",
@@ -37,4 +38,4 @@
"FAIRY": "Fée",
"STELLAR": "Stellaire"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/fr/pokemon-summary.json b/src/locales/fr/pokemon-summary.json
index f0b4f5a474f..01e712c8468 100644
--- a/src/locales/fr/pokemon-summary.json
+++ b/src/locales/fr/pokemon-summary.json
@@ -13,5 +13,32 @@
"metFragment": {
"normal": "rencontré au N.{{level}},\n{{biome}}.",
"apparently": "apparemment rencontré au N.{{level}},\n{{biome}}."
+ },
+ "natureFragment": {
+ "Hardy": "{{nature}}",
+ "Lonely": "{{nature}}",
+ "Brave": "{{nature}}",
+ "Adamant": "{{nature}}",
+ "Naughty": "{{nature}}",
+ "Bold": "{{nature}}",
+ "Docile": "{{nature}}",
+ "Relaxed": "{{nature}}",
+ "Impish": "{{nature}}",
+ "Lax": "{{nature}}",
+ "Timid": "{{nature}}",
+ "Hasty": "{{nature}}",
+ "Serious": "{{nature}}",
+ "Jolly": "{{nature}}",
+ "Naive": "{{nature}}",
+ "Modest": "{{nature}}",
+ "Mild": "{{nature}}",
+ "Quiet": "{{nature}}",
+ "Bashful": "{{nature}}",
+ "Rash": "{{nature}}",
+ "Calm": "{{nature}}",
+ "Gentle": "{{nature}}",
+ "Sassy": "{{nature}}",
+ "Careful": "{{nature}}",
+ "Quirky": "{{nature}}"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/fr/settings.json b/src/locales/fr/settings.json
index 181a593cc99..c752b336b6e 100644
--- a/src/locales/fr/settings.json
+++ b/src/locales/fr/settings.json
@@ -100,7 +100,7 @@
"moveTouchControls": "Déplacer les contrôles tactiles",
"shopOverlayOpacity": "Opacité boutique",
"shopCursorTarget": "Choix après relance",
- "items": "Obj. gratuits",
+ "rewards": "Obj. gratuits",
"reroll": "Relance",
"shop": "Boutique",
"checkTeam": "Équipe"
diff --git a/src/locales/fr/trainer-classes.json b/src/locales/fr/trainer-classes.json
index cebdadf09e9..b7027cf544f 100644
--- a/src/locales/fr/trainer-classes.json
+++ b/src/locales/fr/trainer-classes.json
@@ -101,8 +101,8 @@
"workers": "Ouvriers",
"youngster": "Gamin",
"rocket_grunt": "Sbire de la Team Rocket",
- "rocket_grunt_female": "Sbire de la Team Rocket",
"rocket_grunts": "Sbires de la Team Rocket",
+ "rocket_grunt_female": "Sbire de la Team Rocket",
"magma_grunt": "Sbire de la Team Magma",
"magma_grunt_female": "Sbire de la Team Magma",
"magma_grunts": "Sbires de la Team Magma",
@@ -123,6 +123,7 @@
"aether_grunts": "Employés de la Fondation Æther",
"skull_grunt": "Sbire de la Team Skull",
"skull_grunt_female": "Sbire de la Team Skull",
+ "skull_grunts": "Sbires de la Team Skull",
"macro_grunt": "Employé de Macro Cosmos",
"macro_grunt_female": "Employée de Macro Cosmos",
"macro_grunts": "Employés de Macro Cosmos"
diff --git a/src/locales/it/achv.json b/src/locales/it/achv.json
index 98e41005c46..d1607f6c548 100644
--- a/src/locales/it/achv.json
+++ b/src/locales/it/achv.json
@@ -80,7 +80,7 @@
"100_RIBBONS": {
"name": "Campione Lega Assoluta"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "Lavoro di Squadra",
"description": "Trasferisci almeno sei bonus statistiche tramite staffetta"
},
@@ -261,4 +261,4 @@
"name": "Buona la prima!",
"description": "Completa la modalità sfida 'Un nuovo inizio'."
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/it/arena-tag.json b/src/locales/it/arena-tag.json
index 9e26dfeeb6e..a1c5ee5b3c9 100644
--- a/src/locales/it/arena-tag.json
+++ b/src/locales/it/arena-tag.json
@@ -1 +1,8 @@
-{}
\ No newline at end of file
+{
+ "safeguardOnAdd": "Un velo mistico ricopre il campo!",
+ "safeguardOnAddPlayer": "Un velo mistico ricopre la tua squadra!",
+ "safeguardOnAddEnemy": "Un velo mistico ricopre la squadra avversaria!",
+ "safeguardOnRemove": "Il campo non è più protetto da Salvaguardia!",
+ "safeguardOnRemovePlayer": "La tua squadra non è più protetta da Salvaguardia!",
+ "safeguardOnRemoveEnemy": "La squadra avversaria non è più protetta da Salvaguardia!"
+}
\ No newline at end of file
diff --git a/src/locales/it/modifier-type.json b/src/locales/it/modifier-type.json
index f5cee70bbc9..99c06bb2038 100644
--- a/src/locales/it/modifier-type.json
+++ b/src/locales/it/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "Raddoppia la possibilità di imbattersi in doppie battaglie per {{battleCount}} battaglie."
},
- "TempBattleStatBoosterModifierType": {
- "description": "Aumenta {{tempBattleStatName}} di un livello a tutti i Pokémon nel gruppo per 5 battaglie."
+ "TempStatStageBoosterModifierType": {
+ "description": "Aumenta la statistica {{stat}} di un livello\na tutti i Pokémon nel gruppo per 5 battaglie."
},
"AttackTypeBoosterModifierType": {
"description": "Aumenta la potenza delle mosse di tipo {{moveType}} del 20% per un Pokémon."
@@ -59,10 +59,10 @@
"description": "Aumenta il livello di un Pokémon di {{levels}}."
},
"AllPokemonLevelIncrementModifierType": {
- "description": "Aumenta i livell di tutti i Pokémon della squadra di {{levels}}."
+ "description": "Aumenta il livello di tutti i Pokémon della squadra di {{levels}}."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "Aumenta {{statName}} di base del possessore del 10%."
+ "BaseStatBoosterModifierType": {
+ "description": "Aumenta l'/la {{stat}} di base del possessore del 10%."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "Restituisce il 100% dei PS a tutti i Pokémon."
@@ -248,6 +248,12 @@
"name": "Mirino",
"description": "Lente che aumenta la probabilità di sferrare brutti colpi."
},
+ "DIRE_HIT": {
+ "name": "Supercolpo",
+ "extra": {
+ "raises": "Tasso di brutti colpi"
+ }
+ },
"LEEK": {
"name": "Porro",
"description": "Strumento da dare a Farfetch'd. Lungo gambo di porro che aumenta la probabilità di sferrare brutti colpi."
@@ -411,25 +417,13 @@
"description": "Strumento da dare a Ditto. Questa strana polvere, fine e al contempo dura, aumenta la Velocità."
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "Attacco X",
"x_defense": "Difesa X",
"x_sp_atk": "Att. Speciale X",
"x_sp_def": "Dif. Speciale X",
"x_speed": "Velocità X",
- "x_accuracy": "Precisione X",
- "dire_hit": "Supercolpo"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "Attacco",
- "DEF": "Difesa",
- "SPATK": "Att. Speciale",
- "SPDEF": "Dif. Speciale",
- "SPD": "Velocità",
- "ACC": "Precisione",
- "CRIT": "Tasso di brutti colpi",
- "EVA": "Elusione",
- "DEFAULT": "???"
+ "x_accuracy": "Precisione X"
},
"AttackTypeBoosterItem": {
"silk_scarf": "Sciarpa seta",
@@ -604,6 +598,6 @@
"DRAGON_MEMORY": "ROM Drago",
"DARK_MEMORY": "ROM Buio",
"FAIRY_MEMORY": "ROM Folletto",
- "BLANK_MEMORY": "ROM Vuota"
+ "NORMAL_MEMORY": "ROM Normale"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/it/modifier.json b/src/locales/it/modifier.json
index 397a1f21f9a..c42bf04bc8a 100644
--- a/src/locales/it/modifier.json
+++ b/src/locales/it/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "{{pokemonNameWithAffix}} recupera alcuni PS con\nil/la suo/a {{typeName}}!",
"hitHealApply": "{{pokemonNameWithAffix}} recupera alcuni PS con\nil/la suo/a {{typeName}}!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} torna in forze\ngrazie al/alla suo/a {{typeName}}!",
- "pokemonResetNegativeStatStageApply": "La riduzione alle statistiche di {{pokemonNameWithAffix}}\nviene annullata grazie al/alla suo/a {{typeName}}!",
+ "resetNegativeStatStageApply": "La riduzione alle statistiche di {{pokemonNameWithAffix}}\nviene annullata grazie al/alla suo/a {{typeName}}!",
"moneyInterestApply": "Ricevi un interesse pari a {{moneyAmount}}₽\ngrazie al/allo/a {{typeName}}!",
"turnHeldItemTransferApply": "Il/l'/lo/la {{itemName}} di {{pokemonNameWithAffix}} è stato assorbito\ndal {{typeName}} di {{pokemonName}}!",
"contactHeldItemTransferApply": "Il/l'/lo/la {{itemName}} di {{pokemonNameWithAffix}} è stato rubato\nda {{pokemonName}} con {{typeName}}!",
diff --git a/src/locales/it/move-trigger.json b/src/locales/it/move-trigger.json
index e852c2fb52a..785972b90f9 100644
--- a/src/locales/it/move-trigger.json
+++ b/src/locales/it/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}} riduce i suoi PS per potenziare la sua mossa!",
"absorbedElectricity": "{{pokemonName}} assorbe elettricità!",
"switchedStatChanges": "{{pokemonName}} scambia con il bersaglio le modifiche alle statistiche!",
+ "switchedTwoStatChanges": "{{pokemonName}} scambia con il bersaglio le modifiche a {{firstStat}} e {{secondStat}}!",
+ "switchedStat": "{{pokemonName}} scambia la sua {{stat}} con quella del bersaglio!",
+ "sharedGuard": "{{pokemonName}} somma le sue capacità difensive con quelle del bersaglio e le ripartisce equamente!",
+ "sharedPower": "{{pokemonName}} somma le sue capacità offensive con quelle del bersaglio e le ripartisce equamente!",
"goingAllOutForAttack": "{{pokemonName}} fa sul serio!",
"regainedHealth": "{{pokemonName}} s'è\nripreso!",
"keptGoingAndCrashed": "{{pokemonName}} si sbilancia e\nsi schianta!",
@@ -61,5 +65,6 @@
"suppressAbilities": "L’abilità di {{pokemonName}}\nperde ogni efficacia!",
"revivalBlessing": "{{pokemonName}} torna in forze!",
"swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!",
- "exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!"
+ "exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!",
+ "safeguard": "Salvaguardia protegge {{targetName}}!"
}
\ No newline at end of file
diff --git a/src/locales/it/party-ui-handler.json b/src/locales/it/party-ui-handler.json
index f5582e5b630..95466779727 100644
--- a/src/locales/it/party-ui-handler.json
+++ b/src/locales/it/party-ui-handler.json
@@ -1,42 +1,47 @@
{
- "SEND_OUT": "Manda in campo",
- "SUMMARY": "Sommario",
- "CANCEL": "Annulla",
- "RELEASE": "Rilascia",
- "APPLY": "Applica",
- "TEACH": "Insegna",
- "SPLICE": "Unisci",
- "UNSPLICE": "Dividi",
- "ACTIVATE": "Attiva",
- "DEACTIVATE": "Disattiva",
- "TRANSFER": "Trasferisci",
- "ALL": "Tutto",
- "PASS_BATON": "Staffetta",
- "UNPAUSE_EVOLUTION": "Consenti evoluzione",
- "REVIVE": "Revitalizza",
- "RENAME": "Rinomina",
- "choosePokemon": "Scegli un Pokémon.",
- "doWhatWithThisPokemon": "Hai selezionato questo Pokémon.",
- "noEnergy": "{{pokemonName}} non ha più energie\nper lottare!",
- "hasEnergy": "{{pokemonName}} ha ancora energie\nper lottare!",
- "cantBeUsed": "{{pokemonName}} non può essere usato\nin questa sfida!",
- "tooManyItems": "{{pokemonName}} possiede già\nquest'oggetto in abbondanza!",
- "anyEffect": "Non avrebbe alcun effetto.",
- "unpausedEvolutions": "{{pokemonName}} può di nuovo evolversi.",
- "unspliceConfirmation": "Vuoi davvero dividere {{fusionName}}\nda {{pokemonName}}? {{fusionName}} andrà perduto.",
- "wasReverted": "{{fusionName}} è tornato ad essere {{pokemonName}}.",
- "releaseConfirmation": "Vuoi davvero liberare {{pokemonName}}?",
- "releaseInBattle": "Non puoi liberare un Pokémon che sta combattendo!",
- "selectAMove": "Scegli una mossa.",
- "changeQuantity": "Scegli un oggetto da trasferire.\nUsa < e > per cambiarne la quantità.",
- "selectAnotherPokemonToSplice": "Scegli un altro Pokémon da unire.",
- "cancel": "Annulla",
- "goodbye": "Addio, {{pokemonName}}!",
- "byebye": "Ciao ciao, {{pokemonName}}!",
- "farewell": "Arrivederci, {{pokemonName}}!",
- "soLong": "È stato bello, {{pokemonName}}!",
- "thisIsWhereWePart": "Le nostre strade si dividono, {{pokemonName}}!",
- "illMissYou": "Mi mancherai, {{pokemonName}}!",
- "illNeverForgetYou": "Non ti dimenticherò, {{pokemonName}}!",
- "untilWeMeetAgain": "Alla prossima, {{pokemonName}}!"
- }
\ No newline at end of file
+ "SEND_OUT": "Manda in campo",
+ "SUMMARY": "Sommario",
+ "CANCEL": "Annulla",
+ "RELEASE": "Rilascia",
+ "APPLY": "Applica",
+ "TEACH": "Insegna",
+ "SPLICE": "Unisci",
+ "UNSPLICE": "Dividi",
+ "ACTIVATE": "Attiva",
+ "DEACTIVATE": "Disattiva",
+ "TRANSFER": "Trasferisci",
+ "ALL": "Tutto",
+ "PASS_BATON": "Staffetta",
+ "UNPAUSE_EVOLUTION": "Consenti evoluzione",
+ "REVIVE": "Revitalizza",
+ "RENAME": "Rinomina",
+ "choosePokemon": "Scegli un Pokémon.",
+ "doWhatWithThisPokemon": "Hai selezionato questo Pokémon.",
+ "noEnergy": "{{pokemonName}} non ha più energie\nper lottare!",
+ "hasEnergy": "{{pokemonName}} ha ancora energie\nper lottare!",
+ "cantBeUsed": "{{pokemonName}} non può essere usato\nin questa sfida!",
+ "tooManyItems": "{{pokemonName}} possiede già\nquest'oggetto in abbondanza!",
+ "anyEffect": "Non avrebbe alcun effetto.",
+ "unpausedEvolutions": "{{pokemonName}} può di nuovo evolversi.",
+ "unspliceConfirmation": "Vuoi davvero dividere {{fusionName}}\nda {{pokemonName}}? {{fusionName}} andrà perduto.",
+ "wasReverted": "{{fusionName}} è tornato ad essere {{pokemonName}}.",
+ "releaseConfirmation": "Vuoi davvero liberare {{pokemonName}}?",
+ "releaseInBattle": "Non puoi liberare un Pokémon che sta combattendo!",
+ "selectAMove": "Scegli una mossa.",
+ "changeQuantity": "Scegli un oggetto da trasferire.\nUsa < e > per cambiarne la quantità.",
+ "selectAnotherPokemonToSplice": "Scegli un altro Pokémon da unire.",
+ "cancel": "Annulla",
+ "able": "Sì!",
+ "notAble": "No!",
+ "learned": "La conosce!",
+ "goodbye": "Addio, {{pokemonName}}!",
+ "byebye": "Ciao ciao, {{pokemonName}}!",
+ "farewell": "Arrivederci, {{pokemonName}}!",
+ "soLong": "È stato bello, {{pokemonName}}!",
+ "thisIsWhereWePart": "Le nostre strade si dividono, {{pokemonName}}!",
+ "illMissYou": "Mi mancherai, {{pokemonName}}!",
+ "illNeverForgetYou": "Non ti dimenticherò, {{pokemonName}}!",
+ "untilWeMeetAgain": "Alla prossima, {{pokemonName}}!",
+ "sayonara": "Sayonara, {{pokemonName}}!",
+ "smellYaLater": "Ci becchiamo, {{pokemonName}}!"
+}
diff --git a/src/locales/it/pokemon-info-container.json b/src/locales/it/pokemon-info-container.json
index c3cc8d49ce1..f3de9081ebc 100644
--- a/src/locales/it/pokemon-info-container.json
+++ b/src/locales/it/pokemon-info-container.json
@@ -2,5 +2,6 @@
"moveset": "Set di mosse",
"gender": "Genere:",
"ability": "Abilità:",
- "nature": "Natura:"
-}
\ No newline at end of file
+ "nature": "Natura:",
+ "form": "Forma:"
+}
diff --git a/src/locales/it/pokemon.json b/src/locales/it/pokemon.json
index 9e26dfeeb6e..dcc4cc7f310 100644
--- a/src/locales/it/pokemon.json
+++ b/src/locales/it/pokemon.json
@@ -1 +1,23 @@
-{}
\ No newline at end of file
+{
+ "type_null": "Tipo Zero",
+ "great_tusk": "Grandizanne",
+ "scream_tail": "Codaurlante",
+ "brute_bonnet": "Fungofurioso",
+ "flutter_mane": "Crinealato",
+ "slither_wing": "Alirasenti",
+ "sandy_shocks": "Peldisabbia",
+ "iron_treads": "Solcoferreo",
+ "iron_bundle": "Saccoferreo",
+ "iron_hands": "Manoferrea",
+ "iron_jugulis": "Colloferreo",
+ "iron_moth": "Falenaferrea",
+ "iron_thorns": "Spineferree",
+ "roaring_moon": "Lunaruggente",
+ "iron_valiant": "Eroeferreo",
+ "walking_wake": "Acquecrespe",
+ "iron_leaves": "Fogliaferrea",
+ "gouging_fire": "Vampeaguzze",
+ "raging_bolt": "Furiatonante",
+ "iron_boulder": "Massoferreo",
+ "iron_crown": "Capoferreo"
+}
diff --git a/src/locales/it/settings.json b/src/locales/it/settings.json
index 381503f21bd..c09f5e22d4d 100644
--- a/src/locales/it/settings.json
+++ b/src/locales/it/settings.json
@@ -8,7 +8,7 @@
"moveTouchControls": "Move Touch Controls",
"shopOverlayOpacity": "Opacità Finestra Negozio",
"shopCursorTarget": "Target Cursore Negozio",
- "items": "Oggetti",
+ "rewards": "Oggetti",
"reroll": "Rerolla",
"shop": "Negozio",
"checkTeam": "Squadra"
diff --git a/src/locales/ja/ability-trigger.json b/src/locales/ja/ability-trigger.json
index f9d1cc60e4c..ec77d4d97d3 100644
--- a/src/locales/ja/ability-trigger.json
+++ b/src/locales/ja/ability-trigger.json
@@ -12,7 +12,6 @@
"blockItemTheft": "{{pokemonNameWithAffix}}の {{abilityName}}で\n道具を うばわれない!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}は {{abilityName}}で\n体力を 回復した!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}は {{abilityName}}で\nダメージを 受けない。",
- "postDefendDisguise": "{{pokemonNameWithAffix}}の\nばけのかわが はがれた!",
"moveImmunity": "{{pokemonNameWithAffix}}には\n効果が ないようだ…",
"reverseDrain": "{{pokemonNameWithAffix}}は\nヘドロえきを 吸い取った!",
"postDefendTypeChange": "{{pokemonNameWithAffix}}は {{abilityName}}で\n{{typeName}}タイプに なった!",
@@ -60,4 +59,4 @@
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}の わざわいのおふだ\nまわりの {{statName}}が 弱まった!",
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}の わざわいのたまで\nまわりの {{statName}}が 弱まった!",
"preventBerryUse": "{{pokemonNameWithAffix}}は 緊張して\nきのみが 食べられなくなった!"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/achv.json b/src/locales/ja/achv.json
index 0dc5dc8185a..182da0aed2e 100644
--- a/src/locales/ja/achv.json
+++ b/src/locales/ja/achv.json
@@ -6,7 +6,7 @@
"name": "なし"
},
"MoneyAchv": {
- "description": "一回の ランで ₽{{moneyAmount}}を 稼ぐ"
+ "description": "一回の ランで {{moneyAmount}}円を 稼ぐ"
},
"10K_MONEY": {
"name": "お金を持つ人"
@@ -21,7 +21,7 @@
"name": "超富裕層"
},
"DamageAchv": {
- "description": "一撃で {{damageAmount}}ダメージを 与える"
+ "description": "一撃で HP{{damageAmount}}の ダメージを 与える"
},
"250_DMG": {
"name": "力持ち"
@@ -33,10 +33,11 @@
"name": "カカロット"
},
"10000_DMG": {
- "name": "ワンパンマン"
+ "name": "ワンパンマン",
+ "name_female": "ワンパンウーマン"
},
"HealAchv": {
- "description": "一つの 技や 特性や 持っているアイテムで {{healAmount}}{{HP}}を 一気に 回復する"
+ "description": "一つの 技や 特性や 持っているアイテムで\n{{healAmount}}{{HP}}を 一気に 回復する"
},
"250_HEAL": {
"name": "回復発見者"
@@ -80,9 +81,9 @@
"100_RIBBONS": {
"name": "マスターリーグチャンピオン"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "同力",
- "description": "少なくとも 一つの 能力を 最大まで あげて 他の 手持ちポケモンに バトンタッチする"
+ "description": "少なくとも 一つの 能力を 最大まで あげて\n他の 手持ちポケモンに バトンタッチする"
},
"MAX_FRIENDSHIP": {
"name": "マブ達",
@@ -106,7 +107,7 @@
},
"SPLICE": {
"name": "インフィニット・フュジョン",
- "description": "いでんしのくさびで 二つの ポケモンを 吸収合体させる"
+ "description": "遺伝子のくさびで 二つの ポケモンを 吸収合体させる"
},
"MINI_BLACK_HOLE": {
"name": "アイテムホーリック",
@@ -161,8 +162,8 @@
"description": "クラシックモードを クリアする"
},
"UNEVOLVED_CLASSIC_VICTORY": {
- "name": "Bring Your Child To Work Day",
- "description": "Beat the game in Classic Mode with at least one unevolved party member."
+ "name": "はじめてのおつかい",
+ "description": "少なくとも 一つの 進化していない 手持ちポケモンで\nクラシックモードを クリアする"
},
"MONO_GEN_ONE": {
"name": "原始",
@@ -260,5 +261,9 @@
"FRESH_START": {
"name": "一発で!",
"description": "出直しチャレンジを クリアする"
+ },
+ "INVERSE_BATTLE": {
+ "name": "カガミよミガカ",
+ "description": "反転バトルチャレンジを クリアする\nるすアリク をジンレャチルトバ転反"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/battler-tags.json b/src/locales/ja/battler-tags.json
index beef485ffd2..25412c971e9 100644
--- a/src/locales/ja/battler-tags.json
+++ b/src/locales/ja/battler-tags.json
@@ -1,4 +1,12 @@
{
+ "trappedDesc": "捕らわれること",
+ "flinchedDesc": "ひるむこと",
+ "confusedDesc": "混乱",
+ "infatuatedDesc": "メロメロ",
+ "seedDesc": "種を植えつくこと",
+ "nightmareDesc": "あくむ",
+ "ingrainDesc": "根",
+ "drowsyDesc": "ねむけ",
"rechargingLapse": "{{pokemonNameWithAffix}}は 攻撃の 反動で 動けない!",
"trappedOnAdd": "{{pokemonNameWithAffix}}は もう 逃げられない!",
"trappedOnRemove": "{{pokemonNameWithAffix}}は\n{{moveName}}の 効果が 解けた!",
@@ -13,9 +21,9 @@
"infatuatedOnAdd": "{{pokemonNameWithAffix}}は {{sourcePokemonName}}に メロメロに なった!",
"infatuatedOnOverlap": "{{pokemonNameWithAffix}}は すでに メロメロだ!",
"infatuatedLapse": "{{pokemonNameWithAffix}}は {{sourcePokemonName}}に メロメロだ!",
- "infatuatedLapseImmobilize": "{{pokemonNameWithAffix}}は\nメロメロで わざが 出せなかった!",
+ "infatuatedLapseImmobilize": "{{pokemonNameWithAffix}}は メロメロで 技が出せなかった!",
"infatuatedOnRemove": "{{pokemonNameWithAffix}}は メロメロ状態が 治った!",
- "seededOnAdd": "{{pokemonNameWithAffix}}に 種を 植(う)えつけた!",
+ "seededOnAdd": "{{pokemonNameWithAffix}}に 種を 植えつけた!",
"seededLapse": "やどりぎが {{pokemonNameWithAffix}}の 体力を うばう!",
"seededLapseShed": "{{pokemonNameWithAffix}}は ヘドロえきを 吸い取った!",
"nightmareOnAdd": "{{pokemonNameWithAffix}}は あくむを 見始めた!",
@@ -60,4 +68,4 @@
"cursedOnAdd": "{{pokemonNameWithAffix}}は 自分の 体力を 削って\n{{pokemonName}}に のろいを かけた!",
"cursedLapse": "{{pokemonNameWithAffix}}は のろわれている!",
"stockpilingOnAdd": "{{pokemonNameWithAffix}}は {{stockpiledCount}}つ たくわえた!"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/berry.json b/src/locales/ja/berry.json
index 641901583b2..73d13d5e8f0 100644
--- a/src/locales/ja/berry.json
+++ b/src/locales/ja/berry.json
@@ -1,46 +1,46 @@
{
"SITRUS": {
"name": "オボンのみ",
- "effect": "HP 50%いかのとき HPを 25パーセント かいふくする"
+ "effect": "持たせると HPが 50%以下になるとき HPを 25% 回復する"
},
"LUM": {
"name": "ラムのみ",
- "effect": "すべての じょうたい いじょうと こんらんを かいふくする"
+ "effect": "持たせると 状態異常や 混乱になるとき 回復する\n"
},
"ENIGMA": {
"name": "ナゾのみ",
- "effect": "こうかばつぐんの わざを うけたとき HPを 25パーセント かいふくする"
+ "effect": "持たせると 効果バツグンの 技を 受けたとき HPを 25%回復する"
},
"LIECHI": {
"name": "チイラのみ",
- "effect": "HP 25%いかのとき こうげきが あがる"
+ "effect": "持たせると HPが 25%以下に なるとき 攻撃が あがる"
},
"GANLON": {
"name": "リュガのみ",
- "effect": "HP 25%いかのとき ぼうぎょが あがる"
+ "effect": "持たせると HPが 25%以下に なるとき 防御が あがる\n"
},
"PETAYA": {
"name": "ヤタピのみ",
- "effect": "HP 25%いかのとき とくこうが あがる"
+ "effect": "持たせると HPが 25%以下に なるとき 特攻が あがる\n"
},
"APICOT": {
"name": "ズアのみ",
- "effect": "HP 25%いかのとき とくぼうが あがる"
+ "effect": "持たせると HPが 25%以下に なるとき 特防が あがる\n"
},
"SALAC": {
"name": "カムラのみ",
- "effect": "HP 25%いかのとき すばやさが あがる"
+ "effect": "持たせると HPが 25%以下に なるとき 素早さが あがる"
},
"LANSAT": {
"name": "サンのみ",
- "effect": "HP 25%いかのとき こうげきが きゅうしょに あたりやすくなる"
+ "effect": "持たせると HPが 25%以下に なるとき 攻撃が 急所に 当たりやすくなる"
},
"STARF": {
"name": "スターのみ",
- "effect": "HP 25%いかのとき のうりょくの どれか 1つが ぐーんと あがる"
+ "effect": "持たせると HPが 25%以下に なるとき どれか 1つの 能力が ぐーんと あがる"
},
"LEPPA": {
"name": "ヒメリのみ",
- "effect": "PPが 0に なった わざの PPを 10だけ かいふくする"
+ "effect": "持たせると PPが 0になる 技のPPを 10回復する"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/challenges.json b/src/locales/ja/challenges.json
index 54225ebf766..99c6e978d49 100644
--- a/src/locales/ja/challenges.json
+++ b/src/locales/ja/challenges.json
@@ -1,10 +1,10 @@
{
- "title": "チャレンジを 設定",
+ "title": "チャレンジを 設定",
"illegalEvolution": "{{pokemon}}は このチャレンジで\n対象外の ポケモンに なってしまった!",
"singleGeneration": {
"name": "単一世代",
- "desc": "{{gen}}世代からの ポケモンしか 使えません",
- "desc_default": "選んだ 世代からの ポケモンしか 使えません",
+ "desc": "{{gen}}世代からの ポケモンしか 使えません",
+ "desc_default": "選んだ 世代からの ポケモンしか 使えません",
"gen_1": "1",
"gen_2": "2",
"gen_3": "3",
@@ -17,14 +17,20 @@
},
"singleType": {
"name": "単一タイプ",
- "desc": "{{type}}タイプの ポケモンしか 使えません",
- "desc_default": "選んだ タイプの ポケモンしか 使えません"
+ "desc": "{{type}}タイプの ポケモンしか 使えません",
+ "desc_default": "選んだ タイプの ポケモンしか 使えません"
},
"freshStart": {
"name": "出直し",
- "shortName": "出直し",
- "desc": "ポケローグを 始めた ばかりの ような ままで ゲーム開始の 最初のパートナーしか 使えません",
+ "desc": "ポケローグを 始めた ばかりの ような ままで ゲーム開始の スターターしか 使えません",
+ "value.0": "オフ",
+ "value.1": "オン"
+ },
+ "inverseBattle": {
+ "name": "反転バトル",
+ "shortName": "反バ",
+ "desc": "タイプ相性が 反転で、なんの タイプも 「効果はなし」が ありません\n他の チャレンジの 実績が 無効に されます",
"value.0": "オフ",
"value.1": "オン"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/command-ui-handler.json b/src/locales/ja/command-ui-handler.json
index 0b2020a9517..6248a19785f 100644
--- a/src/locales/ja/command-ui-handler.json
+++ b/src/locales/ja/command-ui-handler.json
@@ -3,5 +3,5 @@
"ball": "ボール",
"pokemon": "ポケモン",
"run": "にげる",
- "actionMessage": "{{pokemonName}}は どうする?"
-}
\ No newline at end of file
+ "actionMessage": "{{pokemonName}}は どうする?"
+}
diff --git a/src/locales/ja/egg.json b/src/locales/ja/egg.json
index b0cb7b7de61..91b1442c56c 100644
--- a/src/locales/ja/egg.json
+++ b/src/locales/ja/egg.json
@@ -4,23 +4,23 @@
"ultraTier": "超レア",
"masterTier": "伝説",
"defaultTier": "ふつう",
- "hatchWavesMessageSoon": "なかから おとが きこえてくる! もうすぐ うまれそう!",
- "hatchWavesMessageClose": "ときどき うごいている みたい。 うまれるまで もう ちょっとかな?",
- "hatchWavesMessageNotClose": "なにが うまれてくるのかな? うまれるまで まだまだ じかんが かかりそう。",
- "hatchWavesMessageLongTime": "この タマゴは うまれるまで かなり じかんが かかりそう。",
+ "hatchWavesMessageSoon": "中から 音が 聞こえてくる! もうすぐ 生まれそう!",
+ "hatchWavesMessageClose": "時々 動いている みたい。生まれるまで もう ちょっとかな?",
+ "hatchWavesMessageNotClose": "なにが 生まれてくるのかな? 生まれるまで まだまだ 時間が かかりそう。",
+ "hatchWavesMessageLongTime": "この タマゴは 生まれるまで かなり 時間が かかりそう。",
"gachaTypeLegendary": "伝説確率アップ",
- "gachaTypeMove": "レアなタマゴわざ確率アップ",
+ "gachaTypeMove": "レアなタマゴ技確率アップ",
"gachaTypeShiny": "色違い確率アップ",
"selectMachine": "ガチャマシンを選択",
"notEnoughVouchers": "タマゴクーポンが足りません!",
"tooManyEggs": "タマゴが一杯です!",
"pull": "回引く",
"pulls": "回引く",
- "sameSpeciesEgg": "{{species}}は このタマゴから うまれる!",
- "hatchFromTheEgg": "{{pokemonName}}は タマゴから うまれた!",
- "eggMoveUnlock": "タマゴわざ {{moveName}}を おぼえた!",
- "rareEggMoveUnlock": "レアなタマゴわざ {{moveName}}を おぼえた!!",
- "moveUPGacha": "わざ UP!",
+ "sameSpeciesEgg": "{{species}}は このタマゴから 生まれる!",
+ "hatchFromTheEgg": "{{pokemonName}}は タマゴから 生まれた!",
+ "eggMoveUnlock": "タマゴ技: {{moveName}}を 覚えた!",
+ "rareEggMoveUnlock": "レアなタマゴ技: {{moveName}}を 覚えた!!",
+ "moveUPGacha": "技 UP!",
"shinyUPGacha": "色違い UP!",
"legendaryUPGacha": "UP!"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/game-mode.json b/src/locales/ja/game-mode.json
index 36559e5cce3..dc04b36932f 100644
--- a/src/locales/ja/game-mode.json
+++ b/src/locales/ja/game-mode.json
@@ -1,8 +1,8 @@
{
"classic": "クラシック",
"endless": "エンドレス",
- "endlessSpliced": "エンドレス (Spliced)",
+ "endlessSpliced": "エンドレス(吸収合体)",
"dailyRun": "デイリーラン",
- "unknown": "Unknown",
+ "unknown": "???",
"challenge": "チャレンジ"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/game-stats-ui-handler.json b/src/locales/ja/game-stats-ui-handler.json
index 25bb21f701a..2fff802734a 100644
--- a/src/locales/ja/game-stats-ui-handler.json
+++ b/src/locales/ja/game-stats-ui-handler.json
@@ -1,6 +1,6 @@
{
"stats": "統計",
- "playTime": "プレー時間",
+ "playTime": "プレイ時間",
"totalBattles": "合計バトル数",
"starters": "スターター数",
"shinyStarters": "色違いスターター数",
@@ -12,31 +12,31 @@
"dailyRunAttempts": "デイリーラン",
"dailyRunWins": "デイリーラン勝利",
"endlessRuns": "エンドレスラン",
- "highestWaveEndless": "エンドレス最高ウェーブ",
+ "highestWaveEndless": "エンドレス最高波",
"highestMoney": "最大貯金",
"highestDamage": "最大ダメージ",
"highestHPHealed": "最大HP回復",
"pokemonEncountered": "遭遇したポケモン",
"pokemonDefeated": "倒したポケモン",
"pokemonCaught": "捕まえたポケモン",
- "eggsHatched": "ふかしたタマゴ",
- "subLegendsSeen": "見つけた順伝説",
- "subLegendsCaught": "捕まえた順伝説",
- "subLegendsHatched": "ふかした順伝説",
- "legendsSeen": "見つけた伝説",
- "legendsCaught": "捕まえた伝説",
- "legendsHatched": "ふかした伝説",
+ "eggsHatched": "孵化したタマゴ",
+ "subLegendsSeen": "見つけた順伝説ポケモン",
+ "subLegendsCaught": "捕まえた準伝説ポケモン",
+ "subLegendsHatched": "孵化した準伝説ポケモン",
+ "legendsSeen": "見つけた伝説ポケモン",
+ "legendsCaught": "捕まえた伝説ポケモン",
+ "legendsHatched": "孵化した伝説ポケモン",
"mythicalsSeen": "見つけた幻ポケモン",
"mythicalsCaught": "捕まえた幻ポケモン",
- "mythicalsHatched": "ふかした幻ポケモン",
- "shiniesSeen": "見つけた色違い",
- "shiniesCaught": "捕まえた色違い",
- "shiniesHatched": "ふかした色違い",
- "pokemonFused": "合体したポケモン",
+ "mythicalsHatched": "孵化した幻ポケモン",
+ "shiniesSeen": "見つけた色違いポケモン",
+ "shiniesCaught": "捕まえた色違いポケモン",
+ "shiniesHatched": "孵化した色違いポケモン",
+ "pokemonFused": "吸収合体したポケモン",
"trainersDefeated": "倒したトレーナー",
"eggsPulled": "引いたタマゴ",
"rareEggsPulled": "引いたレアタマゴ",
"epicEggsPulled": "引いた超レアタマゴ",
"legendaryEggsPulled": "引いた伝説タマゴ",
"manaphyEggsPulled": "引いたマナフィタマゴ"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/menu-ui-handler.json b/src/locales/ja/menu-ui-handler.json
index beb014b84a9..1930c3999c6 100644
--- a/src/locales/ja/menu-ui-handler.json
+++ b/src/locales/ja/menu-ui-handler.json
@@ -2,19 +2,22 @@
"GAME_SETTINGS": "設定",
"ACHIEVEMENTS": "実績",
"STATS": "統計",
+ "RUN_HISTORY": "ラン歴",
"EGG_LIST": "タマゴリスト",
"EGG_GACHA": "タマゴガチャ",
"MANAGE_DATA": "データ管理",
"COMMUNITY": "コミュニティ",
- "SAVE_AND_QUIT": "保存して終了",
+ "SAVE_AND_QUIT": "セーブして終了",
"LOG_OUT": "ログアウト",
"slot": "スロット {{slotNumber}}",
- "importSession": "セッションのインポート",
+ "importSession": "セッションをインポート",
"importSlotSelect": "インポート先の スロットを 選んでください",
- "exportSession": "セッションのエクスポート",
+ "exportSession": "セッションをエクスポート",
"exportSlotSelect": "エクスポート元の スロットを 選んでください",
- "importData": "データのインポート",
- "exportData": "データのエクスポート",
+ "importRunHistory": "ラン歴をインポート",
+ "exportRunHistory": "ラン歴をエクスポート",
+ "importData": "データをインポート",
+ "exportData": "データをエクスポート",
"consentPreferences": "同意設定",
"linkDiscord": "Discord連携",
"unlinkDiscord": "Discord連携解除",
@@ -22,5 +25,5 @@
"unlinkGoogle": "Google連携解除",
"cancel": "キャンセル",
"losingProgressionWarning": "戦闘開始からの データが 保存されません。\nよろしいですか?",
- "noEggs": "現在 タマゴを ふかしていません!"
-}
\ No newline at end of file
+ "noEggs": "現在は タマゴを 孵化していません!"
+}
diff --git a/src/locales/ja/menu.json b/src/locales/ja/menu.json
index ce6f9ae0672..0e7701578bf 100644
--- a/src/locales/ja/menu.json
+++ b/src/locales/ja/menu.json
@@ -1,38 +1,55 @@
{
"cancel": "キャンセル",
"continue": "つづきから",
- "loadGame": "ロードセーブ",
+ "dailyRun": "日替わりラン(ベータ版)",
+ "loadGame": "セーブを読み込む",
"newGame": "はじめから",
- "username": "ユーザーめい",
+ "settings": "設定",
+ "selectGameMode": "ゲームモードを 選んでください。",
+ "logInOrCreateAccount": "始めるには、ログイン、または 登録して ください。\nメールアドレスは 必要が ありません!",
+ "username": "ユーザー名",
"password": "パスワード",
"login": "ログイン",
- "orUse": "Or use",
- "register": "かいいん とうろく",
- "emptyUsername": "ユーザー名は空にできません",
- "invalidLoginUsername": "入力したユーザー名は無効です",
- "invalidRegisterUsername": "ユーザー名には英文字、数字、アンダースコアのみを含める必要があります",
+ "orUse": "他の\nログイン方法",
+ "register": "登録",
+ "emptyUsername": "ユーザー名を 空にする ことは できません",
+ "invalidLoginUsername": "入力されたユーザー名は無効です",
+ "invalidRegisterUsername": "ユーザー名には 英文字、 数字、 アンダースコアのみを 含くむ必要が あります",
"invalidLoginPassword": "入力したパスワードは無効です",
- "invalidRegisterPassword": "パスワードは6文字以上でなければなりません",
- "usernameAlreadyUsed": "ユーザー名は既に使用されています",
- "accountNonExistent": "ユーザーは存在しません",
- "unmatchingPassword": "パスワードが一致しません",
- "passwordNotMatchingConfirmPassword": "パスワードは確認パスワードと一致する必要があります",
+ "invalidRegisterPassword": "パスワードは 6文字以上 でなければなりません",
+ "usernameAlreadyUsed": "入力したユーザー名は すでに 使用されています",
+ "accountNonExistent": "入力したユーザーは 存在しません",
+ "unmatchingPassword": "入力したパスワードが 一致しません",
+ "passwordNotMatchingConfirmPassword": "パスワードは パスワード確認と 一致する 必要があります",
"confirmPassword": "パスワード確認",
- "registrationAgeWarning": "登録することで、あなたが13歳以上であることを確認します。",
+ "registrationAgeWarning": "登録では 13歳以上 であることを 確認します。",
"backToLogin": "ログインへ",
- "failedToLoadSaveData": "保存データの読み込みに失敗しました。ページを再読み込みしてください。\nこれが続く場合は、管理者に連絡してください。",
- "sessionSuccess": "セッションが正常に読み込まれました。",
- "failedToLoadSession": "セッションデータを読み込むことができませんでした。\nデータが破損している可能性があります。",
- "boyOrGirl": "おとこのこ?\nそれとも おんなのこ?",
- "evolving": "…おや!?\n{{pokemonName}}のようすが…!",
- "stoppedEvolving": "{{pokemonName}}のへんかがとまった",
- "evolutionDone": "おめでとう!\n{{pokemonName}}は{{evolvedPokemonName}}にしんかした!",
- "dailyRankings": "ほんじつのランキング",
- "weeklyRankings": "しゅうのランキング",
+ "failedToLoadSaveData": "セーブデータの 読み込みは 不可能でした。ページを 再読み込み してください。\n長い間に続く 場合は 管理者に 連絡してください。",
+ "sessionSuccess": "セッションが 正常に 読み込まれました。",
+ "failedToLoadSession": "セッションデータを 読み込むことが できませんでした。\nデータが 破損している 可能性が あります。",
+ "boyOrGirl": "男の子?\nそれとも 女の子?",
+ "evolving": "…おや!?\n{{pokemonName}}の 様子が…!",
+ "stoppedEvolving": "あれ…? {{pokemonName}}の 変化が 止まった!",
+ "pauseEvolutionsQuestion": "{{pokemonName}}の 進化を 休止しますか?\n後で 手持ち画面から 進化を また 可能にできます。",
+ "evolutionsPaused": "{{pokemonName}}の 進化を 休止しました。",
+ "evolutionDone": "おめでとう!\n{{pokemonName}}は {{evolvedPokemonName}}に 進化した!",
+ "dailyRankings": "今日のランキング",
+ "weeklyRankings": "今週のランキング",
"noRankings": "ランキングなし",
"positionIcon": "#",
- "loading": "よみこみちゅう…",
+ "usernameScoreboard": "ユーザー名",
+ "score": "スコア",
+ "wave": "波",
+ "loading": "読み込み中…",
+ "loadingAsset": "読み込み中:{{assetName}}",
"playersOnline": "オンラインのプレイヤー",
"yes": "はい",
- "no": "いいえ"
-}
\ No newline at end of file
+ "no": "いいえ",
+ "disclaimer": "免責",
+ "disclaimerDescription": "このゲームは 未完成作品です。\nセーブデータの 損失を含める ゲーム性に関する 問題が 起きる可能性が あります。\nなお、ゲームは 予告なく変更される 可能性もあり、さらに更新され、完成されるとも 限りません。",
+ "choosePokemon": "ポケモンを選ぶ",
+ "renamePokemon": "ニックネームを変える",
+ "rename": "変える",
+ "nickname": "ニックネーム",
+ "errorServerDown": "おや!\nサーバーとの 接続中に 問題が 発生しました。\nゲームは 自動的に 再接続されます から\nウィンドウは 開いたままに しておいても よろしいです。"
+}
diff --git a/src/locales/ja/modifier-select-ui-handler.json b/src/locales/ja/modifier-select-ui-handler.json
index 9370f01491e..d7428c8e373 100644
--- a/src/locales/ja/modifier-select-ui-handler.json
+++ b/src/locales/ja/modifier-select-ui-handler.json
@@ -1,12 +1,12 @@
{
"transfer": "アイテム移行",
"reroll": "選択肢変更",
- "lockRarities": "レア度の固定",
- "checkTeam": "チームを確認",
- "transferDesc": "ポケモンの 手持ちアイテムを 移行する",
+ "lockRarities": "レア度を固定",
+ "checkTeam": "手持ちを確認",
+ "transferDesc": "手持ちポケモンの 持たせるアイテムを 移行する",
"rerollDesc": "お金を 使って アイテムの 選択肢を 変更する",
- "lockRaritiesDesc": "選択肢を 変更するときの レア度を 固定する\n(選択肢変更金額を影響する)",
- "checkTeamDesc": "チームの 状態を 確認する\nフォルムチェンジアイテムを 有効・無効にする",
+ "lockRaritiesDesc": "選択肢を 変更するときの レア度を 固定する\n(選択肢変更の価格は変わる)",
+ "checkTeamDesc": "手持ちポケモンの 状態を 確認する\nフォルムチェンジアイテムを 有効・無効にする",
"rerollCost": "{{formattedMoney}}円",
"itemCost": "{{formattedMoney}}円"
}
diff --git a/src/locales/ja/modifier-type.json b/src/locales/ja/modifier-type.json
index 6effb1d9d82..f1fcc4d3005 100644
--- a/src/locales/ja/modifier-type.json
+++ b/src/locales/ja/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "バトル{{battleCount}}かいのあいだ ダブルバトルになるかくりつを2ばいにする"
},
- "TempBattleStatBoosterModifierType": {
- "description": "すべてのパーティメンバーの {{tempBattleStatName}}を5かいのバトルのあいだ 1だんかいあげる"
+ "TempStatStageBoosterModifierType": {
+ "description": "すべてのパーティメンバーの {{stat}}を5かいのバトルのあいだ 1だんかいあげる"
},
"AttackTypeBoosterModifierType": {
"description": "ポケモンの {{moveType}}タイプのわざのいりょくを20パーセントあげる"
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "すべてのパーティメンバーのレベルを1あげる"
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "ポケモンの{{statName}}のきほんステータスを10パーセントあげる。こたいちがたかいほどスタックのげんかいもたかくなる。"
+ "BaseStatBoosterModifierType": {
+ "description": "ポケモンの{{stat}}のきほんステータスを10パーセントあげる。こたいちがたかいほどスタックのげんかいもたかくなる。"
},
"AllPokemonFullHpRestoreModifierType": {
"description": "すべてのポケモンのHPを100パーセントかいふくする"
@@ -248,6 +248,12 @@
"name": "ピントレンズ",
"description": "弱点が 見える レンズ。持たせた ポケモンの技が 急所に 当たりやすくなる。"
},
+ "DIRE_HIT": {
+ "name": "クリティカット",
+ "extra": {
+ "raises": "きゅうしょりつ"
+ }
+ },
"LEEK": {
"name": "ながねぎ",
"description": "とても長くて 硬いクキ。カモネギに 持たせると 技が 急所に 当たりやすくなる。"
@@ -411,25 +417,13 @@
"description": "メタモンに 持たせると 素早さが あがる 不思議 粉。とても こまかくて 硬い。"
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "プラスパワー",
"x_defense": "ディフェンダー",
"x_sp_atk": "スペシャルアップ",
"x_sp_def": "スペシャルガード",
"x_speed": "スピーダー",
- "x_accuracy": "ヨクアタール",
- "dire_hit": "クリティカット"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "こうげき",
- "DEF": "ぼうぎょ",
- "SPATK": "とくこう",
- "SPDEF": "とくぼう",
- "SPD": "すばやさ",
- "ACC": "めいちゅう",
- "CRIT": "きゅうしょりつ",
- "EVA": "かいひ",
- "DEFAULT": "???"
+ "x_accuracy": "ヨクアタール"
},
"AttackTypeBoosterItem": {
"silk_scarf": "シルクのスカーフ",
@@ -569,4 +563,4 @@
"DOUSE_DRIVE": "アクアカセット",
"ULTRANECROZIUM_Z": "ウルトラネクロZ"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/modifier.json b/src/locales/ja/modifier.json
index 30d5270d94f..a42a849e232 100644
--- a/src/locales/ja/modifier.json
+++ b/src/locales/ja/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 少し 回復!",
"hitHealApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 少し 回復!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 復活した!",
- "pokemonResetNegativeStatStageApply": "{{pokemonNameWithAffix}}は {{typeName}}で\n下がった能力が 元に戻った!",
+ "resetNegativeStatStageApply": "{{pokemonNameWithAffix}}は {{typeName}}で\n下がった能力が 元に戻った!",
"moneyInterestApply": "{{typeName}}から {{moneyAmount}}円 取得した!",
"turnHeldItemTransferApply": "{{pokemonName}}の {{typeName}}が\n{{pokemonNameWithAffix}}の {{itemName}}を 吸い取った!",
"contactHeldItemTransferApply": "{{pokemonName}}の {{typeName}}が\n{{pokemonNameWithAffix}}の {{itemName}}を うばい取った!",
diff --git a/src/locales/ja/move-trigger.json b/src/locales/ja/move-trigger.json
index 0c404feeed6..11a327c01d7 100644
--- a/src/locales/ja/move-trigger.json
+++ b/src/locales/ja/move-trigger.json
@@ -2,7 +2,9 @@
"hitWithRecoil": "{{pokemonName}}は\nはんどうによる ダメージを うけた!",
"cutHpPowerUpMove": "{{pokemonName}}は\nたいりょくを けずって パワーぜんかい!",
"absorbedElectricity": "{{pokemonName}}は\n でんきを きゅうしゅうした!",
- "switchedStatChanges": "{{pokemonName}}は あいてと じぶんのn\nのうりょくへんかを いれかえた!",
+ "switchedStatChanges": "{{pokemonName}}は あいてと じぶんの\nのうりょくへんかを いれかえた!",
+ "sharedGuard": "{{pokemonName}}は\nおたがいのガードを シェアした!",
+ "sharedPower": "{{pokemonName}}は\nおたがいのパワーを シェアした!",
"goingAllOutForAttack": "{{pokemonName}}は\nほんきを だした!",
"regainedHealth": "{{pokemonName}}は\nたいりょくを かいふくした!",
"keptGoingAndCrashed": "いきおいあまって {{pokemonName}}は\nじめんに ぶつかった!",
@@ -59,4 +61,4 @@
"suppressAbilities": "{{pokemonName}}の とくせいが きかなくなった!",
"revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった!",
"swapArenaTags": "{{pokemonName}}は\nおたがいの ばのこうかを いれかえた!"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/pokemon-info.json b/src/locales/ja/pokemon-info.json
index 9b7a7506953..456b4949839 100644
--- a/src/locales/ja/pokemon-info.json
+++ b/src/locales/ja/pokemon-info.json
@@ -2,21 +2,22 @@
"Stat": {
"HP": "HP",
"HPshortened": "HP",
- "ATK": "こうげき",
- "ATKshortened": "こうげき",
- "DEF": "ぼうぎょ",
- "DEFshortened": "ぼうぎょ",
- "SPATK": "とくこう",
- "SPATKshortened": "とくこう",
- "SPDEF": "とくぼう",
- "SPDEFshortened": "とくぼう",
- "SPD": "すばやさ",
- "SPDshortened": "すばやさ",
- "ACC": "めいちゅう",
- "EVA": "かいひ"
+ "ATK": "攻撃",
+ "ATKshortened": "攻撃",
+ "DEF": "防御",
+ "DEFshortened": "防御",
+ "SPATK": "特攻",
+ "SPATKshortened": "特攻",
+ "SPDEF": "特防",
+ "SPDEFshortened": "特防",
+ "SPD": "素早さ",
+ "SPDshortened": "素早さ",
+ "ACC": "命中",
+ "EVA": "回避",
+ "HPStat": "HP"
},
"Type": {
- "UNKNOWN": "Unknown",
+ "UNKNOWN": "???",
"NORMAL": "ノーマル",
"FIGHTING": "かくとう",
"FLYING": "ひこう",
@@ -37,4 +38,4 @@
"FAIRY": "フェアリー",
"STELLAR": "ステラ"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/pokemon.json b/src/locales/ja/pokemon.json
index 6c182c09f86..e6fcd02a750 100644
--- a/src/locales/ja/pokemon.json
+++ b/src/locales/ja/pokemon.json
@@ -437,7 +437,7 @@
"bronzor": "ドーミラー",
"bronzong": "ドータクン",
"bonsly": "ウソハチ",
- "mime_jr.": "マネネ",
+ "mime_jr": "マネネ",
"happiny": "ピンプク",
"chatot": "ペラップ",
"spiritomb": "ミカルゲ",
@@ -770,7 +770,7 @@
"sandygast": "スナバァ",
"palossand": "シロデスナ",
"pyukumuku": "ナマコブシ",
- "type:_null": "タイプ:ヌル",
+ "type_null": "タイプ:ヌル",
"silvally": "シルヴァディ",
"minior": "メテノ",
"komala": "ネッコアラ",
@@ -863,7 +863,7 @@
"obstagoon": "タチフサグマ",
"perrserker": "ニャイキング",
"cursola": "サニゴーン",
- "sirfetch_d": "ネギガナイト",
+ "sirfetchd": "ネギガナイト",
"mr_rime": "バリコオル",
"runerigus": "デスバーン",
"milcery": "マホミル",
@@ -1081,4 +1081,4 @@
"paldea_tauros": "ケンタロス",
"paldea_wooper": "ウパー",
"bloodmoon_ursaluna": "ガチグマ"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/settings.json b/src/locales/ja/settings.json
index c88792979f6..55d39ee70a4 100644
--- a/src/locales/ja/settings.json
+++ b/src/locales/ja/settings.json
@@ -6,12 +6,14 @@
"audio": "音声",
"gamepad": "コントローラー",
"keyboard": "キーボード",
- "gameSpeed": "ゲームスピード",
- "hpBarSpeed": "HPバーの増減スピード",
- "expGainsSpeed": "EXPバーの増加スピード",
- "expPartyDisplay": "パーティの経験値取得表示",
+ "gameSpeed": "ゲームの速さ",
+ "hpBarSpeed": "HPバー増減の速さ",
+ "expGainsSpeed": "経験値バー増加の速さ",
+ "expPartyDisplay": "手持ちの経験値取得表示",
+ "skipSeenDialogues": "もう見た話をスキップ",
"battleStyle": "試合のルール",
- "enableRetries": "リトライを有効にする",
+ "enableRetries": "再挑戦を有効にする",
+ "hideIvs": "個体値スキャナーを隠す",
"tutorials": "チュートリアル",
"touchControls": "タッチ操作",
"vibrations": "振動",
@@ -35,33 +37,71 @@
"moneyFormat": "お金の表示形式",
"damageNumbers": "ダメージ表示",
"simple": "シンプル",
- "fancy": "Fancy",
+ "fancy": "オシャレ",
"abbreviated": "省略",
- "moveAnimations": "戦闘アニメ",
+ "moveAnimations": "戦闘アニメーション",
"showStatsOnLevelUp": "レベルアップ時のステータス表示",
+ "candyUpgradeNotification": "飴アプグレ通知",
"passivesOnly": "パッシブのみ",
+ "candyUpgradeDisplay": "飴アプグレ表示",
"icon": "アイコン",
"animation": "アニメーション",
- "moveInfo": "技の情報表示",
+ "moveInfo": "技情報",
+ "showMovesetFlyout": "技情報表示",
+ "showArenaFlyout": "戦場情報表示",
+ "showTimeOfDayWidget": "時刻指標",
+ "timeOfDayAnimation": "時刻指標アニメーション",
+ "bounce": "跳ねる",
+ "timeOfDay_back": "跳ね返る",
+ "spriteSet": "スプライト設定",
+ "consistent": "一貫",
+ "mixedAnimated": "アニメーションミックス",
+ "fusionPaletteSwaps": "吸収合体ポケモンの色違い",
"playerGender": "プレイヤーの性別",
- "typeHints": "相性のヒント",
+ "typeHints": "タイプ相性ヒント",
"masterVolume": "マスターボリューム",
- "bgmVolume": "BGMのボリューム",
- "seVolume": "SEのボリューム",
+ "bgmVolume": "BGMボリューム",
+ "fieldVolume": "フィールドボリューム",
+ "seVolume": "SEボリューム",
+ "uiVolume": "UIボリューム",
+ "musicPreference": "BGM設定",
+ "mixed": "ミックス",
+ "gamepadPleasePlug": "コントローラーを 接続してください\nまたは、ボタンを 押してください",
+ "delete": "削除",
+ "keyboardPleasePress": "キーを押してください",
"reset": "リセット",
"requireReload": "再読み込みが必要",
"action": "決定",
"back": "戻る",
+ "pressToBind": "押下でキーバインド",
+ "pressButton": "ボタンを押してください",
"buttonUp": "上",
"buttonDown": "下",
"buttonLeft": "左",
"buttonRight": "右",
"buttonAction": "決定",
"buttonMenu": "メニュー",
- "buttonSubmit": "Submit",
+ "buttonSubmit": "提出",
"buttonCancel": "キャンセル",
- "alt": " (代替)",
+ "buttonStats": "能力変化表示",
+ "buttonCycleForm": "フォルム変更",
+ "buttonCycleShiny": "色違い変更",
+ "buttonCycleGender": "性別変更",
+ "buttonCycleAbility": "特性変更",
+ "buttonCycleNature": "性格変更",
+ "buttonCycleVariant": "色変更",
+ "buttonSpeedUp": "速さを上げる",
+ "buttonSlowDown": "速さを下げる",
+ "alt": "(代替)",
"mute": "ミュート",
"controller": "コントローラー",
- "gamepadSupport": "コントローラーサポート"
+ "gamepadSupport": "コントローラーサポート",
+ "showBgmBar": "BGMの名前を表示",
+ "moveTouchControls": "タッチ移動操作",
+ "shopOverlayOpacity": "ショップオーバレイ不透明度",
+ "shopCursorTarget": "ショップカーソル初位置",
+ "rewards": "ご褒美",
+ "reroll": "選択肢変更",
+ "shop": "ショップ",
+ "checkTeam": "手持ちを確認"
}
diff --git a/src/locales/ja/starter-select-ui-handler.json b/src/locales/ja/starter-select-ui-handler.json
index 84eaa8598e9..cab5c500df6 100644
--- a/src/locales/ja/starter-select-ui-handler.json
+++ b/src/locales/ja/starter-select-ui-handler.json
@@ -1,5 +1,5 @@
{
- "confirmStartTeam": "この条件で チャレンジを 始めますか?",
+ "confirmStartTeam": "この手持ちで 始めますか?",
"confirmExit": "終了しますか?",
"invalidParty": "手持ちは チャレンジの 条件で 認められない!",
"gen1": "1世代",
@@ -16,8 +16,8 @@
"passive": "パッシブ:",
"nature": "性格:",
"eggMoves": "タマゴ技",
- "start": "始める",
"addToParty": "手持ちに入れる",
+ "removeFromParty": "手持ちから除く",
"toggleIVs": "個体値を表示",
"manageMoves": "技を並び替える",
"manageNature": "性格を変える",
@@ -36,9 +36,10 @@
"cycleAbility": ": 特性変更",
"cycleNature": ": 性格変更",
"cycleVariant": ": 色変更",
+ "goFilter": ": フィルタ へ ",
"enablePassive": "パッシブ - オン",
"disablePassive": "パッシブ - オフ",
- "locked": "開放されていない",
+ "locked": "非開放",
"disabled": "無効",
"uncaught": "捕まっていない"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ja/trainer-classes.json b/src/locales/ja/trainer-classes.json
index 9e26dfeeb6e..aba294fbbbd 100644
--- a/src/locales/ja/trainer-classes.json
+++ b/src/locales/ja/trainer-classes.json
@@ -1 +1,130 @@
-{}
\ No newline at end of file
+{
+ "ace_trainer": "エリートトレーナー",
+ "ace_trainer_female": "エリートトレーナー",
+ "ace_duo": "エリートコンビ",
+ "artist": "芸術家",
+ "artist_female": "芸術家",
+ "backers": "ファンクラブ",
+ "backpacker": "バックパッカー",
+ "backpacker_female": "バックパッカー",
+ "backpackers": "バックパッカーズ",
+ "baker": "ベーカリー",
+ "battle_girl": "バトルガール",
+ "beauty": "大人のおねえさん",
+ "beginners": "初心者",
+ "biker": "暴走族",
+ "black_belt": "カラテ王",
+ "breeder": "ポケモンブリーダー",
+ "breeder_female": "ポケモンブリーダー",
+ "breeders": "ブリーダーコンビ",
+ "clerk": "ビジネスマン",
+ "clerk_female": "OL",
+ "colleagues": "ビジネスパートナー",
+ "crush_kin": "格闘兄妹",
+ "cyclist": "サイクリング",
+ "cyclist_female": "サイクリング",
+ "cyclists": "サイクリングチーム",
+ "dancer": "ダンサー",
+ "dancer_female": "ダンサー",
+ "depot_agent": "鉄道員",
+ "doctor": "ドクター",
+ "doctor_female": "ドクター",
+ "firebreather": "火吹きやろう",
+ "fisherman": "釣り人",
+ "fisherman_female": "釣り人",
+ "gentleman": "ジェントルマン",
+ "guitarist": "ギタリスト",
+ "guitarist_female": "ギタリスト",
+ "harlequin": "クラウン",
+ "hiker": "山男",
+ "hooligans": "バッドチーム",
+ "hoopster": "バスケ選手",
+ "infielder": "野球選手",
+ "janitor": "清掃員",
+ "lady": "お嬢さま",
+ "lass": "ミニスカート",
+ "linebacker": "フットボーラー",
+ "maid": "メイド",
+ "madame": "マダム",
+ "medical_team": "医療チーム",
+ "musician": "ミュージシャン",
+ "hex_maniac": "オカルトマニア",
+ "nurse": "ナース",
+ "nursery_aide": "保育士",
+ "officer": "お巡りさん",
+ "parasol_lady": "パラソルおねえさん",
+ "pilot": "パイロット",
+ "pokéfan": "大好きクラブ",
+ "pokéfan_female": "大好きクラブ",
+ "pokéfan_family": "大好き夫婦",
+ "preschooler": "園児",
+ "preschooler_female": "園児",
+ "preschoolers": "園児たち",
+ "psychic": "サイキッカー",
+ "psychic_female": "サイキッカー",
+ "psychics": "サイキッ家",
+ "pokémon_ranger": "ポケモンレンジャー",
+ "pokémon_ranger_female": "ポケモンレンジャー",
+ "pokémon_rangers": "レンジャーズ",
+ "ranger": "レンジャー",
+ "restaurant_staff": "レストランスタッフ",
+ "rich": "お金持ち",
+ "rich_female": "お金持ち",
+ "rich_boy": "お坊っちゃま",
+ "rich_couple": "お二人さま",
+ "rich_kid": "ブルジョワ男子",
+ "rich_kid_female": "ブルジョワ女子",
+ "rich_kids": "ブルジョワ子達",
+ "roughneck": "スキンヘッズ",
+ "sailor": "船乗り",
+ "scientist": "研究員",
+ "scientist_female": "研究員",
+ "scientists": "研究チーム",
+ "smasher": "テニスプレイヤー",
+ "snow_worker": "冷凍作業員",
+ "snow_worker_female": "冷凍作業員",
+ "striker": "サッカー選手",
+ "school_kid": "塾帰り",
+ "school_kid_female": "塾帰り",
+ "school_kids": "塾生たち",
+ "swimmer": "海パンやろう",
+ "swimmer_female": "ビキニのおねえさん",
+ "swimmers": "水着カップル",
+ "twins": "双子ちゃん",
+ "veteran": "ベテラントレーナー",
+ "veteran_female": "ベテラントレーナー",
+ "veteran_duo": "ベテランコンビ",
+ "waiter": "ウエーター",
+ "waitress": "ウエートレス",
+ "worker": "作業員",
+ "worker_female": "作業員",
+ "workers": "作業班",
+ "youngster": "短パン小僧",
+ "rocket_grunt": "ロケット団の下っ端",
+ "rocket_grunts": " ロケット団の下っ端",
+ "rocket_grunt_female": "ロケット団の下っ端",
+ "magma_grunt": "マグマ団の下っ端",
+ "magma_grunt_female": "マグマ団の下っ端",
+ "magma_grunts": "マグマ団の下っ端",
+ "aqua_grunt": "アクア団の下っ端",
+ "aqua_grunt_female": "アクア団の下っ端",
+ "aqua_grunts": "アクア団の下っ端",
+ "galactic_grunt": "ギンガ団の下っ端",
+ "galactic_grunt_female": "ギンガ団の下っ端",
+ "galactic_grunts": "ギンガ団の下っ端",
+ "plasma_grunt": "プラスマ団の下っ端",
+ "plasma_grunt_female": "プラズマ団の下っ端",
+ "plasma_grunts": "プラズマ団の下っ端",
+ "flare_grunt": "フレア団の下っ端",
+ "flare_grunt_female": "フレア団の下っ端",
+ "flare_grunts": "フレア団の下っ端",
+ "aether_grunt": "エーテル財団の職員",
+ "aether_grunt_female": "エーテル財団の職員",
+ "aether_grunts": "エーテル財団の職員",
+ "skull_grunt": "スカル団の下っ端",
+ "skull_grunt_female": "スカル団の下っ端",
+ "skull_grunts": "スカル団の下っ端",
+ "macro_grunt": "マクロコスモスのトレーナ",
+ "macro_grunt_female": "マクロコスモスのトレーナ",
+ "macro_grunts": "マクロコスモスのトレーナ"
+}
diff --git a/src/locales/ja/trainer-names.json b/src/locales/ja/trainer-names.json
index 9e26dfeeb6e..70841734b5b 100644
--- a/src/locales/ja/trainer-names.json
+++ b/src/locales/ja/trainer-names.json
@@ -1 +1,164 @@
-{}
\ No newline at end of file
+{
+ "brock": "タケシ",
+ "misty": "カスミ",
+ "lt_surge": "マチス",
+ "erika": "エリカ",
+ "janine": "アンズ",
+ "sabrina": "ナツメ",
+ "blaine": "カツラ",
+ "giovanni": "サカキ",
+ "falkner": "ハヤト",
+ "bugsy": "ツクシ",
+ "whitney": "アカネ",
+ "morty": "マツバ",
+ "chuck": "シジマ",
+ "jasmine": "ミカン",
+ "pryce": "ヤナギ",
+ "clair": "イブキ",
+ "roxanne": "ツツジ",
+ "brawly": "トウキ",
+ "wattson": "テッセン",
+ "flannery": "アスナ",
+ "norman": "センリ",
+ "winona": "ナギ",
+ "tate": "フウ",
+ "liza": "ラン",
+ "juan": "アダン",
+ "roark": "ヒョウタ",
+ "gardenia": "ナタネ",
+ "maylene": "スモモ",
+ "crasher_wake": "マキシ",
+ "fantina": "メリッサ",
+ "byron": "トウガン",
+ "candice": "スズナ",
+ "volkner": "デンジ",
+ "cilan": "デント",
+ "chili": "ポッド",
+ "cress": "コーン",
+ "cheren": "チェレン",
+ "lenora": "アロエ",
+ "roxie": "ホミカ",
+ "burgh": "アーティ",
+ "elesa": "カミツレ",
+ "clay": "ヤーコン",
+ "skyla": "フウロ",
+ "brycen": "ハチク",
+ "drayden": "シャガ",
+ "marlon": "シズイ",
+ "viola": "ビオラ",
+ "grant": "ザクロ",
+ "korrina": "コルニ",
+ "ramos": "フクジ",
+ "clemont": "シトロン",
+ "valerie": "マーシュ",
+ "olympia": "ゴジカ",
+ "wulfric": "ウルップ",
+ "milo": "ヤロー",
+ "nessa": "ルリナ",
+ "kabu": "カブ",
+ "bea": "サイトウ",
+ "allister": "オニオン",
+ "opal": "ポプラ",
+ "bede": "ビート",
+ "gordie": "マクワ",
+ "melony": "メロン",
+ "piers": "ネズ",
+ "marnie": "マリィ",
+ "raihan": "キバナ",
+ "katy": "カエデ",
+ "brassius": "コルサ",
+ "iono": " ナンジャモ",
+ "kofu": "ハイダイ",
+ "larry": "アオキ",
+ "ryme": "ライム",
+ "tulip": "リップ",
+ "grusha": "グルーシャ",
+ "lorelei": "カンナ",
+ "bruno": "シバ",
+ "agatha": "キクコ",
+ "lance": "ワタル",
+ "will": "イツキ",
+ "koga": "キョウ",
+ "karen": "カリン",
+ "sidney": "カゲツ",
+ "phoebe": "フヨウ",
+ "glacia": "プリム",
+ "drake": "ゲンジ",
+ "aaron": "リョウ",
+ "bertha": "キクノ",
+ "flint": "オーバ",
+ "lucian": "ゴヨウ",
+ "shauntal": "シキミ",
+ "marshal": "レンブ",
+ "grimsley": "ギーマ",
+ "caitlin": "カトレア",
+ "malva": "パキラ",
+ "siebold": "ズミ",
+ "wikstrom": "ガンピ",
+ "drasna": "ドラセナ",
+ "hala": "ハラ",
+ "molayne": "マーレイン",
+ "olivia": "ライチ",
+ "acerola": "アセロラ",
+ "kahili": "カヒリ",
+ "rika": "チリ",
+ "poppy": "ポピー",
+ "hassel": "ハッサク",
+ "crispin": "アカマツ",
+ "amarys": "ネリネ",
+ "lacey": "タロ",
+ "drayton": "カキツバタ",
+ "blue": "グリーン",
+ "red": "レッド",
+ "steven": "ダイゴ",
+ "wallace": "ミクリ",
+ "cynthia": "シロナ",
+ "alder": "アデク",
+ "iris": "アイリス",
+ "diantha": "カルネ",
+ "hau": "ハウ",
+ "geeta": "オモダカ",
+ "nemona": "ネモ",
+ "kieran": "スグリ",
+ "leon": "ダンデ",
+ "rival": "フィン",
+ "rival_female": "アイヴィー",
+ "archer": "アポロ",
+ "ariana": "アテナ",
+ "proton": "ランス",
+ "petrel": "ラムダ",
+ "tabitha": "ホムラ",
+ "courtney": "カガリ",
+ "shelly": "イズミ",
+ "matt": "ウシオ",
+ "mars": "マーズ",
+ "jupiter": "ジュピター",
+ "saturn": "サターン",
+ "zinzolin": "ヴィオ",
+ "rood": "ロット",
+ "xerosic": "クセロシキ",
+ "bryony": "バラ",
+ "faba": "ザオボー",
+ "plumeria": "プルメリ",
+ "oleana": "オリーヴ",
+
+ "maxie": "マツブサ",
+ "archie": "アオギリ",
+ "cyrus": "アカギ",
+ "ghetsis": "ゲーチス",
+ "lysandre": "フラダリ",
+ "lusamine": "ルザミーネ",
+ "guzma": "グズマ",
+ "rose": "ローズ",
+
+ "blue_red_double": "グリーンとレッド",
+ "red_blue_double": "レッドとグリーン",
+ "tate_liza_double": "フウとラン",
+ "liza_tate_double": "ランとフウ",
+ "steven_wallace_double": "ダイゴとミクリ",
+ "wallace_steven_double": "ミクリとダイゴ",
+ "alder_iris_double": "アデクとアイリス",
+ "iris_alder_double": "アイリスとアデク",
+ "marnie_piers_double": "マリィとネズ",
+ "piers_marnie_double": "ネズとマリィ"
+}
diff --git a/src/locales/ja/trainer-titles.json b/src/locales/ja/trainer-titles.json
index 9e26dfeeb6e..b3829c701e5 100644
--- a/src/locales/ja/trainer-titles.json
+++ b/src/locales/ja/trainer-titles.json
@@ -1 +1,38 @@
-{}
\ No newline at end of file
+{
+ "elite_four": "四天王",
+ "elite_four_female": "四天王",
+ "gym_leader": "ジムリーダー",
+ "gym_leader_female": "ジムリーダー",
+ "gym_leader_double": "ジムリーダーコンビ",
+ "champion": "チャンピオン",
+ "champion_female": "チャンピオン",
+ "champion_double": "チャンピオンコンビ",
+ "rival": "ライバル",
+ "professor": "ポケモン博士",
+ "frontier_brain": "フロンティアブレーン",
+ "rocket_boss": "ロケット団ボス",
+ "magma_boss": "マグマ団リーダー",
+ "aqua_boss": "アクア団リーダー",
+ "galactic_boss": "ギンガ団ボス",
+ "plasma_boss": "プラズマ団ボス",
+ "flare_boss": "フレア団ボス",
+ "aether_boss": "エーテル代表",
+ "skull_boss": "スカル団ボス",
+ "macro_boss": "マクロコスモス社長",
+
+ "rocket_admin": "ロケット団幹部",
+ "rocket_admin_female": "ロケット団幹部",
+ "magma_admin": "マグマ団幹部",
+ "magma_admin_female": "マグマロケット団幹部",
+ "aqua_admin": "アクア団幹部",
+ "aqua_admin_female": "アクア団幹部",
+ "galactic_commander": "ギンガ団幹部",
+ "galactic_commander_female": "ギンガ団幹部",
+ "plasma_sage": "プラズマ団賢人",
+ "plasma_admin": "プラズマ団賢人",
+ "flare_admin": "フレア団幹部",
+ "flare_admin_female": "フレア団幹部",
+ "aether_admin": "エーテル支部長",
+ "skull_admin": "スカル団幹部",
+ "macro_admin": "マクロコスモス"
+}
diff --git a/src/locales/ko/achv.json b/src/locales/ko/achv.json
index 8546dff949c..9364c1c55b6 100644
--- a/src/locales/ko/achv.json
+++ b/src/locales/ko/achv.json
@@ -80,7 +80,7 @@
"100_RIBBONS": {
"name": "마스터 리그 챔피언"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "팀워크",
"description": "한 개 이상의 능력치가 최대 랭크일 때 배턴터치 사용"
},
@@ -225,7 +225,7 @@
"name": "독침붕처럼 쏴라"
},
"MONO_GHOST": {
- "name": "누굴 부를 거야?"
+ "name": "무서운 게 딱 좋아!"
},
"MONO_STEEL": {
"name": "강철 심장"
@@ -265,4 +265,4 @@
"name": "상성 전문가(였던 것)",
"description": "거꾸로 배틀 챌린지 모드 클리어."
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ko/arena-tag.json b/src/locales/ko/arena-tag.json
index 61586508a94..ce9922ab3bf 100644
--- a/src/locales/ko/arena-tag.json
+++ b/src/locales/ko/arena-tag.json
@@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "우리 편의\n순풍이 멈췄다!",
"tailwindOnRemoveEnemy": "상대의\n순풍이 멈췄다!",
"happyHourOnAdd": "모두 행복한 기분에\n휩싸였다!",
- "happyHourOnRemove": "기분이 원래대로 돌아왔다."
+ "happyHourOnRemove": "기분이 원래대로 돌아왔다.",
+ "safeguardOnAdd": "필드 전체가 신비의 베일에 둘러싸였다!",
+ "safeguardOnAddPlayer": "우리 편은 신비의 베일에 둘러싸였다!",
+ "safeguardOnAddEnemy": "상대 편은 신비의 베일에 둘러싸였다!",
+ "safeguardOnRemove": "필드를 감싸던 신비의 베일이 없어졌다!",
+ "safeguardOnRemovePlayer": "우리 편을 감싸던 신비의 베일이 없어졌다!",
+ "safeguardOnRemoveEnemy": "상대 편을 감싸던 신비의 베일이 없어졌다!"
}
\ No newline at end of file
diff --git a/src/locales/ko/dialogue-misc.json b/src/locales/ko/dialogue-misc.json
index 8445c5c4810..f24fc79ea99 100644
--- a/src/locales/ko/dialogue-misc.json
+++ b/src/locales/ko/dialogue-misc.json
@@ -1,6 +1,6 @@
{
- "ending": "@c{smile}오? 이긴거야?@d{96} @c{smile_eclosed}진즉 알았어야 했는데.\n아무튼, 돌아왔구나.\n$@c{smile}다 끝난거야.@d{64} 네가 굴레를 끝장냈어.\n$@c{serious_smile_fists}네 꿈도 이뤄졌고말야.\n진짜로 한 번도 안 졌잖아.\n$@c{neutral}기억하는 건 우리들 뿐일 모양이지만.@d{96}\n그래도, 괜찮지?\n$@c{serious_smile_fists}오늘의 일은\n너와 나의 마음 속에 항상 함께할 거야.\n$@c{smile_eclosed}여기 구경도 충분히 했으니\n이제 집에 가자.\n$@c{serious_smile_fists}되돌아가서, 다시 배틀을 할 수도 있지 않을까?\n네가 원한다면 말야.",
- "ending_female": "@c{shock}돌아왔구나?@d{32} 그 말은…@d{96} 이겼어?!\n@c{smile_ehalf}그럴 줄 알았다니까.\n$@c{smile_eclosed}물론… 언제나 느껴왔지.\n@c{smile}끝난 거, 맞지? 이 굴레를 말이야.\n$@c{smile_ehalf}네 꿈도 이뤘고 말이야.\n어떻게 한번도 안 졌대?\n$네가 한 일은 나만 기억하게 될 모양이지만.\n@c{angry_mopen}나, 안 까먹어볼 테니까!\n$@c{smile_wave_wink}농담이야!@d{64} @c{smile}절대 안 잊어버릴 거야.@d{32}\n마음 속엔 쭉 남아있을 수 있게.\n$@c{smile_wave}어쨌든,@d{64} 시간이 좀 늦었어…@d{96}\n이런 곳에서 할 말은 아닌가?\n$집에 가자. @c{smile_wave_wink}아마 내일은,\n추억을 되짚어보기 위한 배틀을 해볼 수 있을 거야.",
+ "ending": "@c{shock}돌아왔구나?@d{32} 그 말은…@d{96} 이겼어?!\n@c{smile_ehalf}그럴 줄 알았다니까.\n$@c{smile_eclosed}물론… 언제나 느껴왔지.\n@c{smile}끝난 거, 맞지? 이 굴레를 말이야.\n$@c{smile_ehalf}네 꿈도 이뤘고 말이야.\n어떻게 한번도 안 졌대?\n$네가 한 일은 나만 기억하게 될 모양이지만.\n@c{angry_mopen}나, 안 까먹어볼 테니까!\n$@c{smile_wave_wink}농담이야!@d{64} @c{smile}절대 안 잊어버릴 거야.@d{32}\n마음 속엔 쭉 남아있을 수 있게.\n$@c{smile_wave}어쨌든,@d{64} 시간이 좀 늦었어…@d{96}\n이런 곳에서 할 말은 아닌가?\n$집에 가자. @c{smile_wave_wink}아마 내일은,\n추억을 되짚어보기 위한 배틀을 해볼 수 있을 거야.",
+ "ending_female": "@c{smile}오? 이긴거야?@d{96} @c{smile_eclosed}진즉 알았어야 했는데.\n아무튼, 돌아왔구나.\n$@c{smile}다 끝난거야.@d{64} 네가 굴레를 끝장냈어.\n$@c{serious_smile_fists}네 꿈도 이뤄졌고말야.\n진짜로 한 번도 안 졌잖아.\n$@c{neutral}기억하는 건 우리들 뿐일 모양이지만.@d{96}\n그래도, 괜찮지?\n$@c{serious_smile_fists}오늘의 일은\n너와 나의 마음 속에 항상 함께할 거야.\n$@c{smile_eclosed}여기 구경도 충분히 했으니\n이제 집에 가자.\n$@c{serious_smile_fists}되돌아가서, 다시 배틀을 할 수도 있지 않을까?\n네가 원한다면 말야.",
"ending_endless": "끝에 도달하신 것을 축하드립니다!\n더 많은 컨텐츠를 기다려주세요.",
"ending_name": "Devs"
-}
\ No newline at end of file
+}
diff --git a/src/locales/ko/modifier-type.json b/src/locales/ko/modifier-type.json
index 3d282dfffd1..d94837bb0d2 100644
--- a/src/locales/ko/modifier-type.json
+++ b/src/locales/ko/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "{{battleCount}}번의 배틀 동안 더블 배틀이 등장할 확률이 두 배가 된다."
},
- "TempBattleStatBoosterModifierType": {
- "description": "자신의 모든 포켓몬이 5번의 배틀 동안 {{tempBattleStatName}}[[가]] 한 단계 증가한다."
+ "TempStatStageBoosterModifierType": {
+ "description": "자신의 모든 포켓몬이 5번의 배틀 동안 {{stat}}[[가]] 한 단계 증가한다."
},
"AttackTypeBoosterModifierType": {
"description": "지니게 하면 {{moveType}}타입 기술의 위력이 20% 상승한다."
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "자신의 모든 포켓몬의 레벨이 {{levels}}만큼 상승한다."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "지니게 하면 {{statName}} 종족값을 10% 올려준다. 개체값이 높을수록 더 많이 누적시킬 수 있다."
+ "BaseStatBoosterModifierType": {
+ "description": "지니게 하면 {{stat}} 종족값을 10% 올려준다. 개체값이 높을수록 더 많이 누적시킬 수 있다."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "자신의 포켓몬의 HP를 모두 회복한다."
@@ -248,6 +248,12 @@
"name": "초점렌즈",
"description": "약점이 보이는 렌즈. 지니게 한 포켓몬의 기술이 급소에 맞기 쉬워진다."
},
+ "DIRE_HIT": {
+ "name": "크리티컬커터",
+ "extra": {
+ "raises": "급소율"
+ }
+ },
"LEEK": {
"name": "대파",
"description": "매우 길고 단단한 줄기. 파오리에게 지니게 하면 기술이 급소에 맞기 쉬워진다."
@@ -411,25 +417,13 @@
"description": "메타몽에게 지니게 하면 스피드가 올라가는 이상한 가루. 매우 잘고 단단하다."
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "플러스파워",
"x_defense": "디펜드업",
"x_sp_atk": "스페셜업",
"x_sp_def": "스페셜가드",
"x_speed": "스피드업",
- "x_accuracy": "잘-맞히기",
- "dire_hit": "크리티컬커터"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "공격",
- "DEF": "방어",
- "SPATK": "특수공격",
- "SPDEF": "특수방어",
- "SPD": "스피드",
- "ACC": "명중률",
- "CRIT": "급소율",
- "EVA": "회피율",
- "DEFAULT": "???"
+ "x_accuracy": "잘-맞히기"
},
"AttackTypeBoosterItem": {
"silk_scarf": "실크스카프",
@@ -604,6 +598,6 @@
"DRAGON_MEMORY": "드래곤메모리",
"DARK_MEMORY": "다크메모리",
"FAIRY_MEMORY": "페어리메모리",
- "BLANK_MEMORY": "빈메모리"
+ "NORMAL_MEMORY": "일반메모리"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/ko/move-trigger.json b/src/locales/ko/move-trigger.json
index 61dffa122a3..2a38bb13b0a 100644
--- a/src/locales/ko/move-trigger.json
+++ b/src/locales/ko/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}}[[는]]\n체력을 깎아서 자신의 기술을 강화했다!",
"absorbedElectricity": "{{pokemonName}}는(은)\n전기를 흡수했다!",
"switchedStatChanges": "{{pokemonName}}[[는]] 상대와 자신의\n능력 변화를 바꿨다!",
+ "switchedTwoStatChanges": "{{pokemonName}} 상대와 자신의 {{firstStat}}과 {{secondStat}}의 능력 변화를 바꿨다!",
+ "switchedStat": "{{pokemonName}} 서로의 {{stat}}를 교체했다!",
+ "sharedGuard": "{{pokemonName}} 서로의 가드를 셰어했다!",
+ "sharedPower": "{{pokemonName}} 서로의 파워를 셰어했다!",
"goingAllOutForAttack": "{{pokemonName}}[[는]]\n전력을 다하기 시작했다!",
"regainedHealth": "{{pokemonName}}[[는]]\n기력을 회복했다!",
"keptGoingAndCrashed": "{{pokemonName}}[[는]]\n의욕이 넘쳐서 땅에 부딪쳤다!",
@@ -61,5 +65,6 @@
"suppressAbilities": "{{pokemonName}}의\n특성이 효과를 발휘하지 못하게 되었다!",
"revivalBlessing": "{{pokemonName}}[[는]]\n정신을 차려 싸울 수 있게 되었다!",
"swapArenaTags": "{{pokemonName}}[[는]]\n서로의 필드 효과를 교체했다!",
- "exposedMove": "{{pokemonName}}[[는]]\n{{targetPokemonName}}의 정체를 꿰뚫어 보았다!"
-}
\ No newline at end of file
+ "exposedMove": "{{pokemonName}}[[는]]\n{{targetPokemonName}}의 정체를 꿰뚫어 보았다!",
+ "safeguard": "{{targetName}}[[는]] 신비의 베일이 지켜 주고 있다!"
+}
diff --git a/src/locales/ko/settings.json b/src/locales/ko/settings.json
index b7fc01cb148..c10046385e1 100644
--- a/src/locales/ko/settings.json
+++ b/src/locales/ko/settings.json
@@ -100,7 +100,7 @@
"moveTouchControls": "터치 컨트롤 이동",
"shopOverlayOpacity": "상점 오버레이 투명도",
"shopCursorTarget": "상점 커서 위치",
- "items": "아이템",
+ "rewards": "아이템",
"reroll": "갱신",
"shop": "상점",
"checkTeam": "파티 확인"
diff --git a/src/locales/pt_BR/achv.json b/src/locales/pt_BR/achv.json
index acdec1ae306..93e982b60ea 100644
--- a/src/locales/pt_BR/achv.json
+++ b/src/locales/pt_BR/achv.json
@@ -84,7 +84,7 @@
"100_RIBBONS": {
"name": "Fita de Diamante"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "Trabalho em Equipe",
"description": "Use Baton Pass com pelo menos um atributo aumentado ao máximo"
},
@@ -269,4 +269,4 @@
"name": "A torre da derrotA",
"description": "Complete o desafio da Batalha Inversa.\n.asrevnI ahlataB ad oifased o etelpmoC"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/pt_BR/arena-tag.json b/src/locales/pt_BR/arena-tag.json
index 20ef208c8fc..7ab1ecea721 100644
--- a/src/locales/pt_BR/arena-tag.json
+++ b/src/locales/pt_BR/arena-tag.json
@@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "O Tailwind de sua equipe acabou!",
"tailwindOnRemoveEnemy": "O Tailwind da equipe adversária acabou!",
"happyHourOnAdd": "Todos foram envolvidos por uma atmosfera alegre!",
- "happyHourOnRemove": "A atmosfera retornou ao normal."
+ "happyHourOnRemove": "A atmosfera retornou ao normal.",
+ "safeguardOnAdd": "O campo de batalha está envolto num véu místico!",
+ "safeguardOnAddPlayer": "Sua equipe se envolveu num véu místico!",
+ "safeguardOnAddEnemy": "A equipe adversária se envolveu num véu místico!",
+ "safeguardOnRemove": "O campo não está mais protegido por Safeguard!",
+ "safeguardOnRemovePlayer": "Sua equipe não está mais protegido por Safeguard!",
+ "safeguardOnRemoveEnemy": "A equipe adversária não está mais protegido por Safeguard!"
}
\ No newline at end of file
diff --git a/src/locales/pt_BR/challenges.json b/src/locales/pt_BR/challenges.json
index 6b20a92f4f6..8402ad106b6 100644
--- a/src/locales/pt_BR/challenges.json
+++ b/src/locales/pt_BR/challenges.json
@@ -33,4 +33,4 @@
"value.0": "Desligado",
"value.1": "Ligado"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/pt_BR/dialogue-misc.json b/src/locales/pt_BR/dialogue-misc.json
index 18eb2ba8c91..10e50aaa7e1 100644
--- a/src/locales/pt_BR/dialogue-misc.json
+++ b/src/locales/pt_BR/dialogue-misc.json
@@ -1,6 +1,6 @@
{
- "ending": "@c{smile}Oh? Você venceu?@d{96} @c{smile_eclosed}Acho que eu deveria saber.\nMas, você está de volta agora.\n$@c{smile}Acabou.@d{64} Você quebrou o ciclo.\n$@c{serious_smile_fists}Você também realizou seu sonho, não é?\nVocê não perdeu nenhuma vez.\n$@c{neutral}Eu sou o único que vai lembrar o que você fez.@d{96}\nAcho que está tudo bem, não é?\n$@c{serious_smile_fists}Sua lenda sempre viverá em nossos corações.\n$@c{smile_eclosed}Enfim, já tive o suficiente deste lugar, não é? Vamos para casa.\n$@c{serious_smile_fists}Talvez quando voltarmos, possamos ter outra batalha?\nSe você estiver disposto.",
- "ending_female": "@c{shock}Você está de volta?@d{32} Isso significa que…@d{96} você venceu?!\n@c{smile_ehalf}Eu deveria saber que você conseguiria.\n$@c{smile_eclosed}Claro… Eu sempre tive essa sensação.\n@c{smile}Acabou agora, certo? Você quebrou o ciclo.\n$@c{smile_ehalf}Você também realizou seu sonho, não foi?\nVocê não perdeu nenhuma vez.\n$Eu serei a única a lembrar o que você fez.\n@c{angry_mopen}Eu tentarei não esquecer!\n$@c{smile_wave_wink}Brincadeirinha!@d{64} @c{smile}Eu nunca esqueceria.@d{32}\nSua lenda viverá em nossos corações.\n$@c{smile_wave}De qualquer forma,@d{64} está ficando tarde…@d{96} Eu acho?\nÉ difícil dizer neste lugar.\n$Vamos para casa. @c{smile_wave_wink}Talvez amanhã possamos ter outra batalha, pelos velhos tempos?",
+ "ending": "@c{shock}Você está de volta?@d{32} Isso significa que…@d{96} você venceu?!\n@c{smile_ehalf}Eu deveria saber que você conseguiria.\n$@c{smile_eclosed}Claro… Eu sempre tive essa sensação.\n@c{smile}Acabou agora, certo? Você quebrou o ciclo.\n$@c{smile_ehalf}Você também realizou seu sonho, não foi?\nVocê não perdeu nenhuma vez.\n$Eu serei a única a lembrar o que você fez.\n@c{angry_mopen}Eu tentarei não esquecer!\n$@c{smile_wave_wink}Brincadeirinha!@d{64} @c{smile}Eu nunca esqueceria.@d{32}\nSua lenda viverá em nossos corações.\n$@c{smile_wave}De qualquer forma,@d{64} está ficando tarde…@d{96} Eu acho?\nÉ difícil dizer neste lugar.\n$Vamos para casa. @c{smile_wave_wink}Talvez amanhã possamos ter outra batalha, pelos velhos tempos?",
+ "ending_female": "@c{smile}Oh? Você venceu?@d{96} @c{smile_eclosed}Acho que eu deveria saber.\nMas, você está de volta agora.\n$@c{smile}Acabou.@d{64} Você quebrou o ciclo.\n$@c{serious_smile_fists}Você também realizou seu sonho, não é?\nVocê não perdeu nenhuma vez.\n$@c{neutral}Eu sou o único que vai lembrar o que você fez.@d{96}\nAcho que está tudo bem, não é?\n$@c{serious_smile_fists}Sua lenda sempre viverá em nossos corações.\n$@c{smile_eclosed}Enfim, já tive o suficiente deste lugar, não é? Vamos para casa.\n$@c{serious_smile_fists}Talvez quando voltarmos, possamos ter outra batalha?\nSe você estiver disposto.",
"ending_endless": "Parabéns por alcançar o final atual!\nMais conteúdo chegará em breve.",
"ending_name": "Desenvolvedores"
-}
\ No newline at end of file
+}
diff --git a/src/locales/pt_BR/menu-ui-handler.json b/src/locales/pt_BR/menu-ui-handler.json
index cc087c8335e..df654976d68 100644
--- a/src/locales/pt_BR/menu-ui-handler.json
+++ b/src/locales/pt_BR/menu-ui-handler.json
@@ -14,8 +14,8 @@
"importSlotSelect": "Selecione um slot para importar.",
"exportSession": "Exportar sessão",
"exportSlotSelect": "Selecione um slot para exportar.",
- "importRunHistory":"Importar Histórico de Jogos",
- "exportRunHistory":"Exportar Histórico de Jogos",
+ "importRunHistory": "Importar Histórico de Jogos",
+ "exportRunHistory": "Exportar Histórico de Jogos",
"importData": "Importar dados",
"exportData": "Exportar dados",
"consentPreferences": "Opções de Privacidade",
@@ -25,5 +25,5 @@
"unlinkGoogle": "Desconectar Google",
"cancel": "Cancelar",
"losingProgressionWarning": "Você vai perder todo o progresso desde o início da batalha. Confirmar?",
- "noEggs": "Você não está chocando\nnenhum ovo no momento!"
-}
\ No newline at end of file
+ "noEggs": "Você não está chocando nenhum ovo\nno momento!"
+}
diff --git a/src/locales/pt_BR/modifier-type.json b/src/locales/pt_BR/modifier-type.json
index 1787360b51e..823d6b35e16 100644
--- a/src/locales/pt_BR/modifier-type.json
+++ b/src/locales/pt_BR/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "Dobra as chances de encontrar uma batalha em dupla por {{battleCount}} batalhas."
},
- "TempBattleStatBoosterModifierType": {
- "description": "Aumenta o atributo de {{tempBattleStatName}} para todos os membros da equipe por 5 batalhas."
+ "TempStatStageBoosterModifierType": {
+ "description": "Aumenta o atributo de {{stat}} para todos os membros da equipe por 5 batalhas."
},
"AttackTypeBoosterModifierType": {
"description": "Aumenta o poder dos ataques do tipo {{moveType}} de um Pokémon em 20%."
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "Aumenta em {{levels}} o nível de todos os membros da equipe."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "Aumenta o atributo base de {{statName}} em 10%. Quanto maior os IVs, maior o limite de aumento."
+ "BaseStatBoosterModifierType": {
+ "description": "Aumenta o atributo base de {{stat}} em 10%. Quanto maior os IVs, maior o limite de aumento."
},
"AllPokemonFullHpRestoreModifierType": {
"description": "Restaura totalmente os PS de todos os Pokémon."
@@ -248,6 +248,12 @@
"name": "Lentes de Mira",
"description": "Estas lentes facilitam o foco em pontos fracos. Aumenta a chance de acerto crítico de quem a segurar."
},
+ "DIRE_HIT": {
+ "name": "Direto",
+ "extra": {
+ "raises": "Chance de Acerto Crítico"
+ }
+ },
"LEEK": {
"name": "Alho-poró",
"description": "Esse talo de alho-poró muito longo e rígido aumenta a taxa de acerto crítico dos movimentos do Farfetch'd."
@@ -411,25 +417,13 @@
"description": "Extremamente fino, porém duro, este pó estranho aumenta o atributo de Velocidade de Ditto."
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "Ataque X",
"x_defense": "Defesa X",
"x_sp_atk": "Ataque Esp. X",
"x_sp_def": "Defesa Esp. X",
"x_speed": "Velocidade X",
- "x_accuracy": "Precisão X",
- "dire_hit": "Direto"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "Ataque",
- "DEF": "Defesa",
- "SPATK": "Ataque Esp.",
- "SPDEF": "Defesa Esp.",
- "SPD": "Velocidade",
- "ACC": "Precisão",
- "CRIT": "Chance de Acerto Crítico",
- "EVA": "Evasão",
- "DEFAULT": "???"
+ "x_accuracy": "Precisão X"
},
"AttackTypeBoosterItem": {
"silk_scarf": "Lenço de Seda",
@@ -604,6 +598,6 @@
"DRAGON_MEMORY": "Memória do Dragão",
"DARK_MEMORY": "Memória Sombria",
"FAIRY_MEMORY": "Memória de Fada",
- "BLANK_MEMORY": "Memória Vazia"
+ "NORMAL_MEMORY": "Memória Normal"
}
}
diff --git a/src/locales/pt_BR/modifier.json b/src/locales/pt_BR/modifier.json
index 602a0be3a5b..38622de579e 100644
--- a/src/locales/pt_BR/modifier.json
+++ b/src/locales/pt_BR/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsuas {{typeName}}!",
"hitHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsua {{typeName}}!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} foi reanimado\npor sua {{typeName}}!",
- "pokemonResetNegativeStatStageApply": "Os atributos diminuídos de {{pokemonNameWithAffix}} foram\nrestaurados por seu(sua) {{typeName}}!",
+ "resetNegativeStatStageApply": "Os atributos diminuídos de {{pokemonNameWithAffix}} foram\nrestaurados por seu(sua) {{typeName}}!",
"moneyInterestApply": "Você recebeu um juros de ₽{{moneyAmount}}\nde sua {{typeName}}!",
"turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi absorvido(a)\npelo {{typeName}} de {{pokemonName}}!",
"contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi pego(a)\npela {{typeName}} de {{pokemonName}}!",
diff --git a/src/locales/pt_BR/move-trigger.json b/src/locales/pt_BR/move-trigger.json
index 416740dba0d..9aa13dedad5 100644
--- a/src/locales/pt_BR/move-trigger.json
+++ b/src/locales/pt_BR/move-trigger.json
@@ -61,5 +61,6 @@
"suppressAbilities": "A habilidade de {{pokemonName}}\nfoi suprimida!",
"revivalBlessing": "{{pokemonName}} foi reanimado!",
"swapArenaTags": "{{pokemonName}} trocou os efeitos de batalha que afetam cada lado do campo!",
- "exposedMove": "{{pokemonName}} identificou\n{{targetPokemonName}}!"
-}
\ No newline at end of file
+ "exposedMove": "{{pokemonName}} identificou\n{{targetPokemonName}}!",
+ "safeguard": "{{targetName}} está protegido por Safeguard!"
+}
diff --git a/src/locales/pt_BR/pokemon-summary.json b/src/locales/pt_BR/pokemon-summary.json
index 62add589847..4c427dbac4f 100644
--- a/src/locales/pt_BR/pokemon-summary.json
+++ b/src/locales/pt_BR/pokemon-summary.json
@@ -13,5 +13,32 @@
"metFragment": {
"normal": "encontrado no Nv.{{level}},\n{{biome}}.",
"apparently": "aparentemente encontrado no Nv.{{level}},\n{{biome}}."
+ },
+ "natureFragment": {
+ "Hardy": "{{nature}}",
+ "Lonely": "{{nature}}",
+ "Brave": "{{nature}}",
+ "Adamant": "{{nature}}",
+ "Naughty": "{{nature}}",
+ "Bold": "{{nature}}",
+ "Docile": "{{nature}}",
+ "Relaxed": "{{nature}}",
+ "Impish": "{{nature}}",
+ "Lax": "{{nature}}",
+ "Timid": "{{nature}}",
+ "Hasty": "{{nature}}",
+ "Serious": "{{nature}}",
+ "Jolly": "{{nature}}",
+ "Naive": "{{nature}}",
+ "Modest": "{{nature}}",
+ "Mild": "{{nature}}",
+ "Quiet": "{{nature}}",
+ "Bashful": "{{nature}}",
+ "Rash": "{{nature}}",
+ "Calm": "{{nature}}",
+ "Gentle": "{{nature}}",
+ "Sassy": "{{nature}}",
+ "Careful": "{{nature}}",
+ "Quirky": "{{nature}}"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/pt_BR/settings.json b/src/locales/pt_BR/settings.json
index 58ccb45f86d..74f3918bed8 100644
--- a/src/locales/pt_BR/settings.json
+++ b/src/locales/pt_BR/settings.json
@@ -100,7 +100,7 @@
"moveTouchControls": "Mover Controles de Toque",
"shopOverlayOpacity": "Opacidade da Loja",
"shopCursorTarget": "Alvo do Cursor da Loja",
- "items": "Itens",
+ "rewards": "Itens",
"reroll": "Atualizar",
"shop": "Loja",
"checkTeam": "Checar Time"
diff --git a/src/locales/pt_BR/tutorial.json b/src/locales/pt_BR/tutorial.json
index e347ca6fbee..92ea0dd080f 100644
--- a/src/locales/pt_BR/tutorial.json
+++ b/src/locales/pt_BR/tutorial.json
@@ -1,10 +1,10 @@
{
- "intro": "Bem-vindo ao PokéRogue! Este é um jogo Pokémon feito por fãs focado em batalhas com elementos roguelite.\n$Este jogo não é monetizado e não reivindicamos propriedade de Pokémon nem dos ativos protegidos\n$por direitos autorais usados.\n$O jogo é um trabalho em andamento, mas é totalmente jogável.\n$Para relatórios de bugs, use a comunidade no Discord.\n$Se o jogo estiver rodando lentamente, certifique-se de que a 'Aceleração de hardware' esteja ativada \n$nas configurações do seu navegador.",
+ "intro": "Bem-vindo ao PokéRogue!\n$Este é um fangame Pokémon focado em batalhas com elementos roguelite.\n$Este jogo não é monetizado e não reivindicamos propriedade do Pokémon nem dos ativos protegidos$por direitos autorais usados.\n$O jogo é um trabalho em andamento,\nmas totalmente jogável.\n$Para relatórios de bugs, use a comunidade do Discord.\n$Se o jogo rodar lentamente, certifique-se de que\na 'Aceleração de Hardware' esteja ativada$nas configurações do seu navegador.",
"accessMenu": "Para acessar o menu, pressione M ou Esc.\n$O menu contém configurações e diversas funções.",
- "menu": "A partir deste menu, você pode acessar as configurações. \n$Nas configurações, você pode alterar a velocidade do jogo,\n$o estilo da janela, entre outras opções. \n$Existem também vários outros recursos disponíveis aqui.\n$Não deixe de conferir todos eles!",
- "starterSelect": "Aqui você pode escolher seus iniciais apertando a tecla Z ou\na Barra de Espaço.\n$Esses serão os primeiro Pokémon da sua equipe.\n$Cada inicial tem seu custo. Sua equipe pode ter até 6\nmembros, desde que a soma dos custos não ultrapasse 10. \n$Você pode escolher o gênero, a habilidade\ne até a forma do seu inicial.\n$Essas opções dependem das variantes dessa\nespécie que você já capturou ou chocou. \n$Os IVs de cada inicial são os melhores de todos os Pokémon\ndaquela espécie que você já capturou ou chocou.\n$Sempre capture vários Pokémon de várias espécies!",
+ "menu": "A partir deste menu, você pode\\nacessar as configurações.\n$A partir das configurações, você\npode alterar a velocidade do jogo,\n$o estilo da janela e outras opções.\n$Há também vários outros recursos aqui.\nCertifique-se de verificar todos eles!",
+ "starterSelect": "Nesta tela, você pode selecionar seus iniciais\npressionando Z ou a barra de espaço.\n$Esses serão os primeiros membros da sua equipe.\n$Cada inicial tem um custo. Sua equipe pode ter até 6 membros,\ndesde que desde que o custo total não exceda 10.\n$Você pode escolher o gênero, a habilidade\ne até a forma do seu inicial.\n$Essas opções dependem das variantes dessa\nespécie que você já capturou ou chocou.\n$Os IVs de cada inicial são os melhores de todos os Pokémon\ndaquela espécie que você já capturou ou chocou.\n$Sempre capture vários Pokémon de todas as espécies!",
"pokerus": "Todo dia, 3 Pokémon iniciais ficam com uma borda roxa.\n$Caso veja um inicial que você possui com uma dessa, tente\nadicioná-lo a sua equipe. Lembre-se de olhar seu sumário!",
"statChange": "As mudanças de atributos se mantém após a batalha desde que o Pokémon não seja trocado.\n$Seus Pokémon voltam a suas Poké Bolas antes de batalhas contra treinadores e de entrar em um novo bioma.\n$Para ver as mudanças de atributos dos Pokémon em campo, mantena C ou Shift pressionado durante a batalha.",
"selectItem": "Após cada batalha, você pode escolher entre 3 itens aleatórios.\n$Você pode escolher apenas um deles.\n$Esses itens variam entre consumíveis, itens de segurar e itens passivos permanentes.\n$A maioria dos efeitos de itens não consumíveis podem ser acumulados.\n$Alguns itens só aparecerão se puderem ser usados, como os itens de evolução.\n$Você também pode transferir itens de segurar entre os Pokémon utilizando a opção \"Alterar\".\n$A opção de transferir irá aparecer no canto inferior direito assim que você obter um item de segurar.\n$Você pode comprar itens consumíveis com dinheiro, e sua variedade aumentará conforme você for mais longe.\n$Certifique-se de comprá-los antes de escolher seu item aleatório. Ao escolhê-lo, a próxima batalha começará.",
- "eggGacha": "Aqui você pode trocar seus vouchers\npor ovos de Pokémon.\n$Ovos ficam mais próximos de chocar após cada batalha.\nOvos mais raros demoram mais para chocar.\n$Pokémon chocados não serão adicionados a sua equipe,\nmas sim aos seus iniciais.\n$Pokémon chocados geralmente possuem IVs melhores\nque Pokémon selvagens.\n$Alguns Pokémon só podem ser obtidos através de seus ovos.\n$Temos 3 máquinas, cada uma com seu bônus específico,\nentão escolha a que mais lhe convém!"
+ "eggGacha": "Nesta tela, você pode trocar seus vouchers por ovos\nde Pokémon.\n$Ovos ficam mais próximos de chocar após cada batalha.\nOvos mais raros demoram mais tempo para chocar.\n$Pokémon chocados não serão adicionados a sua equipe,\nmas sim aos seus iniciais.\n$Pokémon chocados de ovos geralmente têm IVs melhores\ndo que Pokémon selvagens.\n$Alguns Pokémon só podem ser obtidos através de ovos.\n$Existem 3 máquinas para usar com diferentes bônus, então\nescolha a que mais lhe convém!"
}
diff --git a/src/locales/zh_CN/achv.json b/src/locales/zh_CN/achv.json
index 8de0c48a2c3..90dfda0e3c1 100644
--- a/src/locales/zh_CN/achv.json
+++ b/src/locales/zh_CN/achv.json
@@ -86,7 +86,7 @@
"name": "大师球联盟冠军"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "团队协作",
"description": "在一项属性强化至最大时用接力棒传递给其他宝可梦"
},
diff --git a/src/locales/zh_CN/arena-tag.json b/src/locales/zh_CN/arena-tag.json
index 5a36b3ae1f7..74ad38ba9bf 100644
--- a/src/locales/zh_CN/arena-tag.json
+++ b/src/locales/zh_CN/arena-tag.json
@@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "我方的顺风停止了!",
"tailwindOnRemoveEnemy": "敌方的顺风停止了!",
"happyHourOnAdd": "大家被欢乐的\n气氛包围了!",
- "happyHourOnRemove": "气氛回复到平常了。"
+ "happyHourOnRemove": "气氛回复到平常了。",
+ "safeguardOnAdd": "整个场地被\n神秘之幕包围了!",
+ "safeguardOnAddPlayer": "我方被\n神秘之幕包围了!",
+ "safeguardOnAddEnemy": "对手被\n神秘之幕包围了!",
+ "safeguardOnRemove": "包围整个场地的\n神秘之幕消失了!",
+ "safeguardOnRemovePlayer": "包围我方的\n神秘之幕消失了!",
+ "safeguardOnRemoveEnemy": "包围对手的\n神秘之幕消失了!"
}
\ No newline at end of file
diff --git a/src/locales/zh_CN/dialogue-misc.json b/src/locales/zh_CN/dialogue-misc.json
index e9ac66b7955..07aa336d4f6 100644
--- a/src/locales/zh_CN/dialogue-misc.json
+++ b/src/locales/zh_CN/dialogue-misc.json
@@ -1,4 +1,4 @@
{
- "ending": "@c{smile}哦?你赢了?@d{96} @c{smile_eclosed}我应该早猜到了\n你回来了。\n$@c{smile}结束了。@d{64} 你终结了这个循环。\n$@c{serious_smile_fists}你也完成了自己的梦想,不是吗?\n你甚至一次都没失败。\n$@c{neutral}我是唯一能够记得你所作所为的人@d{96}\n我觉得这应该也还行吧?\n$@c{serious_smile_fists}你的传奇将永远留存于我们心中。\n$@c{smile_eclosed}不管了,我真是受够这个地方了,你也一样吗?我们回家吧。\n$@c{serious_smile_fists}可能等我们回家以后,再打一场?\n要是你想的话",
- "ending_female": "@c{shock}你回来了?@d{32} 也就是说…@d{96} 你赢了呀!?\n@c{smile_ehalf}我应该早料到了。\n$@c{smile_eclosed}当然…我一直有这种感觉\n@c{smile}一切都结束了,对么? 你打破了循环。\n$@c{smile_ehalf}你也完成了自己的梦想,不是吗?\n你甚至一次都没失败。\n$我是唯一能够记得你所作所为的人\n@c{angry_mopen}我会努力不忘掉哒!\n$@c{smile_wave_wink}开玩笑啦,@d{64} @c{smile}我才不会忘呢。@d{32}\n你的传奇将永远留存于我们心中。\n$@c{smile_wave}不管了,@d{64} 时候不早了@d{96} ,应该吧?\n在这地方还真搞不清楚。\n$一起回家吧。 @c{smile_wave_wink}可能明天,我们再来打一场,为了重温回忆嘛~"
-}
\ No newline at end of file
+ "ending": "@c{shock}你回来了?@d{32} 也就是说…@d{96} 你赢了呀!?\n@c{smile_ehalf}我应该早料到了。\n$@c{smile_eclosed}当然…我一直有这种感觉\n@c{smile}一切都结束了,对么? 你打破了循环。\n$@c{smile_ehalf}你也完成了自己的梦想,不是吗?\n你甚至一次都没失败。\n$我是唯一能够记得你所作所为的人\n@c{angry_mopen}我会努力不忘掉哒!\n$@c{smile_wave_wink}开玩笑啦,@d{64} @c{smile}我才不会忘呢。@d{32}\n你的传奇将永远留存于我们心中。\n$@c{smile_wave}不管了,@d{64} 时候不早了@d{96} ,应该吧?\n在这地方还真搞不清楚。\n$一起回家吧。 @c{smile_wave_wink}可能明天,我们再来打一场,为了重温回忆嘛~",
+ "ending_female": "@c{smile}哦?你赢了?@d{96} @c{smile_eclosed}我应该早猜到了\n你回来了。\n$@c{smile}结束了。@d{64} 你终结了这个循环。\n$@c{serious_smile_fists}你也完成了自己的梦想,不是吗?\n你甚至一次都没失败。\n$@c{neutral}我是唯一能够记得你所作所为的人@d{96}\n我觉得这应该也还行吧?\n$@c{serious_smile_fists}你的传奇将永远留存于我们心中。\n$@c{smile_eclosed}不管了,我真是受够这个地方了,你也一样吗?我们回家吧。\n$@c{serious_smile_fists}可能等我们回家以后,再打一场?\n要是你想的话"
+}
diff --git a/src/locales/zh_CN/modifier-type.json b/src/locales/zh_CN/modifier-type.json
index e9172985092..5d6184640b1 100644
--- a/src/locales/zh_CN/modifier-type.json
+++ b/src/locales/zh_CN/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "接下来的{{battleCount}}场战斗是双打的概率翻倍。"
},
- "TempBattleStatBoosterModifierType": {
- "description": "为所有成员宝可梦提升一级{{tempBattleStatName}},持续5场战斗。"
+ "TempStatStageBoosterModifierType": {
+ "description": "为所有成员宝可梦提升一级{{stat}},持续5场战斗。"
},
"AttackTypeBoosterModifierType": {
"description": "一只宝可梦的{{moveType}}系招式威力提升20%。"
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "使一只寶可夢的等級提升{{levels}}級。"
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "增加10%持有者的{{statName}},\n个体值越高堆叠上限越高。"
+ "BaseStatBoosterModifierType": {
+ "description": "增加10%持有者的{{stat}},\n个体值越高堆叠上限越高。"
},
"AllPokemonFullHpRestoreModifierType": {
"description": "所有宝可梦完全回复HP。"
@@ -248,6 +248,12 @@
"name": "焦点镜",
"description": "能看见弱点的镜片。携带它的宝可梦的招式\n会变得容易击中要害。"
},
+ "DIRE_HIT": {
+ "name": "要害攻击",
+ "extra": {
+ "raises": "会心"
+ }
+ },
"LEEK": {
"name": "大葱",
"description": "非常长且坚硬的茎。让大葱鸭携带后,\n招式会变得容易击中要害。"
@@ -411,25 +417,13 @@
"description": "让百变怪携带后,速度就会提高的神奇粉末。\n非常细腻坚硬。"
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "力量强化",
"x_defense": "防御强化",
"x_sp_atk": "特攻强化",
"x_sp_def": "特防强化",
"x_speed": "速度强化",
- "x_accuracy": "命中强化",
- "dire_hit": "要害攻击"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "攻击",
- "DEF": "防御",
- "SPATK": "特攻",
- "SPDEF": "特防",
- "SPD": "速度",
- "ACC": "命中",
- "CRIT": "会心",
- "EVA": "闪避",
- "DEFAULT": "???"
+ "x_accuracy": "命中强化"
},
"AttackTypeBoosterItem": {
"silk_scarf": "丝绸围巾",
@@ -604,6 +598,6 @@
"DRAGON_MEMORY": "龙存储碟",
"DARK_MEMORY": "黑暗存储碟",
"FAIRY_MEMORY": "妖精存储碟",
- "BLANK_MEMORY": "空白存储碟"
+ "NORMAL_MEMORY": "一般存储碟"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/zh_CN/modifier.json b/src/locales/zh_CN/modifier.json
index 707fab20ecc..a50cdd35bc1 100644
--- a/src/locales/zh_CN/modifier.json
+++ b/src/locales/zh_CN/modifier.json
@@ -3,7 +3,7 @@
"turnHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回复了体力!",
"hitHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回复了体力!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}}用{{typeName}}\n恢复了活力!",
- "pokemonResetNegativeStatStageApply": "{{pokemonNameWithAffix}}降低的能力被{{typeName}}\n复原了!",
+ "resetNegativeStatStageApply": "{{pokemonNameWithAffix}}降低的能力被{{typeName}}\n复原了!",
"moneyInterestApply": "用{{typeName}}\n获得了 ₽{{moneyAmount}} 利息!",
"turnHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}吸收了!",
"contactHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}夺取了!",
diff --git a/src/locales/zh_CN/move-trigger.json b/src/locales/zh_CN/move-trigger.json
index 5a76f402783..1eb4c397f45 100644
--- a/src/locales/zh_CN/move-trigger.json
+++ b/src/locales/zh_CN/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}}\n削减了体力并提升了招式威力!",
"absorbedElectricity": "{{pokemonName}}\n吸收了电力!",
"switchedStatChanges": "{{pokemonName}}和对手互换了\n自己的能力变化!",
+ "switchedTwoStatChanges": "{{pokemonName}} 和对手互换了自己的{{firstStat}}和{{secondStat}}的能力变化!",
+ "switchedStat": "{{pokemonName}} 互换了各自的{{stat}}!",
+ "sharedGuard": "{{pokemonName}} 平分了各自的防守!",
+ "sharedPower": "{{pokemonName}} 平分了各自的力量!",
"goingAllOutForAttack": "{{pokemonName}}拿出全力了!",
"regainedHealth": "{{pokemonName}}的\n体力回复了!",
"keptGoingAndCrashed": "{{pokemonName}}因势头过猛\n而撞到了地面!",
@@ -61,5 +65,6 @@
"suppressAbilities": "{{pokemonName}}的特性\n变得无效了!",
"revivalBlessing": "{{pokemonName}}复活了!",
"swapArenaTags": "{{pokemonName}}\n交换了双方的场地效果!",
- "exposedMove": "{{pokemonName}}识破了\n{{targetPokemonName}}的原型!"
+ "exposedMove": "{{pokemonName}}识破了\n{{targetPokemonName}}的原型!",
+ "safeguard": "{{targetName}}\n正受到神秘之幕的保护!"
}
\ No newline at end of file
diff --git a/src/locales/zh_CN/pokemon-info.json b/src/locales/zh_CN/pokemon-info.json
index 5194189c806..a21a8156e4c 100644
--- a/src/locales/zh_CN/pokemon-info.json
+++ b/src/locales/zh_CN/pokemon-info.json
@@ -1,7 +1,7 @@
{
"Stat": {
"HP": "最大HP",
- "HPshortened": "最大HP",
+ "HPshortened": "HP",
"ATK": "攻击",
"ATKshortened": "攻击",
"DEF": "防御",
@@ -37,4 +37,4 @@
"FAIRY": "妖精",
"STELLAR": "星晶"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/zh_CN/settings.json b/src/locales/zh_CN/settings.json
index 3ae0fa8204c..dd001213b9e 100644
--- a/src/locales/zh_CN/settings.json
+++ b/src/locales/zh_CN/settings.json
@@ -99,7 +99,7 @@
"moveTouchControls": "移动触摸控制",
"shopOverlayOpacity": "商店显示不透明度",
"shopCursorTarget": "商店指针位置",
- "items": "道具",
+ "rewards": "道具",
"reroll": "刷新",
"shop": "购买",
"checkTeam": "检查队伍"
diff --git a/src/locales/zh_TW/achv.json b/src/locales/zh_TW/achv.json
index 6587394cf41..9edce2e368d 100644
--- a/src/locales/zh_TW/achv.json
+++ b/src/locales/zh_TW/achv.json
@@ -80,7 +80,7 @@
"100_RIBBONS": {
"name": "大師球聯盟冠軍"
},
- "TRANSFER_MAX_BATTLE_STAT": {
+ "TRANSFER_MAX_STAT_STAGE": {
"name": "團隊協作",
"description": "在一項屬性強化至最大時用接力棒傳遞給其他寶可夢"
},
@@ -257,4 +257,4 @@
"name": "鏡子子鏡",
"description": "完成逆轉之戰挑戰\n戰挑戰之轉逆成完"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/zh_TW/arena-tag.json b/src/locales/zh_TW/arena-tag.json
index b60946a3b77..78246d9c44f 100644
--- a/src/locales/zh_TW/arena-tag.json
+++ b/src/locales/zh_TW/arena-tag.json
@@ -1,5 +1,11 @@
{
"noCritOnAddPlayer": "{{moveName}}保護了你的\n隊伍不被擊中要害!",
"noCritOnAddEnemy": "{{moveName}}保護了對方的\n隊伍不被擊中要害!",
- "noCritOnRemove": "{{pokemonNameWithAffix}}的{{moveName}}\n效果消失了!"
+ "noCritOnRemove": "{{pokemonNameWithAffix}}的{{moveName}}\n效果消失了!",
+ "safeguardOnAdd": "整個場地被\n神秘之幕包圍了!",
+ "safeguardOnAddPlayer": "我方被\n神秘之幕包圍了!",
+ "safeguardOnAddEnemy": "對手被\n神秘之幕包圍了!",
+ "safeguardOnRemove": "包圍整個場地的\n神秘之幕消失了!",
+ "safeguardOnRemovePlayer": "包圍我方的\n神秘之幕消失了!",
+ "safeguardOnRemoveEnemy": "包圍對手的\n神秘之幕消失了!"
}
\ No newline at end of file
diff --git a/src/locales/zh_TW/dialogue-misc.json b/src/locales/zh_TW/dialogue-misc.json
index 24e2109e5b3..408bcac546b 100644
--- a/src/locales/zh_TW/dialogue-misc.json
+++ b/src/locales/zh_TW/dialogue-misc.json
@@ -1,4 +1,4 @@
{
- "ending": "@c{smile}哦?你贏了?@d{96} @c{smile_eclosed}我應該早猜到了\n你回來了。\n$@c{smile}結束了。@d{64} 你終結了這個循環。\n$@c{serious_smile_fists}你也完成了自己的夢想,不是嗎?\n你甚至一次都沒失敗。\n$@c{neutral}我是唯一能夠記得你所作所為的人@d{96}\n我覺得這應該也還行吧?\n$@c{serious_smile_fists}你的傳奇將永遠留存於我們心中。\n$@c{smile_eclosed}不管了,我真是受夠這個地方了,你也一樣嗎?我們回家吧。\n$@c{serious_smile_fists}可能等我們回家以後,再打一場?\n要是你想的話",
- "ending_female": "@c{shock}你回來了?@d{32} 也就是說…@d{96} 你贏了呀!?\n@c{smile_ehalf}我應該早料到了。\n$@c{smile_eclosed}當然…我一直有這種感覺\n@c{smile}一切都結束了,對麼? 你打破了循環。\n$@c{smile_ehalf}你也完成了自己的夢想,不是嗎?\n你甚至一次都沒失敗。\n$我是唯一能夠記得你所作所為的人\n@c{angry_mopen}我會努力不忘掉哒!\n$@c{smile_wave_wink}開玩笑啦,@d{64} @c{smile}我才不會忘呢。@d{32}\n你的傳奇將永遠留存於我們心中。\n$@c{smile_wave}不管了,@d{64} 時候不早了@d{96} ,應該吧?\n在這地方還真搞不清楚。\n$一起回家吧。 @c{smile_wave_wink}可能明天,我們再來打一場,為了重溫回憶嘛~"
-}
\ No newline at end of file
+ "ending": "@c{shock}你回來了?@d{32} 也就是說…@d{96} 你贏了呀!?\n@c{smile_ehalf}我應該早料到了。\n$@c{smile_eclosed}當然…我一直有這種感覺\n@c{smile}一切都結束了,對麼? 你打破了循環。\n$@c{smile_ehalf}你也完成了自己的夢想,不是嗎?\n你甚至一次都沒失敗。\n$我是唯一能夠記得你所作所為的人\n@c{angry_mopen}我會努力不忘掉哒!\n$@c{smile_wave_wink}開玩笑啦,@d{64} @c{smile}我才不會忘呢。@d{32}\n你的傳奇將永遠留存於我們心中。\n$@c{smile_wave}不管了,@d{64} 時候不早了@d{96} ,應該吧?\n在這地方還真搞不清楚。\n$一起回家吧。 @c{smile_wave_wink}可能明天,我們再來打一場,為了重溫回憶嘛~",
+ "ending_female": "@c{smile}哦?你贏了?@d{96} @c{smile_eclosed}我應該早猜到了\n你回來了。\n$@c{smile}結束了。@d{64} 你終結了這個循環。\n$@c{serious_smile_fists}你也完成了自己的夢想,不是嗎?\n你甚至一次都沒失敗。\n$@c{neutral}我是唯一能夠記得你所作所為的人@d{96}\n我覺得這應該也還行吧?\n$@c{serious_smile_fists}你的傳奇將永遠留存於我們心中。\n$@c{smile_eclosed}不管了,我真是受夠這個地方了,你也一樣嗎?我們回家吧。\n$@c{serious_smile_fists}可能等我們回家以後,再打一場?\n要是你想的話"
+}
diff --git a/src/locales/zh_TW/modifier-type.json b/src/locales/zh_TW/modifier-type.json
index a7ddaea077b..68881a206cb 100644
--- a/src/locales/zh_TW/modifier-type.json
+++ b/src/locales/zh_TW/modifier-type.json
@@ -49,8 +49,8 @@
"DoubleBattleChanceBoosterModifierType": {
"description": "接下來的{{battleCount}}場戰鬥是雙打的概率翻倍。"
},
- "TempBattleStatBoosterModifierType": {
- "description": "爲所有成員寶可夢提升一級{{tempBattleStatName}},持續5場戰鬥。"
+ "TempStatStageBoosterModifierType": {
+ "description": "爲所有成員寶可夢提升一級{{stat}},持續5場戰鬥。"
},
"AttackTypeBoosterModifierType": {
"description": "一隻寶可夢的{{moveType}}系招式威力提升20%。"
@@ -61,8 +61,8 @@
"AllPokemonLevelIncrementModifierType": {
"description": "Increases all party members' level by {{levels}}."
},
- "PokemonBaseStatBoosterModifierType": {
- "description": "增加持有者的{{statName}}10%,個體值越高堆疊\n上限越高。"
+ "BaseStatBoosterModifierType": {
+ "description": "增加持有者的{{stat}}10%,個體值越高堆疊\n上限越高。"
},
"AllPokemonFullHpRestoreModifierType": {
"description": "所有寶可夢完全恢復HP。"
@@ -244,6 +244,12 @@
"name": "焦點鏡",
"description": "能看見弱點的鏡片。攜帶它的寶可夢的招式 會變得容易擊中要害。"
},
+ "DIRE_HIT": {
+ "name": "要害攻擊",
+ "extra": {
+ "raises": "會心"
+ }
+ },
"LEEK": {
"name": "大蔥",
"description": "非常長且堅硬的莖。讓大蔥鴨攜帶後,招式會 變得容易擊中要害。"
@@ -407,25 +413,13 @@
"description": "讓百變怪攜帶後,速度就會提高的神奇粉末。非常細緻堅硬。"
}
},
- "TempBattleStatBoosterItem": {
+ "TempStatStageBoosterItem": {
"x_attack": "力量強化",
"x_defense": "防禦強化",
"x_sp_atk": "特攻強化",
"x_sp_def": "特防強化",
"x_speed": "速度強化",
- "x_accuracy": "命中強化",
- "dire_hit": "要害攻擊"
- },
- "TempBattleStatBoosterStatName": {
- "ATK": "攻擊",
- "DEF": "防禦",
- "SPATK": "特攻",
- "SPDEF": "特防",
- "SPD": "速度",
- "ACC": "命中",
- "CRIT": "會心",
- "EVA": "閃避",
- "DEFAULT": "???"
+ "x_accuracy": "命中強化"
},
"AttackTypeBoosterItem": {
"silk_scarf": "絲綢圍巾",
@@ -600,6 +594,6 @@
"DRAGON_MEMORY": "龍記憶碟",
"DARK_MEMORY": "黑暗記憶碟",
"FAIRY_MEMORY": "妖精記憶碟",
- "BLANK_MEMORY": "空白記憶碟"
+ "NORMAL_MEMORY": "一般記憶碟"
}
-}
\ No newline at end of file
+}
diff --git a/src/locales/zh_TW/modifier.json b/src/locales/zh_TW/modifier.json
index eb4b5107cff..1c0d4760e6f 100644
--- a/src/locales/zh_TW/modifier.json
+++ b/src/locales/zh_TW/modifier.json
@@ -8,4 +8,4 @@
"contactHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}奪取了!",
"enemyTurnHealApply": "{{pokemonNameWithAffix}}\n回復了一些體力!",
"bypassSpeedChanceApply": "{{pokemonName}}用了{{itemName}}後,行動變快了!"
-}
\ No newline at end of file
+}
diff --git a/src/locales/zh_TW/move-trigger.json b/src/locales/zh_TW/move-trigger.json
index 03ca6841a7f..d6d0ce659ea 100644
--- a/src/locales/zh_TW/move-trigger.json
+++ b/src/locales/zh_TW/move-trigger.json
@@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}}\n削減體力並提升了招式威力!",
"absorbedElectricity": "{{pokemonName}}\n吸收了电力!",
"switchedStatChanges": "{{pokemonName}}和對手互換了\n自身的能力變化!",
+ "switchedTwoStatChanges": "{{pokemonName}} 和對手互換了自身的{{firstStat}}和{{secondStat}}的能力變化!",
+ "switchedStat": "{{pokemonName}} 互換了各自的{{stat}}!",
+ "sharedGuard": "{{pokemonName}} 平分了各自的防守!",
+ "sharedPower": "{{pokemonName}} 平分了各自的力量!",
"goingAllOutForAttack": "{{pokemonName}}拿出全力了!",
"regainedHealth": "{{pokemonName}}的\n體力回復了!",
"keptGoingAndCrashed": "{{pokemonName}}因勢頭過猛\n而撞到了地面!",
@@ -61,5 +65,6 @@
"suppressAbilities": "{{pokemonName}}的特性\n變得無效了!",
"revivalBlessing": "{{pokemonName}}復活了!",
"swapArenaTags": "{{pokemonName}}\n交換了雙方的場地效果!",
- "exposedMove": "{{pokemonName}}識破了\n{{targetPokemonName}}的原形!"
+ "exposedMove": "{{pokemonName}}識破了\n{{targetPokemonName}}的原形!",
+ "safeguard": "{{targetName}}\n正受到神秘之幕的保護!"
}
\ No newline at end of file
diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts
index 3afeb79ff2b..fe586074c79 100644
--- a/src/modifier/modifier-type.ts
+++ b/src/modifier/modifier-type.ts
@@ -3,12 +3,10 @@ import { AttackMove, allMoves, selfStatLowerMoves } from "../data/move";
import { MAX_PER_TYPE_POKEBALLS, PokeballType, getPokeballCatchMultiplier, getPokeballName } from "../data/pokeball";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon";
import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions";
-import { Stat, getStatName } from "../data/pokemon-stat";
import { tmPoolTiers, tmSpecies } from "../data/tms";
import { Type } from "../data/type";
import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "../ui/party-ui-handler";
import * as Utils from "../utils";
-import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat";
import { getBerryEffectDescription, getBerryName } from "../data/berry";
import { Unlockables } from "../system/unlockables";
import { StatusEffect, getStatusEffectDescriptor } from "../data/status-effect";
@@ -28,6 +26,7 @@ import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js";
+import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat, getStatKey } from "#app/enums/stat";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@@ -447,26 +446,28 @@ export class DoubleBattleChanceBoosterModifierType extends ModifierType {
}
}
-export class TempBattleStatBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType {
- public tempBattleStat: TempBattleStat;
+export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType {
+ private stat: TempBattleStat;
+ private key: string;
- constructor(tempBattleStat: TempBattleStat) {
- super("", getTempBattleStatBoosterItemName(tempBattleStat).replace(/\./g, "").replace(/[ ]/g, "_").toLowerCase(),
- (_type, _args) => new Modifiers.TempBattleStatBoosterModifier(this, this.tempBattleStat));
+ constructor(stat: TempBattleStat) {
+ const key = TempStatStageBoosterModifierTypeGenerator.items[stat];
+ super("", key, (_type, _args) => new Modifiers.TempStatStageBoosterModifier(this, this.stat));
- this.tempBattleStat = tempBattleStat;
+ this.stat = stat;
+ this.key = key;
}
get name(): string {
- return i18next.t(`modifierType:TempBattleStatBoosterItem.${getTempBattleStatBoosterItemName(this.tempBattleStat).replace(/\./g, "").replace(/[ ]/g, "_").toLowerCase()}`);
+ return i18next.t(`modifierType:TempStatStageBoosterItem.${this.key}`);
}
- getDescription(scene: BattleScene): string {
- return i18next.t("modifierType:ModifierType.TempBattleStatBoosterModifierType.description", { tempBattleStatName: getTempBattleStatName(this.tempBattleStat) });
+ getDescription(_scene: BattleScene): string {
+ return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) });
}
getPregenArgs(): any[] {
- return [ this.tempBattleStat ];
+ return [ this.stat ];
}
}
@@ -611,40 +612,24 @@ export class AllPokemonLevelIncrementModifierType extends ModifierType {
}
}
-function getBaseStatBoosterItemName(stat: Stat) {
- switch (stat) {
- case Stat.HP:
- return "HP Up";
- case Stat.ATK:
- return "Protein";
- case Stat.DEF:
- return "Iron";
- case Stat.SPATK:
- return "Calcium";
- case Stat.SPDEF:
- return "Zinc";
- case Stat.SPD:
- return "Carbos";
- }
-}
+export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
+ private stat: PermanentStat;
+ private key: string;
-export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
- private localeName: string;
- private stat: Stat;
+ constructor(stat: PermanentStat) {
+ const key = BaseStatBoosterModifierTypeGenerator.items[stat];
+ super("", key, (_type, args) => new Modifiers.BaseStatModifier(this, (args[0] as Pokemon).id, this.stat));
- constructor(localeName: string, stat: Stat) {
- super("", localeName.replace(/[ \-]/g, "_").toLowerCase(), (_type, args) => new Modifiers.PokemonBaseStatModifier(this, (args[0] as Pokemon).id, this.stat));
-
- this.localeName = localeName;
this.stat = stat;
+ this.key = key;
}
get name(): string {
- return i18next.t(`modifierType:BaseStatBoosterItem.${this.localeName.replace(/[ \-]/g, "_").toLowerCase()}`);
+ return i18next.t(`modifierType:BaseStatBoosterItem.${this.key}`);
}
- getDescription(scene: BattleScene): string {
- return i18next.t("modifierType:ModifierType.PokemonBaseStatBoosterModifierType.description", { statName: getStatName(this.stat) });
+ getDescription(_scene: BattleScene): string {
+ return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) });
}
getPregenArgs(): any[] {
@@ -922,6 +907,48 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
}
}
+class BaseStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
+ public static readonly items: Record = {
+ [Stat.HP]: "hp_up",
+ [Stat.ATK]: "protein",
+ [Stat.DEF]: "iron",
+ [Stat.SPATK]: "calcium",
+ [Stat.SPDEF]: "zinc",
+ [Stat.SPD]: "carbos"
+ };
+
+ constructor() {
+ super((_party: Pokemon[], pregenArgs?: any[]) => {
+ if (pregenArgs) {
+ return new BaseStatBoosterModifierType(pregenArgs[0]);
+ }
+ const randStat: PermanentStat = Utils.randSeedInt(Stat.SPD + 1);
+ return new BaseStatBoosterModifierType(randStat);
+ });
+ }
+}
+
+class TempStatStageBoosterModifierTypeGenerator extends ModifierTypeGenerator {
+ public static readonly items: Record = {
+ [Stat.ATK]: "x_attack",
+ [Stat.DEF]: "x_defense",
+ [Stat.SPATK]: "x_sp_atk",
+ [Stat.SPDEF]: "x_sp_def",
+ [Stat.SPD]: "x_speed",
+ [Stat.ACC]: "x_accuracy"
+ };
+
+ constructor() {
+ super((_party: Pokemon[], pregenArgs?: any[]) => {
+ if (pregenArgs && (pregenArgs.length === 1) && TEMP_BATTLE_STATS.includes(pregenArgs[0])) {
+ return new TempStatStageBoosterModifierType(pregenArgs[0]);
+ }
+ const randStat: TempBattleStat = Utils.randSeedInt(Stat.ACC, Stat.ATK);
+ return new TempStatStageBoosterModifierType(randStat);
+ });
+ }
+}
+
/**
* Modifier type generator for {@linkcode SpeciesStatBoosterModifierType}, which
* encapsulates the logic for weighting the most useful held item from
@@ -930,7 +957,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
*/
class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
/** Object comprised of the currently available species-based stat boosting held items */
- public static items = {
+ public static readonly items = {
LIGHT_BALL: { stats: [Stat.ATK, Stat.SPATK], multiplier: 2, species: [Species.PIKACHU] },
THICK_CLUB: { stats: [Stat.ATK], multiplier: 2, species: [Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK] },
METAL_POWDER: { stats: [Stat.DEF], multiplier: 2, species: [Species.DITTO] },
@@ -1043,7 +1070,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
}
class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
- constructor() {
+ constructor(rare: boolean) {
super((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) {
return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem);
@@ -1083,7 +1110,8 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
}
}
return formChangeItemTriggers;
- }).flat().flatMap(fc => fc.item))];
+ }).flat())
+ ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === rare);
// convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party.
if (!formChangeItemPool.length) {
@@ -1232,7 +1260,7 @@ export type GeneratorModifierOverride = {
type?: SpeciesStatBoosterItem;
}
| {
- name: keyof Pick;
+ name: keyof Pick;
type?: TempBattleStat;
}
| {
@@ -1282,7 +1310,8 @@ export const modifierTypes = {
EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(false),
RARE_EVOLUTION_ITEM: () => new EvolutionItemModifierTypeGenerator(true),
- FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(),
+ FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(false),
+ RARE_FORM_CHANGE_ITEM: () => new FormChangeItemModifierTypeGenerator(true),
MEGA_BRACELET: () => new ModifierType("modifierType:ModifierType.MEGA_BRACELET", "mega_bracelet", (type, _args) => new Modifiers.MegaEvolutionAccessModifier(type)),
DYNAMAX_BAND: () => new ModifierType("modifierType:ModifierType.DYNAMAX_BAND", "dynamax_band", (type, _args) => new Modifiers.GigantamaxAccessModifier(type)),
@@ -1304,7 +1333,7 @@ export const modifierTypes = {
SACRED_ASH: () => new AllPokemonFullReviveModifierType("modifierType:ModifierType.SACRED_ASH", "sacred_ash"),
REVIVER_SEED: () => new PokemonHeldItemModifierType("modifierType:ModifierType.REVIVER_SEED", "reviver_seed", (type, args) => new Modifiers.PokemonInstantReviveModifier(type, (args[0] as Pokemon).id)),
- WHITE_HERB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.WHITE_HERB", "white_herb", (type, args) => new Modifiers.PokemonResetNegativeStatStageModifier(type, (args[0] as Pokemon).id)),
+ WHITE_HERB: () => new PokemonHeldItemModifierType("modifierType:ModifierType.WHITE_HERB", "white_herb", (type, args) => new Modifiers.ResetNegativeStatStageModifier(type, (args[0] as Pokemon).id)),
ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.ETHER", "ether", 10),
MAX_ETHER: () => new PokemonPpRestoreModifierType("modifierType:ModifierType.MAX_ETHER", "max_ether", -1),
@@ -1325,23 +1354,15 @@ export const modifierTypes = {
SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(),
- TEMP_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
- if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in TempBattleStat)) {
- return new TempBattleStatBoosterModifierType(pregenArgs[0] as TempBattleStat);
- }
- const randTempBattleStat = Utils.randSeedInt(6) as TempBattleStat;
- return new TempBattleStatBoosterModifierType(randTempBattleStat);
- }),
- DIRE_HIT: () => new TempBattleStatBoosterModifierType(TempBattleStat.CRIT),
+ TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterModifierTypeGenerator(),
- BASE_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
- if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Stat)) {
- const stat = pregenArgs[0] as Stat;
- return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(stat), stat);
+ DIRE_HIT: () => new class extends ModifierType {
+ getDescription(_scene: BattleScene): string {
+ return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises") });
}
- const randStat = Utils.randSeedInt(6) as Stat;
- return new PokemonBaseStatBoosterModifierType(getBaseStatBoosterItemName(randStat), randStat);
- }),
+ }("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new Modifiers.TempCritBoosterModifier(type)),
+
+ BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(),
ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(),
@@ -1511,7 +1532,7 @@ const modifierPool: ModifierPool = {
return thresholdPartyMemberCount;
}, 3),
new WeightedModifierType(modifierTypes.LURE, skipInLastClassicWaveOrDefault(2)),
- new WeightedModifierType(modifierTypes.TEMP_STAT_BOOSTER, 4),
+ new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
new WeightedModifierType(modifierTypes.BERRY, 2),
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
].map(m => {
@@ -1595,6 +1616,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.PP_MAX, 3),
new WeightedModifierType(modifierTypes.MINT, 4),
new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32),
+ new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24),
new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)),
new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => {
if (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.unlocks[Unlockables.EVIOLITE]) {
@@ -1623,7 +1645,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT];
const weightMultiplier = party.filter(
- p => !p.getHeldItems().some(i => i instanceof Modifiers.PokemonResetNegativeStatStageModifier && i.stackCount >= i.getMaxHeldItemCount(p)) &&
+ p => !p.getHeldItems().some(i => i instanceof Modifiers.ResetNegativeStatStageModifier && i.stackCount >= i.getMaxHeldItemCount(p)) &&
(checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && selfStatLowerMoves.includes(m.moveId)))).length;
// If a party member has one of the above moves or abilities and doesn't have max herbs, the herb will appear more frequently
return 0 * (weightMultiplier ? 2 : 1) + (weightMultiplier ? weightMultiplier * 0 : 0);
@@ -1661,7 +1683,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
new WeightedModifierType(modifierTypes.LOCK_CAPSULE, skipInLastClassicWaveOrDefault(3)),
new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)),
- new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24),
+ new WeightedModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24),
new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36),
new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36),
new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3),
@@ -2222,7 +2244,7 @@ export class ModifierTypeOption {
}
export function getPartyLuckValue(party: Pokemon[]): integer {
- const luck = Phaser.Math.Clamp(party.map(p => p.isFainted() ? 0 : p.getLuck())
+ const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() : 0)
.reduce((total: integer, value: integer) => total += value, 0), 0, 14);
return luck || 0;
}
diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts
index 99f4540f493..84d8a1385af 100644
--- a/src/modifier/modifier.ts
+++ b/src/modifier/modifier.ts
@@ -3,16 +3,14 @@ import BattleScene from "../battle-scene";
import { getLevelTotalExp } from "../data/exp";
import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball";
import Pokemon, { PlayerPokemon } from "../field/pokemon";
-import { Stat } from "../data/pokemon-stat";
import { addTextObject, TextStyle } from "../ui/text";
import { Type } from "../data/type";
import { EvolutionPhase } from "../phases/evolution-phase";
import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions";
import { getPokemonNameWithAffix } from "../messages";
import * as Utils from "../utils";
-import { TempBattleStat } from "../data/temp-battle-stat";
import { getBerryEffectFunc, getBerryPredicate } from "../data/berry";
-import { BattlerTagType} from "#enums/battler-tag-type";
+import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
import { StatusEffect, getStatusEffectHealText } from "../data/status-effect";
import { achvs } from "../system/achv";
@@ -23,6 +21,7 @@ import Overrides from "#app/overrides";
import { ModifierType, modifierTypes } from "./modifier-type";
import { Command } from "#app/ui/command-ui-handler";
import { Species } from "#enums/species";
+import { Stat, type PermanentStat, type TempBattleStat, BATTLE_STATS, TEMP_BATTLE_STATS } from "#app/enums/stat";
import i18next from "i18next";
import { allMoves } from "#app/data/move";
@@ -362,41 +361,160 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier
}
}
-export class TempBattleStatBoosterModifier extends LapsingPersistentModifier {
- private tempBattleStat: TempBattleStat;
+/**
+ * Modifier used for party-wide items, specifically the X items, that
+ * temporarily increases the stat stage multiplier of the corresponding
+ * {@linkcode TempBattleStat}.
+ * @extends LapsingPersistentModifier
+ * @see {@linkcode apply}
+ */
+export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
+ private stat: TempBattleStat;
+ private multiplierBoost: number;
- constructor(type: ModifierTypes.TempBattleStatBoosterModifierType, tempBattleStat: TempBattleStat, battlesLeft?: integer, stackCount?: integer) {
- super(type, battlesLeft || 5, stackCount);
+ constructor(type: ModifierType, stat: TempBattleStat, battlesLeft?: number, stackCount?: number) {
+ super(type, battlesLeft ?? 5, stackCount);
- this.tempBattleStat = tempBattleStat;
+ this.stat = stat;
+ // Note that, because we want X Accuracy to maintain its original behavior,
+ // it will increment as it did previously, directly to the stat stage.
+ this.multiplierBoost = stat !== Stat.ACC ? 0.3 : 1;
}
match(modifier: Modifier): boolean {
- if (modifier instanceof TempBattleStatBoosterModifier) {
- return (modifier as TempBattleStatBoosterModifier).tempBattleStat === this.tempBattleStat
- && (modifier as TempBattleStatBoosterModifier).battlesLeft === this.battlesLeft;
+ if (modifier instanceof TempStatStageBoosterModifier) {
+ const modifierInstance = modifier as TempStatStageBoosterModifier;
+ return (modifierInstance.stat === this.stat);
}
return false;
}
- clone(): TempBattleStatBoosterModifier {
- return new TempBattleStatBoosterModifier(this.type as ModifierTypes.TempBattleStatBoosterModifierType, this.tempBattleStat, this.battlesLeft, this.stackCount);
+ clone() {
+ return new TempStatStageBoosterModifier(this.type, this.stat, this.battlesLeft, this.stackCount);
}
getArgs(): any[] {
- return [ this.tempBattleStat, this.battlesLeft ];
+ return [ this.stat, this.battlesLeft ];
}
- apply(args: any[]): boolean {
- const tempBattleStat = args[0] as TempBattleStat;
+ /**
+ * Checks if {@linkcode args} contains the necessary elements and if the
+ * incoming stat is matches {@linkcode stat}.
+ * @param args [0] {@linkcode TempBattleStat} being checked at the time
+ * [1] {@linkcode Utils.NumberHolder} N/A
+ * @returns true if the modifier can be applied, false otherwise
+ */
+ shouldApply(args: any[]): boolean {
+ return args && (args.length === 2) && TEMP_BATTLE_STATS.includes(args[0]) && (args[0] === this.stat) && (args[1] instanceof Utils.NumberHolder);
+ }
- if (tempBattleStat === this.tempBattleStat) {
- const statLevel = args[1] as Utils.IntegerHolder;
- statLevel.value = Math.min(statLevel.value + 1, 6);
- return true;
+ /**
+ * Increases the incoming stat stage matching {@linkcode stat} by {@linkcode multiplierBoost}.
+ * @param args [0] {@linkcode TempBattleStat} N/A
+ * [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier
+ */
+ apply(args: any[]): boolean {
+ (args[1] as Utils.NumberHolder).value += this.multiplierBoost;
+ return true;
+ }
+
+ /**
+ * Goes through existing modifiers for any that match the selected modifier,
+ * which will then either add it to the existing modifiers if none were found
+ * or, if one was found, it will refresh {@linkcode battlesLeft}.
+ * @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
+ * @param _virtual N/A
+ * @param _scene N/A
+ * @returns true if the modifier was successfully added or applied, false otherwise
+ */
+ add(modifiers: PersistentModifier[], _virtual: boolean, _scene: BattleScene): boolean {
+ for (const modifier of modifiers) {
+ if (this.match(modifier)) {
+ const modifierInstance = modifier as TempStatStageBoosterModifier;
+ if (modifierInstance.getBattlesLeft() < 5) {
+ modifierInstance.battlesLeft = 5;
+ return true;
+ }
+ // should never get here
+ return false;
+ }
}
- return false;
+ modifiers.push(this);
+ return true;
+ }
+
+ getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
+ return 1;
+ }
+}
+
+/**
+ * Modifier used for party-wide items, namely Dire Hit, that
+ * temporarily increments the critical-hit stage
+ * @extends LapsingPersistentModifier
+ * @see {@linkcode apply}
+ */
+export class TempCritBoosterModifier extends LapsingPersistentModifier {
+ constructor(type: ModifierType, battlesLeft?: integer, stackCount?: number) {
+ super(type, battlesLeft || 5, stackCount);
+ }
+
+ clone() {
+ return new TempCritBoosterModifier(this.type, this.stackCount);
+ }
+
+ match(modifier: Modifier): boolean {
+ return (modifier instanceof TempCritBoosterModifier);
+ }
+
+ /**
+ * Checks if {@linkcode args} contains the necessary elements.
+ * @param args [1] {@linkcode Utils.NumberHolder} N/A
+ * @returns true if the critical-hit stage boost applies successfully
+ */
+ shouldApply(args: any[]): boolean {
+ return args && (args.length === 1) && (args[0] instanceof Utils.NumberHolder);
+ }
+
+ /**
+ * Increases the current critical-hit stage value by 1.
+ * @param args [0] {@linkcode Utils.IntegerHolder} that holds the resulting critical-hit level
+ * @returns true if the critical-hit stage boost applies successfully
+ */
+ apply(args: any[]): boolean {
+ (args[0] as Utils.NumberHolder).value++;
+ return true;
+ }
+
+ /**
+ * Goes through existing modifiers for any that match the selected modifier,
+ * which will then either add it to the existing modifiers if none were found
+ * or, if one was found, it will refresh {@linkcode battlesLeft}.
+ * @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
+ * @param _virtual N/A
+ * @param _scene N/A
+ * @returns true if the modifier was successfully added or applied, false otherwise
+ */
+ add(modifiers: PersistentModifier[], _virtual: boolean, _scene: BattleScene): boolean {
+ for (const modifier of modifiers) {
+ if (this.match(modifier)) {
+ const modifierInstance = modifier as TempCritBoosterModifier;
+ if (modifierInstance.getBattlesLeft() < 5) {
+ modifierInstance.battlesLeft = 5;
+ return true;
+ }
+ // should never get here
+ return false;
+ }
+ }
+
+ modifiers.push(this);
+ return true;
+ }
+
+ getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
+ return 1;
}
}
@@ -663,24 +781,30 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
}
}
-export class PokemonBaseStatModifier extends PokemonHeldItemModifier {
- protected stat: Stat;
+/**
+ * Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that
+ * increase the value of a given {@linkcode PermanentStat}.
+ * @extends LapsingPersistentModifier
+ * @see {@linkcode apply}
+ */
+export class BaseStatModifier extends PokemonHeldItemModifier {
+ protected stat: PermanentStat;
readonly isTransferrable: boolean = false;
- constructor(type: ModifierTypes.PokemonBaseStatBoosterModifierType, pokemonId: integer, stat: Stat, stackCount?: integer) {
+ constructor(type: ModifierType, pokemonId: integer, stat: PermanentStat, stackCount?: integer) {
super(type, pokemonId, stackCount);
this.stat = stat;
}
matchType(modifier: Modifier): boolean {
- if (modifier instanceof PokemonBaseStatModifier) {
- return (modifier as PokemonBaseStatModifier).stat === this.stat;
+ if (modifier instanceof BaseStatModifier) {
+ return (modifier as BaseStatModifier).stat === this.stat;
}
return false;
}
clone(): PersistentModifier {
- return new PokemonBaseStatModifier(this.type as ModifierTypes.PokemonBaseStatBoosterModifierType, this.pokemonId, this.stat, this.stackCount);
+ return new BaseStatModifier(this.type, this.pokemonId, this.stat, this.stackCount);
}
getArgs(): any[] {
@@ -688,12 +812,12 @@ export class PokemonBaseStatModifier extends PokemonHeldItemModifier {
}
shouldApply(args: any[]): boolean {
- return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
+ return super.shouldApply(args) && args.length === 2 && Array.isArray(args[1]);
}
apply(args: any[]): boolean {
- args[1][this.stat] = Math.min(Math.floor(args[1][this.stat] * (1 + this.getStackCount() * 0.1)), 999999);
-
+ const baseStats = args[1] as number[];
+ baseStats[this.stat] = Math.floor(baseStats[this.stat] * (1 + this.getStackCount() * 0.1));
return true;
}
@@ -1398,42 +1522,48 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
}
/**
- * Modifier used for White Herb, which resets negative {@linkcode Stat} changes
+ * Modifier used for held items, namely White Herb, that restore adverse stat
+ * stages in battle.
* @extends PokemonHeldItemModifier
* @see {@linkcode apply}
*/
-export class PokemonResetNegativeStatStageModifier extends PokemonHeldItemModifier {
+export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier {
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
}
matchType(modifier: Modifier) {
- return modifier instanceof PokemonResetNegativeStatStageModifier;
+ return modifier instanceof ResetNegativeStatStageModifier;
}
clone() {
- return new PokemonResetNegativeStatStageModifier(this.type, this.pokemonId, this.stackCount);
+ return new ResetNegativeStatStageModifier(this.type, this.pokemonId, this.stackCount);
}
/**
- * Restores any negative stat stages of the mon to 0
- * @param args args[0] is the {@linkcode Pokemon} whose stat stages are being checked
- * @returns true if any stat changes were applied (item was used), false otherwise
+ * Goes through the holder's stat stages and, if any are negative, resets that
+ * stat stage back to 0.
+ * @param args [0] {@linkcode Pokemon} that holds the held item
+ * @returns true if any stat stages were reset, false otherwise
*/
apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon;
- const loweredStats = pokemon.summonData.battleStats.filter(s => s < 0);
- if (loweredStats.length) {
- for (let s = 0; s < pokemon.summonData.battleStats.length; s++) {
- pokemon.summonData.battleStats[s] = Math.max(0, pokemon.summonData.battleStats[s]);
+ let statRestored = false;
+
+ for (const s of BATTLE_STATS) {
+ if (pokemon.getStatStage(s) < 0) {
+ pokemon.setStatStage(s, 0);
+ statRestored = true;
}
- pokemon.scene.queueMessage(i18next.t("modifier:pokemonResetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }));
- return true;
}
- return false;
+
+ if (statRestored) {
+ pokemon.scene.queueMessage(i18next.t("modifier:resetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }));
+ }
+ return statRestored;
}
- getMaxHeldItemCount(pokemon: Pokemon): integer {
+ getMaxHeldItemCount(_pokemon: Pokemon): integer {
return 2;
}
}
@@ -1625,7 +1755,7 @@ export class TmModifier extends ConsumablePokemonModifier {
apply(args: any[]): boolean {
const pokemon = args[0] as PlayerPokemon;
- pokemon.scene.unshiftPhase(new LearnMovePhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), (this.type as ModifierTypes.TmModifierType).moveId));
+ pokemon.scene.unshiftPhase(new LearnMovePhase(pokemon.scene, pokemon.scene.getParty().indexOf(pokemon), (this.type as ModifierTypes.TmModifierType).moveId, true));
return true;
}
@@ -2193,7 +2323,7 @@ export class ShinyRateBoosterModifier extends PersistentModifier {
}
apply(args: any[]): boolean {
- (args[0] as Utils.IntegerHolder).value *= Math.pow(2, 2 + this.getStackCount());
+ (args[0] as Utils.IntegerHolder).value *= Math.pow(2, 1 + this.getStackCount());
return true;
}
@@ -2745,7 +2875,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier {
* - The player
* - The enemy
* @param scene current {@linkcode BattleScene}
- * @param isPlayer {@linkcode boolean} for whether the the player (`true`) or enemy (`false`) is being overridden
+ * @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden
*/
export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): void {
const modifiersOverride: ModifierTypes.ModifierOverride[] = isPlayer ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE;
@@ -2760,13 +2890,22 @@ export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true):
modifiersOverride.forEach(item => {
const modifierFunc = modifierTypes[item.name];
- const modifier = modifierFunc().withIdFromFunc(modifierFunc).newModifier() as PersistentModifier;
- modifier.stackCount = item.count || 1;
+ let modifierType: ModifierType | null = modifierFunc();
- if (isPlayer) {
- scene.addModifier(modifier, true, false, false, true);
- } else {
- scene.addEnemyModifier(modifier, true, true);
+ if (modifierType instanceof ModifierTypes.ModifierTypeGenerator) {
+ const pregenArgs = ("type" in item) && (item.type !== null) ? [item.type] : undefined;
+ modifierType = modifierType.generateType([], pregenArgs);
+ }
+
+ const modifier = modifierType && modifierType.withIdFromFunc(modifierFunc).newModifier() as PersistentModifier;
+ if (modifier) {
+ modifier.stackCount = item.count || 1;
+
+ if (isPlayer) {
+ scene.addModifier(modifier, true, false, false, true);
+ } else {
+ scene.addEnemyModifier(modifier, true, true);
+ }
}
});
}
diff --git a/src/overrides.ts b/src/overrides.ts
index 32ff116f41d..48c118b55bc 100644
--- a/src/overrides.ts
+++ b/src/overrides.ts
@@ -116,6 +116,14 @@ class DefaultOverrides {
readonly OPP_VARIANT_OVERRIDE: Variant = 0;
readonly OPP_IVS_OVERRIDE: number | number[] = [];
readonly OPP_FORM_OVERRIDES: Partial> = {};
+ /**
+ * Override to give the enemy Pokemon a given amount of health segments
+ *
+ * 0 (default): the health segments will be handled normally based on wave, level and species
+ * 1: the Pokemon will have a single health segment and therefore will not be a boss
+ * 2+: the Pokemon will be a boss with the given number of health segments
+ */
+ readonly OPP_HEALTH_SEGMENTS_OVERRIDE: number = 0;
// -------------
// EGG OVERRIDES
diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts
index a9999370cdd..06315668a8b 100644
--- a/src/phases/battle-end-phase.ts
+++ b/src/phases/battle-end-phase.ts
@@ -23,12 +23,6 @@ export class BattleEndPhase extends BattlePhase {
this.scene.unshiftPhase(new GameOverPhase(this.scene, true));
}
- for (const pokemon of this.scene.getField()) {
- if (pokemon) {
- pokemon.resetBattleSummonData();
- }
- }
-
for (const pokemon of this.scene.getParty().filter(p => p.isAllowedInBattle())) {
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon);
}
diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts
index 68ede826d95..9681a6eeee8 100644
--- a/src/phases/command-phase.ts
+++ b/src/phases/command-phase.ts
@@ -1,21 +1,18 @@
-import BattleScene from "#app/battle-scene.js";
-import { TurnCommand, BattleType } from "#app/battle.js";
-import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js";
-import { TrappedTag, EncoreTag } from "#app/data/battler-tags.js";
-import { MoveTargetSet, getMoveTargets } from "#app/data/move.js";
-import { speciesStarters } from "#app/data/pokemon-species.js";
-import { Type } from "#app/data/type.js";
-import { Abilities } from "#app/enums/abilities.js";
-import { BattlerTagType } from "#app/enums/battler-tag-type.js";
-import { Biome } from "#app/enums/biome.js";
-import { Moves } from "#app/enums/moves.js";
-import { PokeballType } from "#app/enums/pokeball.js";
-import { FieldPosition, PlayerPokemon } from "#app/field/pokemon.js";
-import { getPokemonNameWithAffix } from "#app/messages.js";
-import { Command } from "#app/ui/command-ui-handler.js";
-import { Mode } from "#app/ui/ui.js";
+import BattleScene from "#app/battle-scene";
+import { TurnCommand, BattleType } from "#app/battle";
+import { TrappedTag, EncoreTag } from "#app/data/battler-tags";
+import { MoveTargetSet, getMoveTargets } from "#app/data/move";
+import { speciesStarters } from "#app/data/pokemon-species";
+import { Abilities } from "#app/enums/abilities";
+import { BattlerTagType } from "#app/enums/battler-tag-type";
+import { Biome } from "#app/enums/biome";
+import { Moves } from "#app/enums/moves";
+import { PokeballType } from "#app/enums/pokeball";
+import { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
+import { getPokemonNameWithAffix } from "#app/messages";
+import { Command } from "#app/ui/command-ui-handler";
+import { Mode } from "#app/ui/ui";
import i18next from "i18next";
-import * as Utils from "#app/utils.js";
import { FieldPhase } from "./field-phase";
import { SelectTargetPhase } from "./select-target-phase";
@@ -77,7 +74,6 @@ export class CommandPhase extends FieldPhase {
handleCommand(command: Command, cursor: integer, ...args: any[]): boolean {
const playerPokemon = this.scene.getPlayerField()[this.fieldIndex];
- const enemyField = this.scene.getEnemyField();
let success: boolean;
switch (command) {
@@ -184,14 +180,9 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else {
- const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
- const trapped = new Utils.BooleanHolder(false);
const batonPass = isSwitch && args[0] as boolean;
const trappedAbMessages: string[] = [];
- if (!batonPass) {
- enemyField.forEach(enemyPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trapped, playerPokemon, trappedAbMessages, true));
- }
- if (batonPass || (!trapTag && !trapped.value)) {
+ if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };
@@ -199,14 +190,27 @@ export class CommandPhase extends FieldPhase {
if (!isSwitch && this.fieldIndex) {
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
}
- } else if (trapTag) {
- if (trapTag.sourceMove === Moves.INGRAIN && trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId)?.isOfType(Type.GHOST)) {
- success = true;
+ } else if (trappedAbMessages.length > 0) {
+ if (!isSwitch) {
+ this.scene.ui.setMode(Mode.MESSAGE);
+ }
+ this.scene.ui.showText(trappedAbMessages[0], null, () => {
+ this.scene.ui.showText("", 0);
+ if (!isSwitch) {
+ this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
+ }
+ }, null, true);
+ } else {
+ const trapTag = playerPokemon.getTag(TrappedTag);
+
+ // trapTag should be defined at this point, but just in case...
+ if (!trapTag) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };
break;
}
+
if (!isSwitch) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
@@ -224,16 +228,6 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}
}, null, true);
- } else if (trapped.value && trappedAbMessages.length > 0) {
- if (!isSwitch) {
- this.scene.ui.setMode(Mode.MESSAGE);
- }
- this.scene.ui.showText(trappedAbMessages[0], null, () => {
- this.scene.ui.showText("", 0);
- if (!isSwitch) {
- this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
- }
- }, null, true);
}
}
break;
diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts
index a5b0252d4de..4b03aa62f02 100644
--- a/src/phases/egg-hatch-phase.ts
+++ b/src/phases/egg-hatch-phase.ts
@@ -1,23 +1,29 @@
-import BattleScene, { AnySound } from "#app/battle-scene.js";
-import { Egg, EGG_SEED } from "#app/data/egg.js";
-import { EggCountChangedEvent } from "#app/events/egg.js";
-import { PlayerPokemon } from "#app/field/pokemon.js";
-import { getPokemonNameWithAffix } from "#app/messages.js";
-import { Phase } from "#app/phase.js";
-import { achvs } from "#app/system/achv.js";
-import EggCounterContainer from "#app/ui/egg-counter-container.js";
-import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler.js";
-import PokemonInfoContainer from "#app/ui/pokemon-info-container.js";
-import { Mode } from "#app/ui/ui.js";
+import BattleScene, { AnySound } from "#app/battle-scene";
+import { Egg } from "#app/data/egg";
+import { EggCountChangedEvent } from "#app/events/egg";
+import { PlayerPokemon } from "#app/field/pokemon";
+import { getPokemonNameWithAffix } from "#app/messages";
+import { Phase } from "#app/phase";
+import { achvs } from "#app/system/achv";
+import EggCounterContainer from "#app/ui/egg-counter-container";
+import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
+import PokemonInfoContainer from "#app/ui/pokemon-info-container";
+import { Mode } from "#app/ui/ui";
import i18next from "i18next";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
-import * as Utils from "#app/utils.js";
+import * as Utils from "#app/utils";
+import { EggLapsePhase } from "./egg-lapse-phase";
+import { EggHatchData } from "#app/data/egg-hatch-data";
+
+
/**
* Class that represents egg hatching
*/
export class EggHatchPhase extends Phase {
/** The egg that is hatching */
private egg: Egg;
+ /** The new EggHatchData for the egg/pokemon that hatches */
+ private eggHatchData: EggHatchData;
/** The number of eggs that are hatching */
private eggsToHatchCount: integer;
@@ -58,10 +64,11 @@ export class EggHatchPhase extends Phase {
private skipped: boolean;
/** The sound effect being played when the egg is hatched */
private evolutionBgm: AnySound;
+ private eggLapsePhase: EggLapsePhase;
- constructor(scene: BattleScene, egg: Egg, eggsToHatchCount: integer) {
+ constructor(scene: BattleScene, hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: integer) {
super(scene);
-
+ this.eggLapsePhase = hatchScene;
this.egg = egg;
this.eggsToHatchCount = eggsToHatchCount;
}
@@ -307,6 +314,7 @@ export class EggHatchPhase extends Phase {
* Function to do the logic and animation of completing a hatch and revealing the Pokemon
*/
doReveal(): void {
+ // set the previous dex data so info container can show new unlocks in egg summary
const isShiny = this.pokemon.isShiny();
if (this.pokemon.species.subLegendary) {
this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY);
@@ -345,13 +353,13 @@ export class EggHatchPhase extends Phase {
this.scene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: getPokemonNameWithAffix(this.pokemon) }), null, () => {
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
- this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
+ this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then((value) => {
+ this.eggHatchData.setEggMoveUnlocked(value);
this.scene.ui.showText("", 0);
this.end();
});
});
}, null, true, 3000);
- //this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm());
});
});
this.scene.tweens.add({
@@ -435,17 +443,11 @@ export class EggHatchPhase extends Phase {
/**
* Generates a Pokemon to be hatched by the egg
+ * Also stores the generated pokemon in this.eggHatchData
* @returns the hatched PlayerPokemon
*/
generatePokemon(): PlayerPokemon {
- let ret: PlayerPokemon;
-
- this.scene.executeWithSeedOffset(() => {
- ret = this.egg.generatePlayerPokemon(this.scene);
- this.eggMoveIndex = this.egg.eggMoveIndex;
-
- }, this.egg.id, EGG_SEED.toString());
-
- return ret!;
+ this.eggHatchData = this.eggLapsePhase.generatePokemon(this.egg);
+ return this.eggHatchData.pokemon;
}
}
diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts
index 50d7106f229..1adb1568166 100644
--- a/src/phases/egg-lapse-phase.ts
+++ b/src/phases/egg-lapse-phase.ts
@@ -1,11 +1,23 @@
-import BattleScene from "#app/battle-scene.js";
-import { Egg } from "#app/data/egg.js";
-import { Phase } from "#app/phase.js";
+import BattleScene from "#app/battle-scene";
+import { Egg, EGG_SEED } from "#app/data/egg";
+import { Phase } from "#app/phase";
import i18next from "i18next";
import Overrides from "#app/overrides";
import { EggHatchPhase } from "./egg-hatch-phase";
+import { Mode } from "#app/ui/ui";
+import { achvs } from "#app/system/achv";
+import { PlayerPokemon } from "#app/field/pokemon";
+import { EggSummaryPhase } from "./egg-summary-phase";
+import { EggHatchData } from "#app/data/egg-hatch-data";
+/**
+ * Phase that handles updating eggs, and hatching any ready eggs
+ * Also handles prompts for skipping animation, and calling the egg summary phase
+ */
export class EggLapsePhase extends Phase {
+
+ private eggHatchData: EggHatchData[] = [];
+ private readonly minEggsToPromptSkip: number = 5;
constructor(scene: BattleScene) {
super(scene);
}
@@ -16,20 +28,111 @@ export class EggLapsePhase extends Phase {
const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => {
return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1;
});
+ const eggsToHatchCount: number = eggsToHatch.length;
+ this.eggHatchData= [];
- let eggCount: integer = eggsToHatch.length;
+ if (eggsToHatchCount > 0) {
- if (eggCount) {
- this.scene.queueMessage(i18next.t("battle:eggHatching"));
-
- for (const egg of eggsToHatch) {
- this.scene.unshiftPhase(new EggHatchPhase(this.scene, egg, eggCount));
- if (eggCount > 0) {
- eggCount--;
- }
+ if (eggsToHatchCount >= this.minEggsToPromptSkip) {
+ this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
+ // show prompt for skip
+ this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
+ this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
+ this.hatchEggsSkipped(eggsToHatch);
+ this.showSummary();
+ }, () => {
+ this.hatchEggsRegular(eggsToHatch);
+ this.showSummary();
+ }
+ );
+ }, 100, true);
+ } else {
+ // regular hatches, no summary
+ this.scene.queueMessage(i18next.t("battle:eggHatching"));
+ this.hatchEggsRegular(eggsToHatch);
+ this.end();
}
-
+ } else {
+ this.end();
}
+ }
+
+ /**
+ * Hatches eggs normally one by one, showing animations
+ * @param eggsToHatch list of eggs to hatch
+ */
+ hatchEggsRegular(eggsToHatch: Egg[]) {
+ let eggsToHatchCount: number = eggsToHatch.length;
+ for (const egg of eggsToHatch) {
+ this.scene.unshiftPhase(new EggHatchPhase(this.scene, this, egg, eggsToHatchCount));
+ eggsToHatchCount--;
+ }
+ }
+
+ /**
+ * Hatches eggs with no animations
+ * @param eggsToHatch list of eggs to hatch
+ */
+ hatchEggsSkipped(eggsToHatch: Egg[]) {
+ for (const egg of eggsToHatch) {
+ this.hatchEggSilently(egg);
+ }
+ }
+
+ showSummary() {
+ this.scene.unshiftPhase(new EggSummaryPhase(this.scene, this.eggHatchData));
this.end();
}
+
+ /**
+ * Hatches an egg and stores it in the local EggHatchData array without animations
+ * Also validates the achievements for the hatched pokemon and removes the egg
+ * @param egg egg to hatch
+ */
+ hatchEggSilently(egg: Egg) {
+ const eggIndex = this.scene.gameData.eggs.findIndex(e => e.id === egg.id);
+ if (eggIndex === -1) {
+ return this.end();
+ }
+ this.scene.gameData.eggs.splice(eggIndex, 1);
+
+ const data = this.generatePokemon(egg);
+ const pokemon = data.pokemon;
+ if (pokemon.fusionSpecies) {
+ pokemon.clearFusionSpecies();
+ }
+
+ if (pokemon.species.subLegendary) {
+ this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY);
+ }
+ if (pokemon.species.legendary) {
+ this.scene.validateAchv(achvs.HATCH_LEGENDARY);
+ }
+ if (pokemon.species.mythical) {
+ this.scene.validateAchv(achvs.HATCH_MYTHICAL);
+ }
+ if (pokemon.isShiny()) {
+ this.scene.validateAchv(achvs.HATCH_SHINY);
+ }
+
+ }
+
+ /**
+ * Generates a Pokemon and creates a new EggHatchData instance for the given egg
+ * @param egg the egg to hatch
+ * @returns the hatched PlayerPokemon
+ */
+ generatePokemon(egg: Egg): EggHatchData {
+ let ret: PlayerPokemon;
+ let newHatchData: EggHatchData;
+ this.scene.executeWithSeedOffset(() => {
+ ret = egg.generatePlayerPokemon(this.scene);
+ newHatchData = new EggHatchData(this.scene, ret, egg.eggMoveIndex);
+ newHatchData.setDex();
+ this.eggHatchData.push(newHatchData);
+
+ }, egg.id, EGG_SEED.toString());
+ return newHatchData!;
+ }
+
}
diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts
new file mode 100644
index 00000000000..190af17c724
--- /dev/null
+++ b/src/phases/egg-summary-phase.ts
@@ -0,0 +1,50 @@
+import BattleScene from "#app/battle-scene";
+import { Phase } from "#app/phase";
+import { Mode } from "#app/ui/ui";
+import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
+import { EggHatchData } from "#app/data/egg-hatch-data";
+
+/**
+ * Class that represents the egg summary phase
+ * It does some of the function for updating egg data
+ * Phase is handled mostly by the egg-hatch-scene-handler UI
+ */
+export class EggSummaryPhase extends Phase {
+ private eggHatchData: EggHatchData[];
+ private eggHatchHandler: EggHatchSceneHandler;
+
+ constructor(scene: BattleScene, eggHatchData: EggHatchData[]) {
+ super(scene);
+ this.eggHatchData = eggHatchData;
+ }
+
+ start() {
+ super.start();
+
+ // updates next pokemon once the current update has been completed
+ const updateNextPokemon = (i: number) => {
+ if (i >= this.eggHatchData.length) {
+ this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => {
+ this.scene.fadeOutBgm(undefined, false);
+ this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
+ });
+
+ } else {
+ this.eggHatchData[i].setDex();
+ this.eggHatchData[i].updatePokemon().then(() => {
+ if (i < this.eggHatchData.length) {
+ updateNextPokemon(i + 1);
+ }
+ });
+ }
+ };
+ updateNextPokemon(0);
+
+ }
+
+ end() {
+ this.eggHatchHandler.clear();
+ this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => {});
+ super.end();
+ }
+}
diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts
index 07bfd72a8bf..3f37095569a 100644
--- a/src/phases/encounter-phase.ts
+++ b/src/phases/encounter-phase.ts
@@ -26,6 +26,7 @@ import { ScanIvsPhase } from "./scan-ivs-phase";
import { ShinySparklePhase } from "./shiny-sparkle-phase";
import { SummonPhase } from "./summon-phase";
import { ToggleDoublePositionPhase } from "./toggle-double-position-phase";
+import Overrides from "#app/overrides";
export class EncounterPhase extends BattlePhase {
private loaded: boolean;
@@ -112,10 +113,11 @@ export class EncounterPhase extends BattlePhase {
if (battle.battleType === BattleType.TRAINER) {
loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct?
} else {
- // This block only applies for double battles to init the boss segments (idk why it's split up like this)
- if (battle.enemyParty.filter(p => p.isBoss()).length > 1) {
+ const overridedBossSegments = Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1;
+ // for double battles, reduce the health segments for boss Pokemon unless there is an override
+ if (!overridedBossSegments && battle.enemyParty.filter(p => p.isBoss()).length > 1) {
for (const enemyPokemon of battle.enemyParty) {
- // If the enemy pokemon is a boss and wasn't populated from data source, then set it up
+ // If the enemy pokemon is a boss and wasn't populated from data source, then update the number of segments
if (enemyPokemon.isBoss() && !enemyPokemon.isPopulatedFromDataSource) {
enemyPokemon.setBoss(true, Math.ceil(enemyPokemon.bossSegments * (enemyPokemon.getSpeciesForm().baseTotal / totalBst)));
enemyPokemon.initBattleInfo();
@@ -159,9 +161,11 @@ export class EncounterPhase extends BattlePhase {
return this.scene.reset(true);
}
this.doEncounter();
+ this.scene.resetSeed();
});
} else {
this.doEncounter();
+ this.scene.resetSeed();
}
});
});
diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts
index 5277b2666c7..91ee0456cd4 100644
--- a/src/phases/enemy-command-phase.ts
+++ b/src/phases/enemy-command-phase.ts
@@ -1,9 +1,6 @@
-import BattleScene from "#app/battle-scene.js";
-import { BattlerIndex } from "#app/battle.js";
-import { applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "#app/data/ability.js";
-import { TrappedTag } from "#app/data/battler-tags.js";
-import { Command } from "#app/ui/command-ui-handler.js";
-import * as Utils from "#app/utils.js";
+import BattleScene from "#app/battle-scene";
+import { BattlerIndex } from "#app/battle";
+import { Command } from "#app/ui/command-ui-handler";
import { FieldPhase } from "./field-phase";
/**
@@ -45,10 +42,7 @@ export class EnemyCommandPhase extends FieldPhase {
if (trainer && !enemyPokemon.getMoveQueue().length) {
const opponents = enemyPokemon.getOpponents();
- const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
- const trapped = new Utils.BooleanHolder(false);
- opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped, enemyPokemon, [""], true));
- if (!trapTag && !trapped.value) {
+ if (!enemyPokemon.isTrapped()) {
const partyMemberScores = trainer.getPartyMemberMatchupScores(enemyPokemon.trainerSlot, true);
if (partyMemberScores.length) {
@@ -83,4 +77,8 @@ export class EnemyCommandPhase extends FieldPhase {
this.end();
}
+
+ getFieldIndex(): number {
+ return this.fieldIndex;
+ }
}
diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts
index 66946d268cb..d5dd9f61340 100644
--- a/src/phases/faint-phase.ts
+++ b/src/phases/faint-phase.ts
@@ -1,14 +1,14 @@
-import BattleScene from "#app/battle-scene.js";
-import { BattlerIndex, BattleType } from "#app/battle.js";
-import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability.js";
-import { BattlerTagLapseType } from "#app/data/battler-tags.js";
-import { battleSpecDialogue } from "#app/data/dialogue.js";
-import { allMoves, PostVictoryStatChangeAttr } from "#app/data/move.js";
-import { BattleSpec } from "#app/enums/battle-spec.js";
-import { StatusEffect } from "#app/enums/status-effect.js";
-import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon.js";
-import { getPokemonNameWithAffix } from "#app/messages.js";
-import { PokemonInstantReviveModifier } from "#app/modifier/modifier.js";
+import BattleScene from "#app/battle-scene";
+import { BattlerIndex, BattleType } from "#app/battle";
+import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability";
+import { BattlerTagLapseType } from "#app/data/battler-tags";
+import { battleSpecDialogue } from "#app/data/dialogue";
+import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move";
+import { BattleSpec } from "#app/enums/battle-spec";
+import { StatusEffect } from "#app/enums/status-effect";
+import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon";
+import { getPokemonNameWithAffix } from "#app/messages";
+import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
import i18next from "i18next";
import { DamagePhase } from "./damage-phase";
import { PokemonPhase } from "./pokemon-phase";
@@ -72,7 +72,7 @@ export class FaintPhase extends PokemonPhase {
if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
- const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr);
+ const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr);
if (pvattrs.length) {
for (const pvattr of pvattrs) {
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);
diff --git a/src/phases/field-phase.ts b/src/phases/field-phase.ts
index a9622271f14..b65e903a32b 100644
--- a/src/phases/field-phase.ts
+++ b/src/phases/field-phase.ts
@@ -1,42 +1,9 @@
-import { BattlerIndex } from "#app/battle.js";
-import { TrickRoomTag } from "#app/data/arena-tag.js";
-import { Stat } from "#app/enums/stat.js";
import Pokemon from "#app/field/pokemon.js";
import { BattlePhase } from "./battle-phase";
-import * as Utils from "#app/utils.js";
type PokemonFunc = (pokemon: Pokemon) => void;
export abstract class FieldPhase extends BattlePhase {
- getOrder(): BattlerIndex[] {
- const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
- const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
-
- // We shuffle the list before sorting so speed ties produce random results
- let orderedTargets: Pokemon[] = playerField.concat(enemyField);
- // We seed it with the current turn to prevent an inconsistency where it
- // was varying based on how long since you last reloaded
- this.scene.executeWithSeedOffset(() => {
- orderedTargets = Utils.randSeedShuffle(orderedTargets);
- }, this.scene.currentBattle.turn, this.scene.waveSeed);
-
- orderedTargets.sort((a: Pokemon, b: Pokemon) => {
- const aSpeed = a?.getBattleStat(Stat.SPD) || 0;
- const bSpeed = b?.getBattleStat(Stat.SPD) || 0;
-
- return bSpeed - aSpeed;
- });
-
- const speedReversed = new Utils.BooleanHolder(false);
- this.scene.arena.applyTags(TrickRoomTag, speedReversed);
-
- if (speedReversed.value) {
- orderedTargets = orderedTargets.reverse();
- }
-
- return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : 0));
- }
-
executeForAll(func: PokemonFunc): void {
const field = this.scene.getField(true).filter(p => p.summonData);
field.forEach(pokemon => func(pokemon));
diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts
index 5a9a16b6f5e..201019e8860 100644
--- a/src/phases/learn-move-phase.ts
+++ b/src/phases/learn-move-phase.ts
@@ -12,11 +12,13 @@ import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-pha
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
private moveId: Moves;
+ private fromTM: boolean;
- constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) {
+ constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, fromTM?: boolean) {
super(scene, partyMemberIndex);
this.moveId = moveId;
+ this.fromTM = fromTM ?? false;
}
start() {
@@ -41,6 +43,9 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
if (emptyMoveIndex > -1) {
pokemon.setMove(emptyMoveIndex, this.moveId);
+ if (this.fromTM) {
+ pokemon.usedTMs.push(this.moveId);
+ }
initMoveAnim(this.scene, this.moveId).then(() => {
loadMoveAnimAssets(this.scene, [this.moveId], true)
.then(() => {
@@ -85,6 +90,9 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => {
this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: pokemon.moveset[moveIndex]!.getName() }), null, () => { // TODO: is the bang correct?
this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => {
+ if (this.fromTM) {
+ pokemon.usedTMs.push(this.moveId);
+ }
pokemon.setMove(moveIndex, Moves.NONE);
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId));
this.end();
diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts
index c446660b16f..e2893d587a7 100644
--- a/src/phases/move-phase.ts
+++ b/src/phases/move-phase.ts
@@ -1,9 +1,9 @@
import BattleScene from "#app/battle-scene.js";
import { BattlerIndex } from "#app/battle.js";
-import { applyAbAttrs, RedirectMoveAbAttr, BlockRedirectAbAttr, IncreasePpAbAttr, applyPreAttackAbAttrs, PokemonTypeChangeAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr } from "#app/data/ability.js";
+import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr } from "#app/data/ability.js";
import { CommonAnim } from "#app/data/battle-anims.js";
-import { CenterOfAttentionTag, BattlerTagLapseType } from "#app/data/battler-tags.js";
-import { MoveFlags, BypassRedirectAttr, allMoves, CopyMoveAttr, applyMoveAttrs, BypassSleepAttr, HealStatusEffectAttr, ChargeAttr, PreMoveMessageAttr } from "#app/data/move.js";
+import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags.js";
+import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move.js";
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms.js";
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect.js";
import { Type } from "#app/data/type.js";
@@ -13,10 +13,10 @@ import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { Moves } from "#app/enums/moves.js";
import { StatusEffect } from "#app/enums/status-effect.js";
import { MoveUsedEvent } from "#app/events/battle-scene.js";
-import Pokemon, { PokemonMove, MoveResult, TurnMove } from "#app/field/pokemon.js";
+import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon.js";
import { getPokemonNameWithAffix } from "#app/messages.js";
-import i18next from "i18next";
import * as Utils from "#app/utils.js";
+import i18next from "i18next";
import { BattlePhase } from "./battle-phase";
import { CommonAnimPhase } from "./common-anim-phase";
import { MoveEffectPhase } from "./move-effect-phase";
@@ -38,8 +38,8 @@ export class MovePhase extends BattlePhase {
this.pokemon = pokemon;
this.targets = targets;
this.move = move;
- this.followUp = !!followUp;
- this.ignorePp = !!ignorePp;
+ this.followUp = followUp ?? false;
+ this.ignorePp = ignorePp ?? false;
this.failed = false;
this.cancelled = false;
}
@@ -194,7 +194,7 @@ export class MovePhase extends BattlePhase {
return this.end();
}
- if (!moveQueue.length || !moveQueue.shift()?.ignorePP) { // using .shift here clears out two turn moves once they've been used
+ if ((!moveQueue.length || !moveQueue.shift()?.ignorePP) && !this.ignorePp) { // using .shift here clears out two turn moves once they've been used
this.move.usePp(ppUsed);
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
}
diff --git a/src/phases/stat-change-phase.ts b/src/phases/stat-change-phase.ts
deleted file mode 100644
index 856d0a33eea..00000000000
--- a/src/phases/stat-change-phase.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import BattleScene from "#app/battle-scene.js";
-import { BattlerIndex } from "#app/battle.js";
-import { applyPreStatChangeAbAttrs, ProtectStatAbAttr, applyAbAttrs, StatChangeMultiplierAbAttr, StatChangeCopyAbAttr, applyPostStatChangeAbAttrs, PostStatChangeAbAttr } from "#app/data/ability.js";
-import { MistTag, ArenaTagSide } from "#app/data/arena-tag.js";
-import { BattleStat, getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js";
-import Pokemon from "#app/field/pokemon.js";
-import { getPokemonNameWithAffix } from "#app/messages.js";
-import { PokemonResetNegativeStatStageModifier } from "#app/modifier/modifier.js";
-import { handleTutorial, Tutorial } from "#app/tutorial.js";
-import i18next from "i18next";
-import * as Utils from "#app/utils.js";
-import { PokemonPhase } from "./pokemon-phase";
-
-export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void;
-
-export class StatChangePhase extends PokemonPhase {
- private stats: BattleStat[];
- private selfTarget: boolean;
- private levels: integer;
- private showMessage: boolean;
- private ignoreAbilities: boolean;
- private canBeCopied: boolean;
- private onChange: StatChangeCallback | null;
-
-
- constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatChangeCallback | null = null) {
- super(scene, battlerIndex);
-
- this.selfTarget = selfTarget;
- this.stats = stats;
- this.levels = levels;
- this.showMessage = showMessage;
- this.ignoreAbilities = ignoreAbilities;
- this.canBeCopied = canBeCopied;
- this.onChange = onChange;
- }
-
- start() {
- const pokemon = this.getPokemon();
-
- let random = false;
-
- if (this.stats.length === 1 && this.stats[0] === BattleStat.RAND) {
- this.stats[0] = this.getRandomStat();
- random = true;
- }
-
- this.aggregateStatChanges(random);
-
- if (!pokemon.isActive(true)) {
- return this.end();
- }
-
- const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => {
- const cancelled = new Utils.BooleanHolder(false);
-
- if (!this.selfTarget && this.levels < 0) {
- this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
- }
-
- if (!cancelled.value && !this.selfTarget && this.levels < 0) {
- applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
- }
-
- return !cancelled.value;
- });
-
- const levels = new Utils.IntegerHolder(this.levels);
-
- if (!this.ignoreAbilities) {
- applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, false, levels);
- }
-
- const battleStats = this.getPokemon().summonData.battleStats;
- const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]);
-
- this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
-
- const end = () => {
- if (this.showMessage) {
- const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels);
- for (const message of messages) {
- this.scene.queueMessage(message);
- }
- }
-
- for (const stat of filteredStats) {
- pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6);
- }
-
- if (levels.value > 0 && this.canBeCopied) {
- for (const opponent of pokemon.getOpponents()) {
- applyAbAttrs(StatChangeCopyAbAttr, opponent, null, false, this.stats, levels.value);
- }
- }
-
- applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget);
-
- // Look for any other stat change phases; if this is the last one, do White Herb check
- const existingPhase = this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex);
- if (!(existingPhase instanceof StatChangePhase)) {
- // Apply White Herb if needed
- const whiteHerb = this.scene.applyModifier(PokemonResetNegativeStatStageModifier, this.player, pokemon) as PokemonResetNegativeStatStageModifier;
- // If the White Herb was applied, consume it
- if (whiteHerb) {
- --whiteHerb.stackCount;
- if (whiteHerb.stackCount <= 0) {
- this.scene.removeModifier(whiteHerb);
- }
- this.scene.updateModifiers(this.player);
- }
- }
-
- pokemon.updateInfo();
-
- handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end());
- };
-
- if (relLevels.filter(l => l).length && this.scene.moveAnimations) {
- pokemon.enableMask();
- const pokemonMaskSprite = pokemon.maskSprite;
-
- const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale;
- const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale;
- const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale();
- const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale();
-
- // On increase, show the red sprite located at ATK
- // On decrease, show the blue sprite located at SPD
- const spriteColor = levels.value >= 1 ? BattleStat[BattleStat.ATK].toLowerCase() : BattleStat[BattleStat.SPD].toLowerCase();
- const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor);
- statSprite.setPipeline(this.scene.fieldSpritePipeline);
- statSprite.setAlpha(0);
- statSprite.setScale(6);
- statSprite.setOrigin(0.5, 1);
-
- this.scene.playSound(`se/stat_${levels.value >= 1 ? "up" : "down"}`);
-
- statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined));
-
- this.scene.tweens.add({
- targets: statSprite,
- duration: 250,
- alpha: 0.8375,
- onComplete: () => {
- this.scene.tweens.add({
- targets: statSprite,
- delay: 1000,
- duration: 250,
- alpha: 0
- });
- }
- });
-
- this.scene.tweens.add({
- targets: statSprite,
- duration: 1500,
- y: `${levels.value >= 1 ? "-" : "+"}=${160 * 6}`
- });
-
- this.scene.time.delayedCall(1750, () => {
- pokemon.disableMask();
- end();
- });
- } else {
- end();
- }
- }
-
- getRandomStat(): BattleStat {
- const allStats = Utils.getEnumValues(BattleStat);
- return this.getPokemon() ? allStats[this.getPokemon()!.randSeedInt(BattleStat.SPD + 1)] : BattleStat.ATK; // TODO: return default ATK on random? idk...
- }
-
- aggregateStatChanges(random: boolean = false): void {
- const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s));
- let existingPhase: StatChangePhase;
- if (this.stats.length === 1) {
- while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1
- && (p.stats[0] === this.stats[0] || (random && p.stats[0] === BattleStat.RAND))
- && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) {
- if (existingPhase.stats[0] === BattleStat.RAND) {
- existingPhase.stats[0] = this.getRandomStat();
- if (existingPhase.stats[0] !== this.stats[0]) {
- continue;
- }
- }
- this.levels += existingPhase.levels;
-
- if (!this.scene.tryRemovePhase(p => p === existingPhase)) {
- break;
- }
- }
- }
- while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget
- && ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva)
- && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) {
- this.stats.push(...existingPhase.stats);
- if (!this.scene.tryRemovePhase(p => p === existingPhase)) {
- break;
- }
- }
- }
-
- getStatChangeMessages(stats: BattleStat[], levels: integer, relLevels: integer[]): string[] {
- const messages: string[] = [];
-
- const relLevelStatIndexes = {};
- for (let rl = 0; rl < relLevels.length; rl++) {
- const relLevel = relLevels[rl];
- if (!relLevelStatIndexes[relLevel]) {
- relLevelStatIndexes[relLevel] = [];
- }
- relLevelStatIndexes[relLevel].push(rl);
- }
-
- Object.keys(relLevelStatIndexes).forEach(rl => {
- const relLevelStats = stats.filter((_, i) => relLevelStatIndexes[rl].includes(i));
- let statsFragment = "";
-
- if (relLevelStats.length > 1) {
- statsFragment = relLevelStats.length >= 5
- ? i18next.t("battle:stats")
- : `${relLevelStats.slice(0, -1).map(s => getBattleStatName(s)).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${getBattleStatName(relLevelStats[relLevelStats.length - 1])}`;
- messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1, relLevelStats.length));
- } else {
- statsFragment = getBattleStatName(relLevelStats[0]);
- messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1, relLevelStats.length));
- }
- });
-
- return messages;
- }
-}
diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts
new file mode 100644
index 00000000000..55faaa29903
--- /dev/null
+++ b/src/phases/stat-stage-change-phase.ts
@@ -0,0 +1,244 @@
+import { BattlerIndex } from "#app/battle";
+import BattleScene from "#app/battle-scene";
+import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, PostStatStageChangeAbAttr, ProtectStatAbAttr, StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr } from "#app/data/ability";
+import { ArenaTagSide, MistTag } from "#app/data/arena-tag";
+import Pokemon from "#app/field/pokemon";
+import { getPokemonNameWithAffix } from "#app/messages";
+import { ResetNegativeStatStageModifier } from "#app/modifier/modifier";
+import { handleTutorial, Tutorial } from "#app/tutorial";
+import * as Utils from "#app/utils";
+import i18next from "i18next";
+import { PokemonPhase } from "./pokemon-phase";
+import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
+
+export type StatStageChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void;
+
+export class StatStageChangePhase extends PokemonPhase {
+ private stats: BattleStat[];
+ private selfTarget: boolean;
+ private stages: integer;
+ private showMessage: boolean;
+ private ignoreAbilities: boolean;
+ private canBeCopied: boolean;
+ private onChange: StatStageChangeCallback | null;
+
+
+ constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatStageChangeCallback | null = null) {
+ super(scene, battlerIndex);
+
+ this.selfTarget = selfTarget;
+ this.stats = stats;
+ this.stages = stages;
+ this.showMessage = showMessage;
+ this.ignoreAbilities = ignoreAbilities;
+ this.canBeCopied = canBeCopied;
+ this.onChange = onChange;
+ }
+
+ start() {
+ const pokemon = this.getPokemon();
+
+ if (!pokemon.isActive(true)) {
+ return this.end();
+ }
+
+ let simulate = false;
+
+ const filteredStats = this.stats.filter(stat => {
+ const cancelled = new Utils.BooleanHolder(false);
+
+ if (!this.selfTarget && this.stages < 0) {
+ // TODO: Include simulate boolean when tag applications can be simulated
+ this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
+ }
+
+ if (!cancelled.value && !this.selfTarget && this.stages < 0) {
+ applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate);
+ }
+
+ // If one stat stage decrease is cancelled, simulate the rest of the applications
+ if (cancelled.value) {
+ simulate = true;
+ }
+
+ return !cancelled.value;
+ });
+
+ const stages = new Utils.IntegerHolder(this.stages);
+
+ if (!this.ignoreAbilities) {
+ applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
+ }
+
+ const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s));
+
+ this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
+
+ const end = () => {
+ if (this.showMessage) {
+ const messages = this.getStatStageChangeMessages(filteredStats, stages.value, relLevels);
+ for (const message of messages) {
+ this.scene.queueMessage(message);
+ }
+ }
+
+ for (const s of filteredStats) {
+ if (stages.value > 0 && pokemon.getStatStage(s) < 6) {
+ if (!pokemon.turnData) {
+ // Temporary fix for missing turn data struct on turn 1
+ pokemon.resetTurnData();
+ }
+ pokemon.turnData.statStagesIncreased = true;
+ } else if (stages.value < 0 && pokemon.getStatStage(s) > -6) {
+ if (!pokemon.turnData) {
+ // Temporary fix for missing turn data struct on turn 1
+ pokemon.resetTurnData();
+ }
+ pokemon.turnData.statStagesDecreased = true;
+ }
+
+ pokemon.setStatStage(s, pokemon.getStatStage(s) + stages.value);
+ }
+
+ if (stages.value > 0 && this.canBeCopied) {
+ for (const opponent of pokemon.getOpponents()) {
+ applyAbAttrs(StatStageChangeCopyAbAttr, opponent, null, false, this.stats, stages.value);
+ }
+ }
+
+ applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget);
+
+ // Look for any other stat change phases; if this is the last one, do White Herb check
+ const existingPhase = this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex);
+ if (!(existingPhase instanceof StatStageChangePhase)) {
+ // Apply White Herb if needed
+ const whiteHerb = this.scene.applyModifier(ResetNegativeStatStageModifier, this.player, pokemon) as ResetNegativeStatStageModifier;
+ // If the White Herb was applied, consume it
+ if (whiteHerb) {
+ whiteHerb.stackCount--;
+ if (whiteHerb.stackCount <= 0) {
+ this.scene.removeModifier(whiteHerb);
+ }
+ this.scene.updateModifiers(this.player);
+ }
+ }
+
+ pokemon.updateInfo();
+
+ handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end());
+ };
+
+ if (relLevels.filter(l => l).length && this.scene.moveAnimations) {
+ pokemon.enableMask();
+ const pokemonMaskSprite = pokemon.maskSprite;
+
+ const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale;
+ const tileY = ((this.player ? 148 : 84) + (stages.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale;
+ const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale();
+ const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale();
+
+ // On increase, show the red sprite located at ATK
+ // On decrease, show the blue sprite located at SPD
+ const spriteColor = stages.value >= 1 ? Stat[Stat.ATK].toLowerCase() : Stat[Stat.SPD].toLowerCase();
+ const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor);
+ statSprite.setPipeline(this.scene.fieldSpritePipeline);
+ statSprite.setAlpha(0);
+ statSprite.setScale(6);
+ statSprite.setOrigin(0.5, 1);
+
+ this.scene.playSound(`se/stat_${stages.value >= 1 ? "up" : "down"}`);
+
+ statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined));
+
+ this.scene.tweens.add({
+ targets: statSprite,
+ duration: 250,
+ alpha: 0.8375,
+ onComplete: () => {
+ this.scene.tweens.add({
+ targets: statSprite,
+ delay: 1000,
+ duration: 250,
+ alpha: 0
+ });
+ }
+ });
+
+ this.scene.tweens.add({
+ targets: statSprite,
+ duration: 1500,
+ y: `${stages.value >= 1 ? "-" : "+"}=${160 * 6}`
+ });
+
+ this.scene.time.delayedCall(1750, () => {
+ pokemon.disableMask();
+ end();
+ });
+ } else {
+ end();
+ }
+ }
+
+ aggregateStatStageChanges(): void {
+ const accEva: BattleStat[] = [ Stat.ACC, Stat.EVA ];
+ const isAccEva = accEva.some(s => this.stats.includes(s));
+ let existingPhase: StatStageChangePhase;
+ if (this.stats.length === 1) {
+ while ((existingPhase = (this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1
+ && (p.stats[0] === this.stats[0])
+ && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatStageChangePhase))) {
+ this.stages += existingPhase.stages;
+
+ if (!this.scene.tryRemovePhase(p => p === existingPhase)) {
+ break;
+ }
+ }
+ }
+ while ((existingPhase = (this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget
+ && (accEva.some(s => p.stats.includes(s)) === isAccEva)
+ && p.stages === this.stages && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatStageChangePhase))) {
+ this.stats.push(...existingPhase.stats);
+ if (!this.scene.tryRemovePhase(p => p === existingPhase)) {
+ break;
+ }
+ }
+ }
+
+ getStatStageChangeMessages(stats: BattleStat[], stages: integer, relStages: integer[]): string[] {
+ const messages: string[] = [];
+
+ const relStageStatIndexes = {};
+ for (let rl = 0; rl < relStages.length; rl++) {
+ const relStage = relStages[rl];
+ if (!relStageStatIndexes[relStage]) {
+ relStageStatIndexes[relStage] = [];
+ }
+ relStageStatIndexes[relStage].push(rl);
+ }
+
+ Object.keys(relStageStatIndexes).forEach(rl => {
+ const relStageStats = stats.filter((_, i) => relStageStatIndexes[rl].includes(i));
+ let statsFragment = "";
+
+ if (relStageStats.length > 1) {
+ statsFragment = relStageStats.length >= 5
+ ? i18next.t("battle:stats")
+ : `${relStageStats.slice(0, -1).map(s => i18next.t(getStatKey(s))).join(", ")}${relStageStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${i18next.t(getStatKey(relStageStats[relStageStats.length - 1]))}`;
+ messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), stages >= 1), {
+ pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()),
+ stats: statsFragment,
+ count: relStageStats.length
+ }));
+ } else {
+ statsFragment = i18next.t(getStatKey(relStageStats[0]));
+ messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), stages >= 1), {
+ pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()),
+ stats: statsFragment,
+ count: relStageStats.length
+ }));
+ }
+ });
+
+ return messages;
+ }
+}
diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts
index 68dc8f8bf65..8201f2879ed 100644
--- a/src/phases/switch-summon-phase.ts
+++ b/src/phases/switch-summon-phase.ts
@@ -71,7 +71,7 @@ export class SwitchSummonPhase extends SummonPhase {
i18next.t("battle:playerComeBack", { pokemonName: getPokemonNameWithAffix(pokemon) }) :
i18next.t("battle:trainerComeBack", {
trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER),
- pokemonName: getPokemonNameWithAffix(pokemon)
+ pokemonName: pokemon.getNameToRender()
})
);
this.scene.playSound("se/pb_rel");
diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts
index 0fc05968bbd..df851d857d1 100644
--- a/src/phases/turn-start-phase.ts
+++ b/src/phases/turn-start-phase.ts
@@ -1,12 +1,12 @@
-import BattleScene from "#app/battle-scene.js";
-import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability.js";
-import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move.js";
-import { Abilities } from "#app/enums/abilities.js";
-import { Stat } from "#app/enums/stat.js";
-import { PokemonMove } from "#app/field/pokemon.js";
-import { BypassSpeedChanceModifier } from "#app/modifier/modifier.js";
-import { Command } from "#app/ui/command-ui-handler.js";
-import * as Utils from "#app/utils.js";
+import BattleScene from "#app/battle-scene";
+import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability";
+import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move";
+import { Abilities } from "#app/enums/abilities";
+import { Stat } from "#app/enums/stat";
+import Pokemon, { PokemonMove } from "#app/field/pokemon";
+import { BypassSpeedChanceModifier } from "#app/modifier/modifier";
+import { Command } from "#app/ui/command-ui-handler";
+import * as Utils from "#app/utils";
import { AttemptCapturePhase } from "./attempt-capture-phase";
import { AttemptRunPhase } from "./attempt-run-phase";
import { BerryPhase } from "./berry-phase";
@@ -17,18 +17,59 @@ import { SwitchSummonPhase } from "./switch-summon-phase";
import { TurnEndPhase } from "./turn-end-phase";
import { WeatherEffectPhase } from "./weather-effect-phase";
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
+import { BattlerIndex } from "#app/battle";
+import { TrickRoomTag } from "#app/data/arena-tag";
export class TurnStartPhase extends FieldPhase {
constructor(scene: BattleScene) {
super(scene);
}
- start() {
- super.start();
+ /**
+ * This orders the active Pokemon on the field by speed into an BattlerIndex array and returns that array.
+ * It also checks for Trick Room and reverses the array if it is present.
+ * @returns {@linkcode BattlerIndex[]} the battle indices of all pokemon on the field ordered by speed
+ */
+ getSpeedOrder(): BattlerIndex[] {
+ const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[];
+ const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[];
- const field = this.scene.getField();
- const order = this.getOrder();
+ // We shuffle the list before sorting so speed ties produce random results
+ let orderedTargets: Pokemon[] = playerField.concat(enemyField);
+ // We seed it with the current turn to prevent an inconsistency where it
+ // was varying based on how long since you last reloaded
+ this.scene.executeWithSeedOffset(() => {
+ orderedTargets = Utils.randSeedShuffle(orderedTargets);
+ }, this.scene.currentBattle.turn, this.scene.waveSeed);
+ orderedTargets.sort((a: Pokemon, b: Pokemon) => {
+ const aSpeed = a?.getEffectiveStat(Stat.SPD) || 0;
+ const bSpeed = b?.getEffectiveStat(Stat.SPD) || 0;
+
+ return bSpeed - aSpeed;
+ });
+
+ // Next, a check for Trick Room is applied. If Trick Room is present, the order is reversed.
+ const speedReversed = new Utils.BooleanHolder(false);
+ this.scene.arena.applyTags(TrickRoomTag, speedReversed);
+
+ if (speedReversed.value) {
+ orderedTargets = orderedTargets.reverse();
+ }
+
+ return orderedTargets.map(t => t.getFieldIndex() + (!t.isPlayer() ? BattlerIndex.ENEMY : BattlerIndex.PLAYER));
+ }
+
+ /**
+ * This takes the result of getSpeedOrder and applies priority / bypass speed attributes to it.
+ * This also considers the priority levels of various commands and changes the result of getSpeedOrder based on such.
+ * @returns {@linkcode BattlerIndex[]} the final sequence of commands for this turn
+ */
+ getCommandOrder(): BattlerIndex[] {
+ let moveOrder = this.getSpeedOrder();
+ // The creation of the battlerBypassSpeed object contains checks for the ability Quick Draw and the held item Quick Claw
+ // The ability Mycelium Might disables Quick Claw's activation when using a status move
+ // This occurs before the main loop because of battles with more than two Pokemon
const battlerBypassSpeed = {};
this.scene.getField(true).filter(p => p.summonData).map(p => {
@@ -42,8 +83,9 @@ export class TurnStartPhase extends FieldPhase {
battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
});
- const moveOrder = order.slice(0);
-
+ // The function begins sorting orderedTargets based on command priority, move priority, and possible speed bypasses.
+ // Non-FIGHT commands (SWITCH, BALL, RUN) have a higher command priority and will always occur before any FIGHT commands.
+ moveOrder = moveOrder.slice(0);
moveOrder.sort((a, b) => {
const aCommand = this.scene.currentBattle.turnCommands[a];
const bCommand = this.scene.currentBattle.turnCommands[b];
@@ -55,37 +97,50 @@ export class TurnStartPhase extends FieldPhase {
return -1;
}
} else if (aCommand?.command === Command.FIGHT) {
- const aMove = allMoves[aCommand.move!.move];//TODO: is the bang correct here?
- const bMove = allMoves[bCommand!.move!.move];//TODO: is the bang correct here?
+ const aMove = allMoves[aCommand.move!.move];
+ const bMove = allMoves[bCommand!.move!.move];
+ // The game now considers priority and applies the relevant move and ability attributes
const aPriority = new Utils.IntegerHolder(aMove.priority);
const bPriority = new Utils.IntegerHolder(bMove.priority);
- applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
- applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
+ applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority);
+ applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority);
- applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority); //TODO: is the bang correct here?
- applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority); //TODO: is the bang correct here?
+ applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority);
+ applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority);
+ // The game now checks for differences in priority levels.
+ // If the moves share the same original priority bracket, it can check for differences in battlerBypassSpeed and return the result.
+ // This conditional is used to ensure that Quick Claw can still activate with abilities like Stall and Mycelium Might (attack moves only)
+ // Otherwise, the game returns the user of the move with the highest priority.
+ const isSameBracket = Math.ceil(aPriority.value) - Math.ceil(bPriority.value) === 0;
if (aPriority.value !== bPriority.value) {
- const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value);
- const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value;
- if (bracketDifference === 0 && hasSpeedDifference) {
+ if (isSameBracket && battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
return battlerBypassSpeed[a].value ? -1 : 1;
}
return aPriority.value < bPriority.value ? 1 : -1;
}
}
+ // If there is no difference between the move's calculated priorities, the game checks for differences in battlerBypassSpeed and returns the result.
if (battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) {
return battlerBypassSpeed[a].value ? -1 : 1;
}
- const aIndex = order.indexOf(a);
- const bIndex = order.indexOf(b);
+ const aIndex = moveOrder.indexOf(a);
+ const bIndex = moveOrder.indexOf(b);
return aIndex < bIndex ? -1 : aIndex > bIndex ? 1 : 0;
});
+ return moveOrder;
+ }
+
+ start() {
+ super.start();
+
+ const field = this.scene.getField();
+ const moveOrder = this.getCommandOrder();
let orderIndex = 0;
@@ -150,7 +205,6 @@ export class TurnStartPhase extends FieldPhase {
}
}
-
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
/** Add a new phase to check who should be taking status damage */
diff --git a/src/system/achv.ts b/src/system/achv.ts
index de2862c2813..89e5493eb2e 100644
--- a/src/system/achv.ts
+++ b/src/system/achv.ts
@@ -5,9 +5,10 @@ import { pokemonEvolutions } from "#app/data/pokemon-evolutions";
import i18next from "i18next";
import * as Utils from "../utils";
import { PlayerGender } from "#enums/player-gender";
-import { Challenge, FreshStartChallenge, InverseBattleChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge";
-import { Challenges } from "#app/enums/challenges";
+import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge";
import { ConditionFn } from "#app/@types/common";
+import { Stat, getShortenedStatKey } from "#app/enums/stat";
+import { Challenges } from "#app/enums/challenges";
export enum AchvTier {
COMMON,
@@ -172,13 +173,13 @@ export function getAchievementDescription(localizationKey: string): string {
case "10000_DMG":
return i18next.t("achv:DamageAchv.description", {context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US")});
case "250_HEAL":
- return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
+ return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP))});
case "1000_HEAL":
- return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
+ return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP))});
case "2500_HEAL":
- return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
+ return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP))});
case "10000_HEAL":
- return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
+ return i18next.t("achv:HealAchv.description", {context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP))});
case "LV_100":
return i18next.t("achv:LevelAchv.description", {context: genderStr, "level": achvs.LV_100.level});
case "LV_250":
@@ -195,7 +196,7 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t("achv:RibbonAchv.description", {context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US")});
case "100_RIBBONS":
return i18next.t("achv:RibbonAchv.description", {context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US")});
- case "TRANSFER_MAX_BATTLE_STAT":
+ case "TRANSFER_MAX_STAT_STAGE":
return i18next.t("achv:TRANSFER_MAX_BATTLE_STAT.description", { context: genderStr });
case "MAX_FRIENDSHIP":
return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr });
@@ -305,7 +306,7 @@ export const achvs = {
_50_RIBBONS: new RibbonAchv("50_RIBBONS", "", 50, "ultra_ribbon", 50).setSecret(true),
_75_RIBBONS: new RibbonAchv("75_RIBBONS", "", 75, "rogue_ribbon", 75).setSecret(true),
_100_RIBBONS: new RibbonAchv("100_RIBBONS", "", 100, "master_ribbon", 100).setSecret(true),
- TRANSFER_MAX_BATTLE_STAT: new Achv("TRANSFER_MAX_BATTLE_STAT", "", "TRANSFER_MAX_BATTLE_STAT.description", "baton", 20),
+ TRANSFER_MAX_STAT_STAGE: new Achv("TRANSFER_MAX_STAT_STAGE", "", "TRANSFER_MAX_STAT_STAGE.description", "baton", 20),
MAX_FRIENDSHIP: new Achv("MAX_FRIENDSHIP", "", "MAX_FRIENDSHIP.description", "soothe_bell", 25),
MEGA_EVOLVE: new Achv("MEGA_EVOLVE", "", "MEGA_EVOLVE.description", "mega_bracelet", 50),
GIGANTAMAX: new Achv("GIGANTAMAX", "", "GIGANTAMAX.description", "dynamax_band", 50),
diff --git a/src/system/game-data.ts b/src/system/game-data.ts
index 42b20b5be35..39b7a2963d9 100644
--- a/src/system/game-data.ts
+++ b/src/system/game-data.ts
@@ -948,7 +948,7 @@ export class GameData {
return ret;
}
- private getSessionSaveData(scene: BattleScene): SessionSaveData {
+ public getSessionSaveData(scene: BattleScene): SessionSaveData {
return {
seed: scene.seed,
playTime: scene.sessionPlayTime,
@@ -1501,7 +1501,7 @@ export class GameData {
};
}
- const defaultStarterAttr = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.DEFAULT_VARIANT | DexAttr.DEFAULT_FORM;
+ const defaultStarterAttr = DexAttr.NON_SHINY | DexAttr.MALE | DexAttr.FEMALE | DexAttr.DEFAULT_VARIANT | DexAttr.DEFAULT_FORM;
const defaultStarterNatures: Nature[] = [];
@@ -1566,11 +1566,11 @@ export class GameData {
}
}
- setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false): Promise {
- return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg);
+ setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false, showMessage: boolean = true): Promise {
+ return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg, showMessage);
}
- setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false): Promise {
+ setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false, showMessage: boolean = true): Promise {
return new Promise(resolve => {
const dexEntry = this.dexData[species.speciesId];
const caughtAttr = dexEntry.caughtAttr;
@@ -1629,13 +1629,17 @@ export class GameData {
const checkPrevolution = () => {
if (hasPrevolution) {
const prevolutionSpecies = pokemonPrevolutions[species.speciesId];
- return this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg).then(() => resolve());
+ this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg, showMessage).then(() => resolve());
} else {
resolve();
}
};
if (newCatch && speciesStarters.hasOwnProperty(species.speciesId)) {
+ if (!showMessage) {
+ resolve();
+ return;
+ }
this.scene.playSound("level_up_fanfare");
this.scene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(), null, true);
} else {
@@ -1681,7 +1685,7 @@ export class GameData {
this.starterData[species.speciesId].candyCount += count;
}
- setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer): Promise {
+ setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer, showMessage: boolean = true): Promise {
return new Promise(resolve => {
const speciesId = species.speciesId;
if (!speciesEggMoves.hasOwnProperty(speciesId) || !speciesEggMoves[speciesId][eggMoveIndex]) {
@@ -1701,11 +1705,15 @@ export class GameData {
}
this.starterData[speciesId].eggMoves |= value;
-
+ if (!showMessage) {
+ resolve(true);
+ return;
+ }
this.scene.playSound("level_up_fanfare");
-
const moveName = allMoves[speciesEggMoves[speciesId][eggMoveIndex]].name;
- this.scene.ui.showText(eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName }), null, () => resolve(true), null, true);
+ this.scene.ui.showText(eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName }), null, (() => {
+ resolve(true);
+ }), null, true);
});
}
@@ -1932,6 +1940,7 @@ export class GameData {
fixStarterData(systemData: SystemSaveData): void {
for (const starterId of defaultStarterSpecies) {
systemData.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
+ systemData.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
}
}
diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts
index 8f094379434..9a743ceb1d2 100644
--- a/src/system/pokemon-data.ts
+++ b/src/system/pokemon-data.ts
@@ -42,6 +42,7 @@ export default class PokemonData {
public luck: integer;
public pauseEvolutions: boolean;
public pokerus: boolean;
+ public usedTMs: Moves[];
public fusionSpecies: Species;
public fusionFormIndex: integer;
@@ -98,6 +99,7 @@ export default class PokemonData {
this.fusionVariant = source.fusionVariant;
this.fusionGender = source.fusionGender;
this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0);
+ this.usedTMs = source.usedTMs ?? [];
if (!forHistory) {
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
@@ -122,7 +124,8 @@ export default class PokemonData {
this.summonData = new PokemonSummonData();
if (!forHistory && source.summonData) {
- this.summonData.battleStats = source.summonData.battleStats;
+ this.summonData.stats = source.summonData.stats;
+ this.summonData.statStages = source.summonData.statStages;
this.summonData.moveQueue = source.summonData.moveQueue;
this.summonData.disabledMove = source.summonData.disabledMove;
this.summonData.disabledTurns = source.summonData.disabledTurns;
diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts
index 7b0fea95a98..6b46b6fe96c 100644
--- a/src/system/settings/settings.ts
+++ b/src/system/settings/settings.ts
@@ -25,6 +25,7 @@ const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i
value: "Mute",
label: getTranslation("settings:mute")
});
+
const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = new Array(9).fill(null).map((_, i) => {
const value = ((i + 1) * 10).toString();
return {
@@ -32,6 +33,7 @@ const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = new Array(9).fill(null).ma
label: value,
};
});
+
const OFF_ON: SettingOption[] = [
{
value: "Off",
@@ -53,6 +55,40 @@ const AUTO_DISABLED: SettingOption[] = [
}
];
+const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [
+ {
+ value: "Rewards",
+ label: i18next.t("settings:rewards")
+ },
+ {
+ value: "Shop",
+ label: i18next.t("settings:shop")
+ },
+ {
+ value: "Reroll",
+ label: i18next.t("settings:reroll")
+ },
+ {
+ value: "Check Team",
+ label: i18next.t("settings:checkTeam")
+ }
+];
+
+const shopCursorTargetIndexMap = SHOP_CURSOR_TARGET_OPTIONS.map(option => {
+ switch (option.value) {
+ case "Rewards":
+ return ShopCursorTarget.REWARDS;
+ case "Shop":
+ return ShopCursorTarget.SHOP;
+ case "Reroll":
+ return ShopCursorTarget.REROLL;
+ case "Check Team":
+ return ShopCursorTarget.CHECK_TEAM;
+ default:
+ throw new Error(`Unknown value: ${option.value}`);
+ }
+});
+
/**
* Types for helping separate settings to different menus
*/
@@ -103,7 +139,7 @@ export const SettingKeys = {
Damage_Numbers: "DAMAGE_NUMBERS",
Move_Animations: "MOVE_ANIMATIONS",
Show_Stats_on_Level_Up: "SHOW_LEVEL_UP_STATS",
- Reroll_Target: "REROLL_TARGET",
+ Shop_Cursor_Target: "SHOP_CURSOR_TARGET",
Candy_Upgrade_Notification: "CANDY_UPGRADE_NOTIFICATION",
Candy_Upgrade_Display: "CANDY_UPGRADE_DISPLAY",
Move_Info: "MOVE_INFO",
@@ -596,27 +632,10 @@ export const Setting: Array = [
isHidden: () => !hasTouchscreen()
},
{
- key: SettingKeys.Reroll_Target,
+ key: SettingKeys.Shop_Cursor_Target,
label: i18next.t("settings:shopCursorTarget"),
- options: [
- {
- value:"Reroll",
- label: i18next.t("settings:reroll")
- },
- {
- value:"Items",
- label: i18next.t("settings:items")
- },
- {
- value:"Shop",
- label: i18next.t("settings:shop")
- },
- {
- value:"Check Team",
- label: i18next.t("settings:checkTeam")
- }
- ],
- default: ShopCursorTarget.CHECK_TEAM,
+ options: SHOP_CURSOR_TARGET_OPTIONS,
+ default: 0,
type: SettingType.DISPLAY
},
{
@@ -758,8 +777,10 @@ export function setSetting(scene: BattleScene, setting: string, value: integer):
case SettingKeys.Show_Stats_on_Level_Up:
scene.showLevelUpStats = Setting[index].options[value].value === "On";
break;
- case SettingKeys.Reroll_Target:
- scene.shopCursorTarget = value;
+ case SettingKeys.Shop_Cursor_Target:
+ const selectedValue = shopCursorTargetIndexMap[value];
+ scene.shopCursorTarget = selectedValue;
+ break;
case SettingKeys.EXP_Gains_Speed:
scene.expGainsSpeed = value;
break;
diff --git a/src/test/abilities/ability_timing.test.ts b/src/test/abilities/ability_timing.test.ts
index 3238f880992..fb3d3af1a6b 100644
--- a/src/test/abilities/ability_timing.test.ts
+++ b/src/test/abilities/ability_timing.test.ts
@@ -1,13 +1,11 @@
+import { BattleStyle } from "#app/enums/battle-style";
import { CommandPhase } from "#app/phases/command-phase";
-import { MessagePhase } from "#app/phases/message-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import i18next, { initI18n } from "#app/plugins/i18n";
import { Mode } from "#app/ui/ui";
import { Abilities } from "#enums/abilities";
-import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
-import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -28,19 +26,18 @@ describe("Ability Timing", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.battleType("single");
- game.override.enemySpecies(Species.PIDGEY);
- game.override.enemyAbility(Abilities.INTIMIDATE);
- game.override.enemyMoveset(SPLASH_ONLY);
-
- game.override.ability(Abilities.BALL_FETCH);
- game.override.moveset([Moves.SPLASH, Moves.ICE_BEAM]);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.INTIMIDATE)
+ .ability(Abilities.BALL_FETCH);
});
- it("should trigger after switch check", async() => {
+ it("should trigger after switch check", async () => {
initI18n();
i18next.changeLanguage("en");
+ game.settings.battleStyle = BattleStyle.SWITCH;
await game.classicMode.runToSummon([Species.EEVEE, Species.FEEBAS]);
game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
@@ -48,7 +45,7 @@ describe("Ability Timing", () => {
game.endPhase();
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
- await game.phaseInterceptor.to(MessagePhase);
+ await game.phaseInterceptor.to("MessagePhase");
const message = game.textInterceptor.getLatestMessage();
expect(message).toContain("Attack fell");
}, 5000);
diff --git a/src/test/abilities/beast_boost.test.ts b/src/test/abilities/beast_boost.test.ts
new file mode 100644
index 00000000000..05645a1231d
--- /dev/null
+++ b/src/test/abilities/beast_boost.test.ts
@@ -0,0 +1,89 @@
+import { BattlerIndex } from "#app/battle";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+
+describe("Abilities - Beast Boost", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.BULBASAUR)
+ .enemyAbility(Abilities.BEAST_BOOST)
+ .ability(Abilities.BEAST_BOOST)
+ .startingLevel(2000)
+ .moveset([ Moves.FLAMETHROWER ])
+ .enemyMoveset(SPLASH_ONLY);
+ });
+
+ it("should prefer highest stat to boost its corresponding stat stage by 1 when winning a battle", async() => {
+ await game.classicMode.startBattle([Species.SLOWBRO]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ // Set the pokemon's highest stat to DEF, so it should be picked by Beast Boost
+ vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([ 10000, 100, 1000, 200, 100, 100 ]);
+ console.log(playerPokemon.stats);
+
+ expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0);
+
+ game.move.select(Moves.FLAMETHROWER);
+ await game.phaseInterceptor.to("VictoryPhase");
+
+ expect(playerPokemon.getStatStage(Stat.DEF)).toBe(1);
+ }, 20000);
+
+ it("should use in-battle overriden stats when determining the stat stage to raise by 1", async() => {
+ game.override.enemyMoveset(new Array(4).fill(Moves.GUARD_SPLIT));
+
+ await game.classicMode.startBattle([Species.SLOWBRO]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ // If the opponent uses Guard Split, the pokemon's second highest stat (SPATK) should be chosen
+ vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([ 10000, 100, 201, 200, 100, 100 ]);
+
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
+
+ game.move.select(Moves.FLAMETHROWER);
+
+ await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
+ await game.phaseInterceptor.to("VictoryPhase");
+
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
+ }, 20000);
+
+ it("should have order preference in case of stat ties", async() => {
+ // Order preference follows the order of EFFECTIVE_STAT
+ await game.classicMode.startBattle([Species.SLOWBRO]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+
+ // Set up tie between SPATK, SPDEF, and SPD, where SPATK should win
+ vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([ 10000, 1, 1, 100, 100, 100 ]);
+
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
+
+ game.move.select(Moves.FLAMETHROWER);
+
+ await game.phaseInterceptor.to("VictoryPhase");
+
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
+ }, 20000);
+});
diff --git a/src/test/abilities/contrary.test.ts b/src/test/abilities/contrary.test.ts
new file mode 100644
index 00000000000..19ecc7e0240
--- /dev/null
+++ b/src/test/abilities/contrary.test.ts
@@ -0,0 +1,42 @@
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
+import { Abilities } from "#enums/abilities";
+import { Species } from "#enums/species";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Abilities - Contrary", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.BULBASAUR)
+ .enemyAbility(Abilities.CONTRARY)
+ .ability(Abilities.INTIMIDATE)
+ .enemyMoveset(SPLASH_ONLY);
+ });
+
+ it("should invert stat changes when applied", async() => {
+ await game.startBattle([
+ Species.SLOWBRO
+ ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
+ }, 20000);
+});
diff --git a/src/test/abilities/costar.test.ts b/src/test/abilities/costar.test.ts
index 9a4baeef1fb..96ec775f2a0 100644
--- a/src/test/abilities/costar.test.ts
+++ b/src/test/abilities/costar.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
@@ -35,7 +35,7 @@ describe("Abilities - COSTAR", () => {
test(
- "ability copies positive stat changes",
+ "ability copies positive stat stages",
async () => {
game.override.enemyAbility(Abilities.BALL_FETCH);
@@ -48,8 +48,8 @@ describe("Abilities - COSTAR", () => {
game.move.select(Moves.SPLASH, 1);
await game.toNextTurn();
- expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2);
- expect(rightPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0);
+ expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2);
+ expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(0);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(CommandPhase);
@@ -57,14 +57,14 @@ describe("Abilities - COSTAR", () => {
await game.phaseInterceptor.to(MessagePhase);
[leftPokemon, rightPokemon] = game.scene.getPlayerField();
- expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2);
- expect(rightPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2);
+ expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2);
+ expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(2);
},
TIMEOUT,
);
test(
- "ability copies negative stat changes",
+ "ability copies negative stat stages",
async () => {
game.override.enemyAbility(Abilities.INTIMIDATE);
@@ -72,8 +72,8 @@ describe("Abilities - COSTAR", () => {
let [leftPokemon, rightPokemon] = game.scene.getPlayerField();
- expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
- expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
+ expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
+ expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(CommandPhase);
@@ -81,8 +81,8 @@ describe("Abilities - COSTAR", () => {
await game.phaseInterceptor.to(MessagePhase);
[leftPokemon, rightPokemon] = game.scene.getPlayerField();
- expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
- expect(rightPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
+ expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
+ expect(rightPokemon.getStatStage(Stat.ATK)).toBe(-2);
},
TIMEOUT,
);
diff --git a/src/test/abilities/dancer.test.ts b/src/test/abilities/dancer.test.ts
new file mode 100644
index 00000000000..d80f497f8b2
--- /dev/null
+++ b/src/test/abilities/dancer.test.ts
@@ -0,0 +1,64 @@
+import { BattlerIndex } from "#app/battle";
+import { MovePhase } from "#app/phases/move-phase";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import GameManager from "#test/utils/gameManager";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+
+const TIMEOUT = 20 * 1000;
+
+describe("Abilities - Dancer", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("double")
+ .moveset([Moves.SWORDS_DANCE, Moves.SPLASH])
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.DANCER)
+ .enemyMoveset(Array(4).fill(Moves.VICTORY_DANCE));
+ });
+
+ // Reference Link: https://bulbapedia.bulbagarden.net/wiki/Dancer_(Ability)
+
+ it("triggers when dance moves are used, doesn't consume extra PP", async () => {
+ await game.classicMode.startBattle([Species.ORICORIO, Species.FEEBAS]);
+
+ const [oricorio] = game.scene.getPlayerField();
+
+ game.move.select(Moves.SPLASH);
+ game.move.select(Moves.SWORDS_DANCE, 1);
+ await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]);
+ await game.phaseInterceptor.to("MovePhase");
+ // immediately copies ally move
+ await game.phaseInterceptor.to("MovePhase", false);
+ let currentPhase = game.scene.getCurrentPhase() as MovePhase;
+ expect(currentPhase.pokemon).toBe(oricorio);
+ expect(currentPhase.move.moveId).toBe(Moves.SWORDS_DANCE);
+ await game.phaseInterceptor.to("MoveEndPhase");
+ await game.phaseInterceptor.to("MovePhase");
+ // immediately copies enemy move
+ await game.phaseInterceptor.to("MovePhase", false);
+ currentPhase = game.scene.getCurrentPhase() as MovePhase;
+ expect(currentPhase.pokemon).toBe(oricorio);
+ expect(currentPhase.move.moveId).toBe(Moves.VICTORY_DANCE);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ // doesn't use PP if copied move is also in moveset
+ expect(oricorio.moveset[0]?.ppUsed).toBe(0);
+ }, TIMEOUT);
+});
diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts
index 85141fdb491..ef145262954 100644
--- a/src/test/abilities/disguise.test.ts
+++ b/src/test/abilities/disguise.test.ts
@@ -1,14 +1,9 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { StatusEffect } from "#app/data/status-effect";
-import { CommandPhase } from "#app/phases/command-phase";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase";
-import { MoveEndPhase } from "#app/phases/move-end-phase";
-import { TurnEndPhase } from "#app/phases/turn-end-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
-import { Mode } from "#app/ui/ui";
import { toDmgValue } from "#app/utils";
+import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
+import { StatusEffect } from "#app/data/status-effect";
+import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
@@ -33,17 +28,16 @@ describe("Abilities - Disguise", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.battleType("single");
-
- game.override.enemySpecies(Species.MIMIKYU);
- game.override.enemyMoveset(SPLASH_ONLY);
-
- game.override.starterSpecies(Species.REGIELEKI);
- game.override.moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MIMIKYU)
+ .enemyMoveset(SPLASH_ONLY)
+ .starterSpecies(Species.REGIELEKI)
+ .moveset([Moves.SHADOW_SNEAK, Moves.VACUUM_WAVE, Moves.TOXIC_THREAD, Moves.SPLASH]);
}, TIMEOUT);
it("takes no damage from attacking move and transforms to Busted form, takes 1/8 max HP damage from the disguise breaking", async () => {
- await game.startBattle();
+ await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
const maxHp = mimikyu.getMaxHp();
@@ -53,14 +47,14 @@ describe("Abilities - Disguise", () => {
game.move.select(Moves.SHADOW_SNEAK);
- await game.phaseInterceptor.to(MoveEndPhase);
+ await game.phaseInterceptor.to("MoveEndPhase");
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
expect(mimikyu.formIndex).toBe(bustedForm);
}, TIMEOUT);
it("doesn't break disguise when attacked with ineffective move", async () => {
- await game.startBattle();
+ await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
@@ -68,15 +62,15 @@ describe("Abilities - Disguise", () => {
game.move.select(Moves.VACUUM_WAVE);
- await game.phaseInterceptor.to(MoveEndPhase);
+ await game.phaseInterceptor.to("MoveEndPhase");
expect(mimikyu.formIndex).toBe(disguisedForm);
}, TIMEOUT);
it("takes no damage from the first hit of a multihit move and transforms to Busted form, then takes damage from the second hit", async () => {
- game.override.moveset([Moves.SURGING_STRIKES]);
+ game.override.moveset([ Moves.SURGING_STRIKES ]);
game.override.enemyLevel(5);
- await game.startBattle();
+ await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
const maxHp = mimikyu.getMaxHp();
@@ -87,29 +81,29 @@ describe("Abilities - Disguise", () => {
game.move.select(Moves.SURGING_STRIKES);
// First hit
- await game.phaseInterceptor.to(MoveEffectPhase);
+ await game.phaseInterceptor.to("MoveEffectPhase");
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
expect(mimikyu.formIndex).toBe(disguisedForm);
// Second hit
- await game.phaseInterceptor.to(MoveEffectPhase);
+ await game.phaseInterceptor.to("MoveEffectPhase");
expect(mimikyu.hp).lessThan(maxHp - disguiseDamage);
expect(mimikyu.formIndex).toBe(bustedForm);
}, TIMEOUT);
it("takes effects from status moves and damage from status effects", async () => {
- await game.startBattle();
+ await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
expect(mimikyu.hp).toBe(mimikyu.getMaxHp());
game.move.select(Moves.TOXIC_THREAD);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
expect(mimikyu.formIndex).toBe(disguisedForm);
expect(mimikyu.status?.effect).toBe(StatusEffect.POISON);
- expect(mimikyu.summonData.battleStats[BattleStat.SPD]).toBe(-1);
+ expect(mimikyu.getStatStage(Stat.SPD)).toBe(-1);
expect(mimikyu.hp).toBeLessThan(mimikyu.getMaxHp());
}, TIMEOUT);
@@ -117,7 +111,7 @@ describe("Abilities - Disguise", () => {
game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
game.override.starterSpecies(0);
- await game.startBattle([Species.MIMIKYU, Species.FURRET]);
+ await game.classicMode.startBattle([ Species.MIMIKYU, Species.FURRET ]);
const mimikyu = game.scene.getPlayerPokemon()!;
const maxHp = mimikyu.getMaxHp();
@@ -125,7 +119,7 @@ describe("Abilities - Disguise", () => {
game.move.select(Moves.SPLASH);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
expect(mimikyu.formIndex).toBe(bustedForm);
expect(mimikyu.hp).equals(maxHp - disguiseDamage);
@@ -133,7 +127,7 @@ describe("Abilities - Disguise", () => {
await game.toNextTurn();
game.doSwitchPokemon(1);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
expect(mimikyu.formIndex).toBe(bustedForm);
}, TIMEOUT);
@@ -143,7 +137,7 @@ describe("Abilities - Disguise", () => {
game.override.starterForms({
[Species.MIMIKYU]: bustedForm
});
- await game.startBattle([Species.FURRET, Species.MIMIKYU]);
+ await game.classicMode.startBattle([ Species.FURRET, Species.MIMIKYU ]);
const mimikyu = game.scene.getParty()[1]!;
expect(mimikyu.formIndex).toBe(bustedForm);
@@ -162,7 +156,7 @@ describe("Abilities - Disguise", () => {
[Species.MIMIKYU]: bustedForm
});
- await game.startBattle();
+ await game.classicMode.startBattle();
const mimikyu = game.scene.getPlayerPokemon()!;
@@ -182,7 +176,7 @@ describe("Abilities - Disguise", () => {
[Species.MIMIKYU]: bustedForm
});
- await game.startBattle([Species.MIMIKYU, Species.FURRET]);
+ await game.classicMode.startBattle([ Species.MIMIKYU, Species.FURRET ]);
const mimikyu1 = game.scene.getPlayerPokemon()!;
@@ -194,15 +188,6 @@ describe("Abilities - Disguise", () => {
await game.toNextTurn();
game.move.select(Moves.SPLASH);
await game.doKillOpponents();
- game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => { // TODO: Make tests run in set mode instead of switch mode
- game.setMode(Mode.MESSAGE);
- game.endPhase();
- }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
-
- game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
- game.setMode(Mode.MESSAGE);
- game.endPhase();
- }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase));
await game.phaseInterceptor.to("PartyHealPhase");
expect(mimikyu1.formIndex).toBe(disguisedForm);
@@ -210,7 +195,7 @@ describe("Abilities - Disguise", () => {
it("doesn't faint twice when fainting due to Disguise break damage, nor prevent faint from Disguise break damage if using Endure", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.ENDURE));
- await game.startBattle();
+ await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
mimikyu.hp = 1;
@@ -221,4 +206,22 @@ describe("Abilities - Disguise", () => {
expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase");
expect(game.scene.currentBattle.waveIndex).toBe(2);
}, TIMEOUT);
+
+ it("activates when Aerilate circumvents immunity to the move's base type", async () => {
+ game.override.ability(Abilities.AERILATE);
+ game.override.moveset([Moves.TACKLE]);
+
+ await game.classicMode.startBattle();
+
+ const mimikyu = game.scene.getEnemyPokemon()!;
+ const maxHp = mimikyu.getMaxHp();
+ const disguiseDamage = toDmgValue(maxHp / 8);
+
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+
+ expect(mimikyu.formIndex).toBe(bustedForm);
+ expect(mimikyu.hp).toBe(maxHp - disguiseDamage);
+ }, TIMEOUT);
});
diff --git a/src/test/abilities/flower_gift.test.ts b/src/test/abilities/flower_gift.test.ts
new file mode 100644
index 00000000000..de07bd29478
--- /dev/null
+++ b/src/test/abilities/flower_gift.test.ts
@@ -0,0 +1,154 @@
+import { BattlerIndex } from "#app/battle";
+import { Abilities } from "#app/enums/abilities";
+import { Stat } from "#app/enums/stat";
+import { WeatherType } from "#app/enums/weather-type";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import GameManager from "#test/utils/gameManager";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+
+describe("Abilities - Flower Gift", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+ const OVERCAST_FORM = 0;
+ const SUNSHINE_FORM = 1;
+
+ /**
+ * Tests reverting to normal form when Cloud Nine/Air Lock is active on the field
+ * @param {GameManager} game The game manager instance
+ * @param {Abilities} ability The ability that is active on the field
+ */
+ const testRevertFormAgainstAbility = async (game: GameManager, ability: Abilities) => {
+ game.override.starterForms({ [Species.CASTFORM]: SUNSHINE_FORM }).enemyAbility(ability);
+ await game.classicMode.startBattle([Species.CASTFORM]);
+
+ game.move.select(Moves.SPLASH);
+
+ expect(game.scene.getPlayerPokemon()?.formIndex).toBe(OVERCAST_FORM);
+ };
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .moveset([Moves.SPLASH, Moves.RAIN_DANCE, Moves.SUNNY_DAY, Moves.SKILL_SWAP])
+ .enemySpecies(Species.MAGIKARP)
+ .enemyMoveset(SPLASH_ONLY)
+ .enemyAbility(Abilities.BALL_FETCH);
+ });
+
+ // TODO: Uncomment expect statements when the ability is implemented - currently does not increase stats of allies
+ it("increases the ATK and SPDEF stat stages of the Pokémon with this Ability and its allies by 1.5× during Harsh Sunlight", async () => {
+ game.override.battleType("double");
+ await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
+
+ const [ cherrim ] = game.scene.getPlayerField();
+ const cherrimAtkStat = cherrim.getEffectiveStat(Stat.ATK);
+ const cherrimSpDefStat = cherrim.getEffectiveStat(Stat.SPDEF);
+
+ // const magikarpAtkStat = magikarp.getEffectiveStat(Stat.ATK);;
+ // const magikarpSpDefStat = magikarp.getEffectiveStat(Stat.SPDEF);
+
+ game.move.select(Moves.SUNNY_DAY, 0);
+ game.move.select(Moves.SPLASH, 1);
+
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
+ await game.phaseInterceptor.to("TurnEndPhase");
+
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+ expect(cherrim.getEffectiveStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 1.5));
+ expect(cherrim.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(cherrimSpDefStat * 1.5));
+ // expect(magikarp.getEffectiveStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5));
+ // expect(magikarp.getEffectiveStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5));
+ });
+
+ it("changes the Pokemon's form during Harsh Sunlight", async () => {
+ game.override.weather(WeatherType.HARSH_SUN);
+ await game.classicMode.startBattle([Species.CHERRIM]);
+
+ const cherrim = game.scene.getPlayerPokemon()!;
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+
+ game.move.select(Moves.SPLASH);
+ });
+
+ it("reverts to Overcast Form if a Pokémon on the field has Air Lock", async () => {
+ await testRevertFormAgainstAbility(game, Abilities.AIR_LOCK);
+ });
+
+ it("reverts to Overcast Form if a Pokémon on the field has Cloud Nine", async () => {
+ await testRevertFormAgainstAbility(game, Abilities.CLOUD_NINE);
+ });
+
+ it("reverts to Overcast Form when the Pokémon loses Flower Gift, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
+ game.override.enemyMoveset(Array(4).fill(Moves.SKILL_SWAP)).weather(WeatherType.HARSH_SUN);
+
+ await game.classicMode.startBattle([Species.CHERRIM]);
+
+ const cherrim = game.scene.getPlayerPokemon()!;
+
+ game.move.select(Moves.SKILL_SWAP);
+
+ await game.phaseInterceptor.to("TurnStartPhase");
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(cherrim.formIndex).toBe(OVERCAST_FORM);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+ });
+
+ it("reverts to Overcast Form when the Flower Gift is suppressed, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
+ game.override.enemyMoveset(Array(4).fill(Moves.GASTRO_ACID)).weather(WeatherType.HARSH_SUN);
+
+ await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
+
+ const cherrim = game.scene.getPlayerPokemon()!;
+
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+
+ game.move.select(Moves.SPLASH);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.phaseInterceptor.to("TurnEndPhase");
+
+ expect(cherrim.summonData.abilitySuppressed).toBe(true);
+ expect(cherrim.formIndex).toBe(OVERCAST_FORM);
+
+ await game.toNextTurn();
+
+ game.doSwitchPokemon(1);
+ await game.toNextTurn();
+
+ game.doSwitchPokemon(1);
+ await game.phaseInterceptor.to("MovePhase");
+
+ expect(cherrim.summonData.abilitySuppressed).toBe(false);
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+ });
+
+ it("should be in Overcast Form after the user is switched out", async () => {
+ game.override.weather(WeatherType.SUNNY);
+
+ await game.classicMode.startBattle([Species.CASTFORM, Species.MAGIKARP]);
+ const cherrim = game.scene.getPlayerPokemon()!;
+
+ expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
+
+ game.doSwitchPokemon(1);
+ await game.toNextTurn();
+
+ expect(cherrim.formIndex).toBe(OVERCAST_FORM);
+ });
+});
diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts
index a451d290906..286c3af1c56 100644
--- a/src/test/abilities/gulp_missile.test.ts
+++ b/src/test/abilities/gulp_missile.test.ts
@@ -1,4 +1,3 @@
-import { BattleStat } from "#app/data/battle-stat";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { StatusEffect } from "#app/enums/status-effect";
import Pokemon from "#app/field/pokemon";
@@ -13,6 +12,7 @@ import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
+import { Stat } from "#enums/stat";
describe("Abilities - Gulp Missile", () => {
let phaserGame: Phaser.Game;
@@ -107,7 +107,7 @@ describe("Abilities - Gulp Missile", () => {
expect(cramorant.formIndex).toBe(GULPING_FORM);
});
- it("deals ¼ of the attacker's maximum HP when hit by a damaging attack", async () => {
+ it("deals 1/4 of the attacker's maximum HP when hit by a damaging attack", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]);
@@ -139,7 +139,7 @@ describe("Abilities - Gulp Missile", () => {
expect(cramorant.formIndex).toBe(GULPING_FORM);
});
- it("lowers the attacker's Defense by 1 stage when hit in Gulping form", async () => {
+ it("lowers attacker's DEF stat stage by 1 when hit in Gulping form", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]);
@@ -158,7 +158,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy));
- expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(-1);
+ expect(enemy.getStatStage(Stat.DEF)).toBe(-1);
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM);
});
@@ -219,7 +219,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBe(enemyHpPreEffect);
- expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(-1);
+ expect(enemy.getStatStage(Stat.DEF)).toBe(-1);
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM);
});
diff --git a/src/test/abilities/hustle.test.ts b/src/test/abilities/hustle.test.ts
index b7c3b723c4b..ff96b98c7ac 100644
--- a/src/test/abilities/hustle.test.ts
+++ b/src/test/abilities/hustle.test.ts
@@ -1,8 +1,6 @@
import { allMoves } from "#app/data/move";
import { Abilities } from "#app/enums/abilities";
import { Stat } from "#app/enums/stat";
-import { DamagePhase } from "#app/phases/damage-phase";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
@@ -26,54 +24,54 @@ describe("Abilities - Hustle", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.ability(Abilities.HUSTLE);
- game.override.moveset([Moves.TACKLE, Moves.GIGA_DRAIN, Moves.FISSURE]);
- game.override.startingLevel(5);
- game.override.disableCrits();
- game.override.enemyLevel(5);
- game.override.enemyMoveset(SPLASH_ONLY);
- game.override.enemySpecies(Species.SHUCKLE);
- game.override.enemyAbility(Abilities.BALL_FETCH);
+ game.override
+ .ability(Abilities.HUSTLE)
+ .moveset([ Moves.TACKLE, Moves.GIGA_DRAIN, Moves.FISSURE ])
+ .disableCrits()
+ .battleType("single")
+ .enemyMoveset(SPLASH_ONLY)
+ .enemySpecies(Species.SHUCKLE)
+ .enemyAbility(Abilities.BALL_FETCH);
});
it("increases the user's Attack stat by 50%", async () => {
- await game.startBattle([Species.PIKACHU]);
+ await game.classicMode.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon()!;
const atk = pikachu.stats[Stat.ATK];
- vi.spyOn(pikachu, "getBattleStat");
+ vi.spyOn(pikachu, "getEffectiveStat");
game.move.select(Moves.TACKLE);
await game.move.forceHit();
- await game.phaseInterceptor.to(DamagePhase);
+ await game.phaseInterceptor.to("DamagePhase");
- expect(pikachu.getBattleStat).toHaveReturnedWith(atk * 1.5);
+ expect(pikachu.getEffectiveStat).toHaveReturnedWith(Math.floor(atk * 1.5));
});
it("lowers the accuracy of the user's physical moves by 20%", async () => {
- await game.startBattle([Species.PIKACHU]);
+ await game.classicMode.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon()!;
vi.spyOn(pikachu, "getAccuracyMultiplier");
game.move.select(Moves.TACKLE);
- await game.phaseInterceptor.to(MoveEffectPhase);
+ await game.phaseInterceptor.to("MoveEffectPhase");
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(0.8);
});
it("does not affect non-physical moves", async () => {
- await game.startBattle([Species.PIKACHU]);
+ await game.classicMode.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon()!;
const spatk = pikachu.stats[Stat.SPATK];
- vi.spyOn(pikachu, "getBattleStat");
+ vi.spyOn(pikachu, "getEffectiveStat");
vi.spyOn(pikachu, "getAccuracyMultiplier");
game.move.select(Moves.GIGA_DRAIN);
- await game.phaseInterceptor.to(DamagePhase);
+ await game.phaseInterceptor.to("DamagePhase");
- expect(pikachu.getBattleStat).toHaveReturnedWith(spatk);
+ expect(pikachu.getEffectiveStat).toHaveReturnedWith(spatk);
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
});
@@ -81,7 +79,7 @@ describe("Abilities - Hustle", () => {
game.override.startingLevel(100);
game.override.enemyLevel(30);
- await game.startBattle([Species.PIKACHU]);
+ await game.classicMode.startBattle([Species.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
@@ -89,7 +87,7 @@ describe("Abilities - Hustle", () => {
vi.spyOn(allMoves[Moves.FISSURE], "calculateBattleAccuracy");
game.move.select(Moves.FISSURE);
- await game.phaseInterceptor.to(DamagePhase);
+ await game.phaseInterceptor.to("DamagePhase");
expect(enemyPokemon.turnData.damageTaken).toBe(enemyPokemon.getMaxHp());
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
diff --git a/src/test/abilities/hyper_cutter.test.ts b/src/test/abilities/hyper_cutter.test.ts
index 28fcc2f6085..64e04ac2fd3 100644
--- a/src/test/abilities/hyper_cutter.test.ts
+++ b/src/test/abilities/hyper_cutter.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@@ -51,7 +51,7 @@ describe("Abilities - Hyper Cutter", () => {
game.move.select(Moves.STRING_SHOT);
await game.toNextTurn();
- expect(enemy.summonData.battleStats[BattleStat.ATK]).toEqual(0);
- [BattleStat.ACC, BattleStat.DEF, BattleStat.EVA, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD].forEach((stat: number) => expect(enemy.summonData.battleStats[stat]).toBeLessThan(0));
+ expect(enemy.getStatStage(Stat.ATK)).toEqual(0);
+ [Stat.ACC, Stat.DEF, Stat.EVA, Stat.SPATK, Stat.SPDEF, Stat.SPD].forEach((stat: number) => expect(enemy.getStatStage(stat)).toBeLessThan(0));
});
});
diff --git a/src/test/abilities/imposter.test.ts b/src/test/abilities/imposter.test.ts
new file mode 100644
index 00000000000..2857f80632a
--- /dev/null
+++ b/src/test/abilities/imposter.test.ts
@@ -0,0 +1,101 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+// TODO: Add more tests once Imposter is fully implemented
+describe("Abilities - Imposter", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .enemyAbility(Abilities.BEAST_BOOST)
+ .enemyPassiveAbility(Abilities.BALL_FETCH)
+ .enemyMoveset(SPLASH_ONLY)
+ .ability(Abilities.IMPOSTER)
+ .moveset(SPLASH_ONLY);
+ });
+
+ it("should copy species, ability, gender, all stats except HP, all stat stages, moveset, and types of target", async () => {
+ await game.startBattle([
+ Species.DITTO
+ ]);
+
+ game.move.select(Moves.SPLASH);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ expect(player.getSpeciesForm().speciesId).toBe(enemy.getSpeciesForm().speciesId);
+ expect(player.getAbility()).toBe(enemy.getAbility());
+ expect(player.getGender()).toBe(enemy.getGender());
+
+ expect(player.getStat(Stat.HP, false)).not.toBe(enemy.getStat(Stat.HP));
+ for (const s of EFFECTIVE_STATS) {
+ expect(player.getStat(s, false)).toBe(enemy.getStat(s, false));
+ }
+
+ for (const s of BATTLE_STATS) {
+ expect(player.getStatStage(s)).toBe(enemy.getStatStage(s));
+ }
+
+ const playerMoveset = player.getMoveset();
+ const enemyMoveset = player.getMoveset();
+
+ for (let i = 0; i < playerMoveset.length && i < enemyMoveset.length; i++) {
+ // TODO: Checks for 5 PP should be done here when that gets addressed
+ expect(playerMoveset[i]?.moveId).toBe(enemyMoveset[i]?.moveId);
+ }
+
+ const playerTypes = player.getTypes();
+ const enemyTypes = enemy.getTypes();
+
+ for (let i = 0; i < playerTypes.length && i < enemyTypes.length; i++) {
+ expect(playerTypes[i]).toBe(enemyTypes[i]);
+ }
+ }, 20000);
+
+ it("should copy in-battle overridden stats", async () => {
+ game.override.enemyMoveset(new Array(4).fill(Moves.POWER_SPLIT));
+
+ await game.startBattle([
+ Species.DITTO
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
+ const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
+
+ game.move.select(Moves.TACKLE);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
+ expect(enemy.getStat(Stat.ATK, false)).toBe(avgAtk);
+
+ expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ });
+});
diff --git a/src/test/abilities/intimidate.test.ts b/src/test/abilities/intimidate.test.ts
index 93b663d06da..f90ba6c0e1e 100644
--- a/src/test/abilities/intimidate.test.ts
+++ b/src/test/abilities/intimidate.test.ts
@@ -1,21 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { Status, StatusEffect } from "#app/data/status-effect";
-import { GameModes, getGameMode } from "#app/game-mode";
-import { CommandPhase } from "#app/phases/command-phase";
-import { DamagePhase } from "#app/phases/damage-phase";
-import { EncounterPhase } from "#app/phases/encounter-phase";
-import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
-import { SelectStarterPhase } from "#app/phases/select-starter-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#test/utils/gameManager";
import { Mode } from "#app/ui/ui";
+import { Stat } from "#enums/stat";
+import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
-import { generateStarter } from "#test/utils/gameManagerUtils";
import { SPLASH_ONLY } from "#test/utils/testUtils";
-import Phaser from "phaser";
-import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Intimidate", () => {
let phaserGame: Phaser.Game;
@@ -33,16 +25,16 @@ describe("Abilities - Intimidate", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.battleType("single");
- game.override.enemySpecies(Species.RATTATA);
- game.override.enemyAbility(Abilities.INTIMIDATE);
- game.override.enemyPassiveAbility(Abilities.HYDRATION);
- game.override.ability(Abilities.INTIMIDATE);
- game.override.startingWave(3);
- game.override.enemyMoveset(SPLASH_ONLY);
+ game.override.battleType("single")
+ .enemySpecies(Species.RATTATA)
+ .enemyAbility(Abilities.INTIMIDATE)
+ .enemyPassiveAbility(Abilities.HYDRATION)
+ .ability(Abilities.INTIMIDATE)
+ .startingWave(3)
+ .enemyMoveset(SPLASH_ONLY);
});
- it("single - wild with switch", async () => {
+ it("should lower ATK stat stage by 1 of enemy Pokemon on entry and player switch", async () => {
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
@@ -51,28 +43,30 @@ describe("Abilities - Intimidate", () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
},
- () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
+ () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("TurnInitPhase")
);
- await game.phaseInterceptor.to(CommandPhase, false);
- expect(game.scene.getParty()[0].species.speciesId).toBe(Species.MIGHTYENA);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
+ await game.phaseInterceptor.to("CommandPhase", false);
+
+ let playerPokemon = game.scene.getPlayerPokemon()!;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ expect(playerPokemon.species.speciesId).toBe(Species.MIGHTYENA);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
+
game.doSwitchPokemon(1);
- await game.phaseInterceptor.run(CommandPhase);
- await game.phaseInterceptor.to(CommandPhase);
- expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
+ await game.phaseInterceptor.run("CommandPhase");
+ await game.phaseInterceptor.to("CommandPhase");
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
-
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
+ playerPokemon = game.scene.getPlayerPokemon()!;
+ expect(playerPokemon.species.speciesId).toBe(Species.POOCHYENA);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2);
}, 20000);
- it("single - boss should only trigger once then switch", async () => {
- game.override.startingWave(10);
+ it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", async () => {
+ game.override.battleType("double")
+ .startingWave(3);
await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
@@ -81,262 +75,63 @@ describe("Abilities - Intimidate", () => {
game.setMode(Mode.MESSAGE);
game.endPhase();
},
- () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
+ () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("TurnInitPhase")
);
- await game.phaseInterceptor.to(CommandPhase, false);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
- game.doSwitchPokemon(1);
- await game.phaseInterceptor.run(CommandPhase);
- await game.phaseInterceptor.to(CommandPhase);
- expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
+ await game.phaseInterceptor.to("CommandPhase", false);
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
+ const playerField = game.scene.getPlayerField()!;
+ const enemyField = game.scene.getEnemyField()!;
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
+ expect(enemyField[0].getStatStage(Stat.ATK)).toBe(-2);
+ expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2);
+ expect(playerField[0].getStatStage(Stat.ATK)).toBe(-2);
+ expect(playerField[1].getStatStage(Stat.ATK)).toBe(-2);
}, 20000);
- it("single - trainer should only trigger once with switch", async () => {
- game.override.startingWave(5);
- await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
- game.onNextPrompt(
- "CheckSwitchPhase",
- Mode.CONFIRM,
- () => {
- game.setMode(Mode.MESSAGE);
- game.endPhase();
- },
- () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
- );
- await game.phaseInterceptor.to(CommandPhase, false);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
- game.doSwitchPokemon(1);
- await game.phaseInterceptor.run(CommandPhase);
- await game.phaseInterceptor.to(CommandPhase);
- expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
-
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
-
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
- }, 200000);
-
- it("double - trainer should only trigger once per pokemon", async () => {
- game.override.battleType("double");
- game.override.startingWave(5);
- await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
- game.onNextPrompt(
- "CheckSwitchPhase",
- Mode.CONFIRM,
- () => {
- game.setMode(Mode.MESSAGE);
- game.endPhase();
- },
- () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
- );
- await game.phaseInterceptor.to(CommandPhase, false);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
- const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
- expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
-
- const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
-
- const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats;
- expect(battleStatsPokemon2[BattleStat.ATK]).toBe(-2);
- }, 20000);
-
- it("double - wild: should only trigger once per pokemon", async () => {
- game.override.battleType("double");
- game.override.startingWave(3);
- await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
- game.onNextPrompt(
- "CheckSwitchPhase",
- Mode.CONFIRM,
- () => {
- game.setMode(Mode.MESSAGE);
- game.endPhase();
- },
- () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
- );
- await game.phaseInterceptor.to(CommandPhase, false);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
- const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
- expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
-
- const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
-
- const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats;
- expect(battleStatsPokemon2[BattleStat.ATK]).toBe(-2);
- }, 20000);
-
- it("double - boss: should only trigger once per pokemon", async () => {
- game.override.battleType("double");
- game.override.startingWave(10);
- await game.classicMode.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
- game.onNextPrompt(
- "CheckSwitchPhase",
- Mode.CONFIRM,
- () => {
- game.setMode(Mode.MESSAGE);
- game.endPhase();
- },
- () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
- );
- await game.phaseInterceptor.to(CommandPhase, false);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
- const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
- expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
-
- const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
-
- const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats;
- expect(battleStatsPokemon2[BattleStat.ATK]).toBe(-2);
- }, 20000);
-
- it("single - wild next wave opp triger once, us: none", async () => {
- game.override.startingWave(2);
- game.override.moveset([Moves.AERIAL_ACE]);
- await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
-
- game.move.select(Moves.AERIAL_ACE);
- await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase);
- await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
- expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
- await game.toNextWave();
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
- }, 20000);
-
- it("single - wild next turn - no retrigger on next turn", async () => {
+ it("should not activate again if there is no switch or new entry", async () => {
game.override.startingWave(2);
game.override.moveset([Moves.SPLASH]);
- await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
+ await game.classicMode.startBattle([ Species.MIGHTYENA, Species.POOCHYENA ]);
- game.move.select(Moves.AERIAL_ACE);
- console.log("===to new turn===");
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
+
+ game.move.select(Moves.SPLASH);
await game.toNextTurn();
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
+
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
- it("single - trainer should only trigger once and each time he switch", async () => {
- game.override.moveset([Moves.SPLASH]);
- game.override.enemyMoveset([Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH]);
- game.override.startingWave(5);
- await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
+ it("should lower ATK stat stage by 1 for every switch", async () => {
+ game.override.moveset([Moves.SPLASH])
+ .enemyMoveset(new Array(4).fill(Moves.VOLT_SWITCH))
+ .startingWave(5);
+ await game.classicMode.startBattle([ Species.MIGHTYENA, Species.POOCHYENA ]);
- game.move.select(Moves.AERIAL_ACE);
- console.log("===to new turn===");
- await game.toNextTurn();
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ let enemyPokemon = game.scene.getEnemyPokemon()!;
- game.move.select(Moves.AERIAL_ACE);
- console.log("===to new turn===");
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
+
+ game.move.select(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.toNextTurn();
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-3);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
+
+ enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-2);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
+
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+
+ enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-3);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
}, 200000);
-
- it("single - trainer should only trigger once whatever turn we are", async () => {
- game.override.moveset([Moves.SPLASH]);
- game.override.enemyMoveset(SPLASH_ONLY);
- game.override.startingWave(5);
- await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
-
- game.move.select(Moves.AERIAL_ACE);
- console.log("===to new turn===");
- await game.toNextTurn();
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
-
- game.move.select(Moves.AERIAL_ACE);
- console.log("===to new turn===");
- await game.toNextTurn();
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- }, 20000);
-
- it("double - wild vs only 1 on player side", async () => {
- game.override.battleType("double");
- game.override.startingWave(3);
- await game.classicMode.runToSummon([Species.MIGHTYENA]);
- await game.phaseInterceptor.to(CommandPhase, false);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
- expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
-
- const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
- }, 20000);
-
- it("double - wild vs only 1 alive on player side", async () => {
- game.override.battleType("double");
- game.override.startingWave(3);
- await game.runToTitle();
-
- game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
- game.scene.gameMode = getGameMode(GameModes.CLASSIC);
- const starters = generateStarter(game.scene, [Species.MIGHTYENA, Species.POOCHYENA]);
- const selectStarterPhase = new SelectStarterPhase(game.scene);
- game.scene.pushPhase(new EncounterPhase(game.scene, false));
- selectStarterPhase.initBattle(starters);
- game.scene.getParty()[1].hp = 0;
- game.scene.getParty()[1].status = new Status(StatusEffect.FAINT);
- });
-
- await game.phaseInterceptor.run(EncounterPhase);
-
- await game.phaseInterceptor.to(CommandPhase, false);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
- expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
-
- const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
- }, 20000);
});
diff --git a/src/test/abilities/intrepid_sword.test.ts b/src/test/abilities/intrepid_sword.test.ts
index 18d6c04adbc..7bf0654276c 100644
--- a/src/test/abilities/intrepid_sword.test.ts
+++ b/src/test/abilities/intrepid_sword.test.ts
@@ -1,8 +1,8 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { CommandPhase } from "#app/phases/command-phase";
import { Abilities } from "#enums/abilities";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -29,14 +29,17 @@ describe("Abilities - Intrepid Sword", () => {
game.override.ability(Abilities.INTREPID_SWORD);
});
- it("INTREPID SWORD on player", async() => {
+ it("should raise ATK stat stage by 1 on entry", async() => {
await game.classicMode.runToSummon([
Species.ZACIAN,
]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
await game.phaseInterceptor.to(CommandPhase, false);
- const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(1);
+
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
});
diff --git a/src/test/abilities/moody.test.ts b/src/test/abilities/moody.test.ts
index 9e936e8100a..5c46ea68ec5 100644
--- a/src/test/abilities/moody.test.ts
+++ b/src/test/abilities/moody.test.ts
@@ -1,18 +1,16 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
-import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Abilities - Moody", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
- const battleStatsArray = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD];
-
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
@@ -30,63 +28,61 @@ describe("Abilities - Moody", () => {
.battleType("single")
.enemySpecies(Species.RATTATA)
.enemyAbility(Abilities.BALL_FETCH)
- .enemyPassiveAbility(Abilities.HYDRATION)
.ability(Abilities.MOODY)
.enemyMoveset(SPLASH_ONLY)
.moveset(SPLASH_ONLY);
});
- it(
- "should increase one BattleStat by 2 stages and decrease a different BattleStat by 1 stage",
+ it("should increase one stat stage by 2 and decrease a different stat stage by 1",
async () => {
- await game.startBattle();
+ await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SPLASH);
await game.toNextTurn();
// Find the increased and decreased stats, make sure they are different.
- const statChanges = playerPokemon.summonData.battleStats;
- const changedStats = battleStatsArray.filter(bs => statChanges[bs] === 2 || statChanges[bs] === -1);
+ const changedStats = EFFECTIVE_STATS.filter(s => playerPokemon.getStatStage(s) === 2 || playerPokemon.getStatStage(s) === -1);
expect(changedStats).toBeTruthy();
expect(changedStats.length).toBe(2);
expect(changedStats[0] !== changedStats[1]).toBeTruthy();
});
- it(
- "should only increase one BattleStat by 2 stages if all BattleStats are at -6",
+ it("should only increase one stat stage by 2 if all stat stages are at -6",
async () => {
- await game.startBattle();
+ await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
- // Set all BattleStats to -6
- battleStatsArray.forEach(bs => playerPokemon.summonData.battleStats[bs] = -6);
+
+ // Set all stat stages to -6
+ vi.spyOn(playerPokemon.summonData, "statStages", "get").mockReturnValue(new Array(BATTLE_STATS.length).fill(-6));
game.move.select(Moves.SPLASH);
await game.toNextTurn();
- // Should increase one BattleStat by 2 (from -6, meaning it will be -4)
- const increasedStat = battleStatsArray.filter(bs => playerPokemon.summonData.battleStats[bs] === -4);
+ // Should increase one stat stage by 2 (from -6, meaning it will be -4)
+ const increasedStat = EFFECTIVE_STATS.filter(s => playerPokemon.getStatStage(s) === -4);
expect(increasedStat).toBeTruthy();
expect(increasedStat.length).toBe(1);
});
- it(
- "should only decrease one BattleStat by 1 stage if all BattleStats are at 6",
+ it("should only decrease one stat stage by 1 stage if all stat stages are at 6",
async () => {
- await game.startBattle();
+ await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
- // Set all BattleStats to 6
- battleStatsArray.forEach(bs => playerPokemon.summonData.battleStats[bs] = 6);
+
+ // Set all stat stages to 6
+ vi.spyOn(playerPokemon.summonData, "statStages", "get").mockReturnValue(new Array(BATTLE_STATS.length).fill(6));
game.move.select(Moves.SPLASH);
await game.toNextTurn();
- // Should decrease one BattleStat by 1 (from 6, meaning it will be 5)
- const decreasedStat = battleStatsArray.filter(bs => playerPokemon.summonData.battleStats[bs] === 5);
+ // Should decrease one stat stage by 1 (from 6, meaning it will be 5)
+ const decreasedStat = EFFECTIVE_STATS.filter(s => playerPokemon.getStatStage(s) === 5);
+
expect(decreasedStat).toBeTruthy();
expect(decreasedStat.length).toBe(1);
});
diff --git a/src/test/abilities/moxie.test.ts b/src/test/abilities/moxie.test.ts
index 6a1838c9a98..e713d78f39e 100644
--- a/src/test/abilities/moxie.test.ts
+++ b/src/test/abilities/moxie.test.ts
@@ -1,14 +1,15 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { Stat } from "#app/data/pokemon-stat";
-import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
-import { VictoryPhase } from "#app/phases/victory-phase";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
-
+import { SPLASH_ONLY } from "../utils/testUtils";
+import { BattlerIndex } from "#app/battle";
+import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
+import { VictoryPhase } from "#app/phases/victory-phase";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
describe("Abilities - Moxie", () => {
let phaserGame: Phaser.Game;
@@ -32,23 +33,47 @@ describe("Abilities - Moxie", () => {
game.override.enemyAbility(Abilities.MOXIE);
game.override.ability(Abilities.MOXIE);
game.override.startingLevel(2000);
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
+ game.override.moveset([ moveToUse ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
});
- it("MOXIE", async () => {
+ it("should raise ATK stat stage by 1 when winning a battle", async() => {
const moveToUse = Moves.AERIAL_ACE;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[Stat.ATK]).toBe(0);
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
game.move.select(moveToUse);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase);
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
+
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
+ }, 20000);
+
+ // TODO: Activate this test when MOXIE is corrected to work on faint and not on battle victory
+ it.todo("should raise ATK stat stage by 1 when defeating an ally Pokemon", async() => {
+ game.override.battleType("double");
+ const moveToUse = Moves.AERIAL_ACE;
+ await game.startBattle([
+ Species.MIGHTYENA,
+ Species.MIGHTYENA,
+ ]);
+
+ const [ firstPokemon, secondPokemon ] = game.scene.getPlayerField();
+
+ expect(firstPokemon.getStatStage(Stat.ATK)).toBe(0);
+
+ secondPokemon.hp = 1;
+
+ game.move.select(moveToUse);
+ game.selectTarget(BattlerIndex.PLAYER_2);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(firstPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
});
diff --git a/src/test/abilities/mycelium_might.test.ts b/src/test/abilities/mycelium_might.test.ts
index 83396f7950f..d8947935880 100644
--- a/src/test/abilities/mycelium_might.test.ts
+++ b/src/test/abilities/mycelium_might.test.ts
@@ -1,14 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { MovePhase } from "#app/phases/move-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { TurnStartPhase } from "#app/phases/turn-start-phase";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
+import { Stat } from "#enums/stat";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
-
describe("Abilities - Mycelium Might", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@@ -35,7 +34,7 @@ describe("Abilities - Mycelium Might", () => {
});
/**
- * Bulbapedia References:
+ * References:
* https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(Ability)
* https://bulbapedia.bulbagarden.net/wiki/Priority
* https://www.smogon.com/forums/threads/scarlet-violet-battle-mechanics-research.3709545/page-24
@@ -44,62 +43,65 @@ describe("Abilities - Mycelium Might", () => {
it("will move last in its priority bracket and ignore protective abilities", async () => {
await game.startBattle([Species.REGIELEKI]);
- const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
const enemyPokemon = game.scene.getEnemyPokemon();
+ const playerIndex = game.scene.getPlayerPokemon()?.getBattlerIndex();
const enemyIndex = enemyPokemon?.getBattlerIndex();
game.move.select(Moves.BABY_DOLL_EYES);
- await game.phaseInterceptor.to(MovePhase, false);
+ await game.phaseInterceptor.to(TurnStartPhase, false);
+ const phase = game.scene.getCurrentPhase() as TurnStartPhase;
+ const speedOrder = phase.getSpeedOrder();
+ const commandOrder = phase.getCommandOrder();
// The opponent Pokemon (without Mycelium Might) goes first despite having lower speed than the player Pokemon.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
-
- await game.phaseInterceptor.run(MovePhase);
- await game.phaseInterceptor.to(MovePhase, false);
-
// The player Pokemon (with Mycelium Might) goes last despite having higher speed than the opponent.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
+ expect(speedOrder).toEqual([playerIndex, enemyIndex]);
+ expect(commandOrder).toEqual([enemyIndex, playerIndex]);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
+
+ // Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced.
+ expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
it("will still go first if a status move that is in a higher priority bracket than the opponent's move is used", async () => {
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
await game.startBattle([Species.REGIELEKI]);
- const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
const enemyPokemon = game.scene.getEnemyPokemon();
+ const playerIndex = game.scene.getPlayerPokemon()?.getBattlerIndex();
const enemyIndex = enemyPokemon?.getBattlerIndex();
game.move.select(Moves.BABY_DOLL_EYES);
- await game.phaseInterceptor.to(MovePhase, false);
+ await game.phaseInterceptor.to(TurnStartPhase, false);
+ const phase = game.scene.getCurrentPhase() as TurnStartPhase;
+ const speedOrder = phase.getSpeedOrder();
+ const commandOrder = phase.getCommandOrder();
// The player Pokemon (with M.M.) goes first because its move is still within a higher priority bracket than its opponent.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
-
- await game.phaseInterceptor.run(MovePhase);
- await game.phaseInterceptor.to(MovePhase, false);
// The enemy Pokemon goes second because its move is in a lower priority bracket.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
+ expect(speedOrder).toEqual([playerIndex, enemyIndex]);
+ expect(commandOrder).toEqual([playerIndex, enemyIndex]);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
+ // Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced.
+ expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
it("will not affect non-status moves", async () => {
await game.startBattle([Species.REGIELEKI]);
- const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
+ const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
game.move.select(Moves.QUICK_ATTACK);
- await game.phaseInterceptor.to(MovePhase, false);
+ await game.phaseInterceptor.to(TurnStartPhase, false);
+ const phase = game.scene.getCurrentPhase() as TurnStartPhase;
+ const speedOrder = phase.getSpeedOrder();
+ const commandOrder = phase.getCommandOrder();
// The player Pokemon (with M.M.) goes first because it has a higher speed and did not use a status move.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
-
- await game.phaseInterceptor.run(MovePhase);
- await game.phaseInterceptor.to(MovePhase, false);
// The enemy Pokemon (without M.M.) goes second because its speed is lower.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
+ // This means that the commandOrder should be identical to the speedOrder
+ expect(speedOrder).toEqual([playerIndex, enemyIndex]);
+ expect(commandOrder).toEqual([playerIndex, enemyIndex]);
}, 20000);
});
diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts
index 1404f597ccf..e3c6c8ec5bb 100644
--- a/src/test/abilities/parental_bond.test.ts
+++ b/src/test/abilities/parental_bond.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { StatusEffect } from "#app/data/status-effect";
import { Type } from "#app/data/type";
import { BattlerTagType } from "#app/enums/battler-tag-type";
@@ -96,7 +96,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
}, TIMEOUT
);
@@ -116,7 +116,7 @@ describe("Abilities - Parental Bond", () => {
game.move.select(Moves.BABY_DOLL_EYES);
await game.phaseInterceptor.to(BerryPhase, false);
- expect(enemyPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, TIMEOUT
);
@@ -568,7 +568,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, TIMEOUT
);
@@ -590,7 +590,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- expect(enemyPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(1);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, TIMEOUT
);
diff --git a/src/test/abilities/sand_veil.test.ts b/src/test/abilities/sand_veil.test.ts
index 2336e2b50de..da9fdcc01ab 100644
--- a/src/test/abilities/sand_veil.test.ts
+++ b/src/test/abilities/sand_veil.test.ts
@@ -1,5 +1,5 @@
-import { BattleStatMultiplierAbAttr, allAbilities } from "#app/data/ability";
-import { BattleStat } from "#app/data/battle-stat";
+import { StatMultiplierAbAttr, allAbilities } from "#app/data/ability";
+import { Stat } from "#enums/stat";
import { WeatherType } from "#app/data/weather";
import { CommandPhase } from "#app/phases/command-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
@@ -49,10 +49,10 @@ describe("Abilities - Sand Veil", () => {
vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[Abilities.SAND_VEIL]);
- const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(BattleStatMultiplierAbAttr)[0];
- vi.spyOn(sandVeilAttr, "applyBattleStat").mockImplementation(
- (pokemon, passive, simulated, battleStat, statValue, args) => {
- if (battleStat === BattleStat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
+ const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(StatMultiplierAbAttr)[0];
+ vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation(
+ (_pokemon, _passive, _simulated, stat, statValue, _args) => {
+ if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
statValue.value *= -1; // will make all attacks miss
return true;
}
diff --git a/src/test/abilities/sap_sipper.test.ts b/src/test/abilities/sap_sipper.test.ts
index f9c20e85eab..2d70ede3530 100644
--- a/src/test/abilities/sap_sipper.test.ts
+++ b/src/test/abilities/sap_sipper.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { TerrainType } from "#app/data/terrain";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
@@ -9,6 +9,7 @@ import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
// See also: TypeImmunityAbAttr
describe("Abilities - Sap Sipper", () => {
@@ -31,52 +32,55 @@ describe("Abilities - Sap Sipper", () => {
game.override.disableCrits();
});
- it("raise attack 1 level and block effects when activated against a grass attack", async () => {
+ it("raises ATK stat stage by 1 and block effects when activated against a grass attack", async() => {
const moveToUse = Moves.LEAFAGE;
const enemyAbility = Abilities.SAP_SIPPER;
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
+ game.override.moveset([ moveToUse ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.DUSKULL);
game.override.enemyAbility(enemyAbility);
await game.startBattle();
- const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ const initialEnemyHp = enemyPokemon.hp;
game.move.select(moveToUse);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
- expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
+ expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
- it("raise attack 1 level and block effects when activated against a grass status move", async () => {
+ it("raises ATK stat stage by 1 and block effects when activated against a grass status move", async() => {
const moveToUse = Moves.SPORE;
const enemyAbility = Abilities.SAP_SIPPER;
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
+ game.override.moveset([ moveToUse ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility);
await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
game.move.select(moveToUse);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(game.scene.getEnemyParty()[0].status).toBeUndefined();
- expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
+ expect(enemyPokemon.status).toBeUndefined();
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
it("do not activate against status moves that target the field", async () => {
const moveToUse = Moves.GRASSY_TERRAIN;
const enemyAbility = Abilities.SAP_SIPPER;
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
+ game.override.moveset([ moveToUse ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility);
@@ -88,51 +92,54 @@ describe("Abilities - Sap Sipper", () => {
expect(game.scene.arena.terrain).toBeDefined();
expect(game.scene.arena.terrain!.terrainType).toBe(TerrainType.GRASSY);
- expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(0);
});
it("activate once against multi-hit grass attacks", async () => {
const moveToUse = Moves.BULLET_SEED;
const enemyAbility = Abilities.SAP_SIPPER;
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
+ game.override.moveset([ moveToUse ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility);
await game.startBattle();
- const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ const initialEnemyHp = enemyPokemon.hp;
game.move.select(moveToUse);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
- expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
+ expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
it("do not activate against status moves that target the user", async () => {
const moveToUse = Moves.SPIKY_SHIELD;
const ability = Abilities.SAP_SIPPER;
- game.override.moveset([moveToUse]);
+ game.override.moveset([ moveToUse ]);
game.override.ability(ability);
- game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
+ game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(Abilities.NONE);
await game.startBattle();
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+
game.move.select(moveToUse);
await game.phaseInterceptor.to(MoveEndPhase);
- expect(game.scene.getParty()[0].getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
+ expect(playerPokemon.getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
await game.phaseInterceptor.to(TurnEndPhase);
- expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
});
@@ -149,13 +156,14 @@ describe("Abilities - Sap Sipper", () => {
await game.startBattle();
- const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ const initialEnemyHp = enemyPokemon.hp;
game.move.select(moveToUse);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
- expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
+ expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
});
diff --git a/src/test/abilities/serene_grace.test.ts b/src/test/abilities/serene_grace.test.ts
index 7316b2ea920..e06288b9de9 100644
--- a/src/test/abilities/serene_grace.test.ts
+++ b/src/test/abilities/serene_grace.test.ts
@@ -1,6 +1,6 @@
import { BattlerIndex } from "#app/battle";
import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import * as Utils from "#app/utils";
import { Abilities } from "#enums/abilities";
diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts
index f73b749dac2..69b47e1eaae 100644
--- a/src/test/abilities/sheer_force.test.ts
+++ b/src/test/abilities/sheer_force.test.ts
@@ -1,6 +1,6 @@
import { BattlerIndex } from "#app/battle";
import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability";
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import * as Utils from "#app/utils";
import { Abilities } from "#enums/abilities";
diff --git a/src/test/abilities/shield_dust.test.ts b/src/test/abilities/shield_dust.test.ts
index 14770c49427..8a0b769827d 100644
--- a/src/test/abilities/shield_dust.test.ts
+++ b/src/test/abilities/shield_dust.test.ts
@@ -1,6 +1,6 @@
import { BattlerIndex } from "#app/battle";
import { applyAbAttrs, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import * as Utils from "#app/utils";
import { Abilities } from "#enums/abilities";
diff --git a/src/test/abilities/simple.test.ts b/src/test/abilities/simple.test.ts
new file mode 100644
index 00000000000..4310c5d45d1
--- /dev/null
+++ b/src/test/abilities/simple.test.ts
@@ -0,0 +1,42 @@
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
+import { Abilities } from "#enums/abilities";
+import { Species } from "#enums/species";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Abilities - Simple", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.BULBASAUR)
+ .enemyAbility(Abilities.SIMPLE)
+ .ability(Abilities.INTIMIDATE)
+ .enemyMoveset(SPLASH_ONLY);
+ });
+
+ it("should double stat changes when applied", async() => {
+ await game.startBattle([
+ Species.SLOWBRO
+ ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2);
+ }, 20000);
+});
diff --git a/src/test/abilities/stall.test.ts b/src/test/abilities/stall.test.ts
index d8dbe9d0e06..7baf7c846f0 100644
--- a/src/test/abilities/stall.test.ts
+++ b/src/test/abilities/stall.test.ts
@@ -1,11 +1,10 @@
-import { MovePhase } from "#app/phases/move-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
-
+import { TurnStartPhase } from "#app/phases/turn-start-phase";
describe("Abilities - Stall", () => {
let phaserGame: Phaser.Game;
@@ -32,7 +31,7 @@ describe("Abilities - Stall", () => {
});
/**
- * Bulbapedia References:
+ * References:
* https://bulbapedia.bulbagarden.net/wiki/Stall_(Ability)
* https://bulbapedia.bulbagarden.net/wiki/Priority
**/
@@ -40,55 +39,56 @@ describe("Abilities - Stall", () => {
it("Pokemon with Stall should move last in its priority bracket regardless of speed", async () => {
await game.startBattle([Species.SHUCKLE]);
- const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
+ const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
game.move.select(Moves.QUICK_ATTACK);
- await game.phaseInterceptor.to(MovePhase, false);
+ await game.phaseInterceptor.to(TurnStartPhase, false);
+ const phase = game.scene.getCurrentPhase() as TurnStartPhase;
+ const speedOrder = phase.getSpeedOrder();
+ const commandOrder = phase.getCommandOrder();
// The player Pokemon (without Stall) goes first despite having lower speed than the opponent.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
-
- await game.phaseInterceptor.run(MovePhase);
- await game.phaseInterceptor.to(MovePhase, false);
// The opponent Pokemon (with Stall) goes last despite having higher speed than the player Pokemon.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
+ expect(speedOrder).toEqual([enemyIndex, playerIndex]);
+ expect(commandOrder).toEqual([playerIndex, enemyIndex]);
}, 20000);
it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async () => {
await game.startBattle([Species.SHUCKLE]);
- const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
+ const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
game.move.select(Moves.TACKLE);
- await game.phaseInterceptor.to(MovePhase, false);
+ await game.phaseInterceptor.to(TurnStartPhase, false);
+ const phase = game.scene.getCurrentPhase() as TurnStartPhase;
+ const speedOrder = phase.getSpeedOrder();
+ const commandOrder = phase.getCommandOrder();
// The opponent Pokemon (with Stall) goes first because its move is still within a higher priority bracket than its opponent.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
-
- await game.phaseInterceptor.run(MovePhase);
- await game.phaseInterceptor.to(MovePhase, false);
// The player Pokemon goes second because its move is in a lower priority bracket.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
+ expect(speedOrder).toEqual([enemyIndex, playerIndex]);
+ expect(commandOrder).toEqual([enemyIndex, playerIndex]);
}, 20000);
it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async () => {
game.override.ability(Abilities.STALL);
await game.startBattle([Species.SHUCKLE]);
- const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
+ const playerIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
game.move.select(Moves.TACKLE);
- await game.phaseInterceptor.to(MovePhase, false);
- // The opponent Pokemon (with Stall) goes first because it has a higher speed.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
+ await game.phaseInterceptor.to(TurnStartPhase, false);
+ const phase = game.scene.getCurrentPhase() as TurnStartPhase;
+ const speedOrder = phase.getSpeedOrder();
+ const commandOrder = phase.getCommandOrder();
- await game.phaseInterceptor.run(MovePhase);
- await game.phaseInterceptor.to(MovePhase, false);
+ // The opponent Pokemon (with Stall) goes first because it has a higher speed.
// The player Pokemon (with Stall) goes second because its speed is lower.
- expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
+ expect(speedOrder).toEqual([enemyIndex, playerIndex]);
+ expect(commandOrder).toEqual([enemyIndex, playerIndex]);
}, 20000);
});
diff --git a/src/test/abilities/steely_spirit.test.ts b/src/test/abilities/steely_spirit.test.ts
index c632d0be777..7aaa0a42ae3 100644
--- a/src/test/abilities/steely_spirit.test.ts
+++ b/src/test/abilities/steely_spirit.test.ts
@@ -1,7 +1,6 @@
import { allAbilities } from "#app/data/ability";
import { allMoves } from "#app/data/move";
import { Abilities } from "#app/enums/abilities";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
@@ -37,7 +36,7 @@ describe("Abilities - Steely Spirit", () => {
});
it("increases Steel-type moves' power used by the user and its allies by 50%", async () => {
- await game.startBattle([Species.PIKACHU, Species.SHUCKLE]);
+ await game.classicMode.startBattle([Species.PIKACHU, Species.SHUCKLE]);
const boostSource = game.scene.getPlayerField()[1];
const enemyToCheck = game.scene.getEnemyPokemon()!;
@@ -47,13 +46,13 @@ describe("Abilities - Steely Spirit", () => {
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
game.move.select(Moves.SPLASH, 1);
- await game.phaseInterceptor.to(MoveEffectPhase);
+ await game.phaseInterceptor.to("MoveEffectPhase");
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * steelySpiritMultiplier);
});
it("stacks if multiple users with this ability are on the field.", async () => {
- await game.startBattle([Species.PIKACHU, Species.PIKACHU]);
+ await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]);
const enemyToCheck = game.scene.getEnemyPokemon()!;
game.scene.getPlayerField().forEach(p => {
@@ -64,13 +63,13 @@ describe("Abilities - Steely Spirit", () => {
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
game.move.select(moveToCheck, 1, enemyToCheck.getBattlerIndex());
- await game.phaseInterceptor.to(MoveEffectPhase);
+ await game.phaseInterceptor.to("MoveEffectPhase");
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2));
});
it("does not take effect when suppressed", async () => {
- await game.startBattle([Species.PIKACHU, Species.SHUCKLE]);
+ await game.classicMode.startBattle([Species.PIKACHU, Species.SHUCKLE]);
const boostSource = game.scene.getPlayerField()[1];
const enemyToCheck = game.scene.getEnemyPokemon()!;
@@ -84,8 +83,25 @@ describe("Abilities - Steely Spirit", () => {
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
game.move.select(Moves.SPLASH, 1);
- await game.phaseInterceptor.to(MoveEffectPhase);
+ await game.phaseInterceptor.to("MoveEffectPhase");
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower);
});
+
+ it("affects variable-type moves if their resolved type is Steel", async () => {
+ game.override
+ .ability(Abilities.STEELY_SPIRIT)
+ .moveset([Moves.REVELATION_DANCE]);
+
+ const revelationDance = allMoves[Moves.REVELATION_DANCE];
+ vi.spyOn(revelationDance, "calculateBattlePower");
+
+ await game.classicMode.startBattle([Species.KLINKLANG]);
+
+ game.move.select(Moves.REVELATION_DANCE);
+
+ await game.phaseInterceptor.to("MoveEffectPhase");
+
+ expect(revelationDance.calculateBattlePower).toHaveReturnedWith(revelationDance.power * 1.5);
+ });
});
diff --git a/src/test/abilities/tera_shell.test.ts b/src/test/abilities/tera_shell.test.ts
new file mode 100644
index 00000000000..f9cb2935619
--- /dev/null
+++ b/src/test/abilities/tera_shell.test.ts
@@ -0,0 +1,111 @@
+import { Abilities } from "#app/enums/abilities";
+import { Moves } from "#app/enums/moves";
+import { Species } from "#app/enums/species";
+import { HitResult } from "#app/field/pokemon.js";
+import GameManager from "#test/utils/gameManager";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+
+const TIMEOUT = 10 * 1000; // 10 second timeout
+
+describe("Abilities - Tera Shell", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .ability(Abilities.TERA_SHELL)
+ .moveset([Moves.SPLASH])
+ .enemySpecies(Species.SNORLAX)
+ .enemyAbility(Abilities.INSOMNIA)
+ .enemyMoveset(Array(4).fill(Moves.MACH_PUNCH))
+ .startingLevel(100)
+ .enemyLevel(100);
+ });
+
+ it(
+ "should change the effectiveness of non-resisted attacks when the source is at full HP",
+ async () => {
+ await game.classicMode.startBattle([Species.SNORLAX]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ vi.spyOn(playerPokemon, "getMoveEffectiveness");
+
+ game.move.select(Moves.SPLASH);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0.5);
+
+ await game.toNextTurn();
+
+ game.move.select(Moves.SPLASH);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(2);
+ }, TIMEOUT
+ );
+
+ it(
+ "should not override type immunities",
+ async () => {
+ game.override.enemyMoveset(Array(4).fill(Moves.SHADOW_SNEAK));
+
+ await game.classicMode.startBattle([Species.SNORLAX]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ vi.spyOn(playerPokemon, "getMoveEffectiveness");
+
+ game.move.select(Moves.SPLASH);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0);
+ }, TIMEOUT
+ );
+
+ it(
+ "should not override type multipliers less than 0.5x",
+ async () => {
+ game.override.enemyMoveset(Array(4).fill(Moves.QUICK_ATTACK));
+
+ await game.classicMode.startBattle([Species.AGGRON]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ vi.spyOn(playerPokemon, "getMoveEffectiveness");
+
+ game.move.select(Moves.SPLASH);
+
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0.25);
+ }, TIMEOUT
+ );
+
+ it(
+ "should not affect the effectiveness of fixed-damage moves",
+ async () => {
+ game.override.enemyMoveset(Array(4).fill(Moves.DRAGON_RAGE));
+
+ await game.classicMode.startBattle([Species.CHARIZARD]);
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ vi.spyOn(playerPokemon, "apply");
+
+ game.move.select(Moves.SPLASH);
+
+ await game.phaseInterceptor.to("BerryPhase", false);
+ expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.EFFECTIVE);
+ expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40);
+ }, TIMEOUT
+ );
+});
diff --git a/src/test/abilities/volt_absorb.test.ts b/src/test/abilities/volt_absorb.test.ts
index d9c3fe34c24..7f3e160c7d0 100644
--- a/src/test/abilities/volt_absorb.test.ts
+++ b/src/test/abilities/volt_absorb.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
@@ -41,12 +41,14 @@ describe("Abilities - Volt Absorb", () => {
await game.startBattle();
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+
game.move.select(moveToUse);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.SPDEF]).toBe(1);
- expect(game.scene.getParty()[0].getTag(BattlerTagType.CHARGED)).toBeDefined();
+ expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(1);
+ expect(playerPokemon.getTag(BattlerTagType.CHARGED)).toBeDefined();
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
});
});
diff --git a/src/test/abilities/wind_rider.test.ts b/src/test/abilities/wind_rider.test.ts
index 97e2e6456dc..7a1fee6794a 100644
--- a/src/test/abilities/wind_rider.test.ts
+++ b/src/test/abilities/wind_rider.test.ts
@@ -1,9 +1,8 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@@ -24,94 +23,99 @@ describe("Abilities - Wind Rider", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.battleType("single");
- game.override.enemySpecies(Species.SHIFTRY);
- game.override.enemyAbility(Abilities.WIND_RIDER);
- game.override.moveset([Moves.TAILWIND, Moves.SPLASH, Moves.PETAL_BLIZZARD, Moves.SANDSTORM]);
- game.override.enemyMoveset(SPLASH_ONLY);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.SHIFTRY)
+ .enemyAbility(Abilities.WIND_RIDER)
+ .moveset([Moves.TAILWIND, Moves.SPLASH, Moves.PETAL_BLIZZARD, Moves.SANDSTORM])
+ .enemyMoveset(SPLASH_ONLY);
});
- it("takes no damage from wind moves and its Attack is increased by one stage when hit by one", async () => {
- await game.startBattle([Species.MAGIKARP]);
+ it("takes no damage from wind moves and its ATK stat stage is raised by 1 when hit by one", async () => {
+ await game.classicMode.startBattle([ Species.MAGIKARP ]);
const shiftry = game.scene.getEnemyPokemon()!;
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
game.move.select(Moves.PETAL_BLIZZARD);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
expect(shiftry.isFullHp()).toBe(true);
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
});
- it("Attack is increased by one stage when Tailwind is present on its side", async () => {
- game.override.ability(Abilities.WIND_RIDER);
- game.override.enemySpecies(Species.MAGIKARP);
+ it("ATK stat stage is raised by 1 when Tailwind is present on its side", async () => {
+ game.override
+ .enemySpecies(Species.MAGIKARP)
+ .ability(Abilities.WIND_RIDER);
- await game.startBattle([Species.SHIFTRY]);
+ await game.classicMode.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon()!;
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
game.move.select(Moves.TAILWIND);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
});
- it("does not increase Attack when Tailwind is present on opposing side", async () => {
- game.override.ability(Abilities.WIND_RIDER);
- game.override.enemySpecies(Species.MAGIKARP);
+ it("does not raise ATK stat stage when Tailwind is present on opposing side", async () => {
+ game.override
+ .enemySpecies(Species.MAGIKARP)
+ .ability(Abilities.WIND_RIDER);
- await game.startBattle([Species.SHIFTRY]);
+ await game.classicMode.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon()!;
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
+ expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
game.move.select(Moves.TAILWIND);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
- expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
+ expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
});
- it("does not increase Attack when Tailwind is present on opposing side", async () => {
- game.override.enemySpecies(Species.MAGIKARP);
+ it("does not raise ATK stat stage when Tailwind is present on opposing side", async () => {
+ game.override
+ .enemySpecies(Species.MAGIKARP)
+ .ability(Abilities.WIND_RIDER);
- await game.startBattle([Species.SHIFTRY]);
+ await game.classicMode.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon()!;
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
+ expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
game.move.select(Moves.TAILWIND);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
- expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
+ expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
});
it("does not interact with Sandstorm", async () => {
game.override.enemySpecies(Species.MAGIKARP);
- await game.startBattle([Species.SHIFTRY]);
+ await game.classicMode.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon()!;
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(shiftry.isFullHp()).toBe(true);
game.move.select(Moves.SANDSTORM);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
- expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(shiftry.hp).lessThan(shiftry.getMaxHp());
});
});
diff --git a/src/test/abilities/zen_mode.test.ts b/src/test/abilities/zen_mode.test.ts
index 677d998e876..fd378647184 100644
--- a/src/test/abilities/zen_mode.test.ts
+++ b/src/test/abilities/zen_mode.test.ts
@@ -1,6 +1,5 @@
+import { Stat } from "#enums/stat";
import { BattlerIndex } from "#app/battle";
-import { Stat } from "#app/data/pokemon-stat";
-import { Status, StatusEffect } from "#app/data/status-effect";
import { DamagePhase } from "#app/phases/damage-phase";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { MessagePhase } from "#app/phases/message-phase";
@@ -18,6 +17,7 @@ import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
+import { Status, StatusEffect } from "#app/data/status-effect";
const TIMEOUT = 20 * 1000;
diff --git a/src/test/achievements/achievement.test.ts b/src/test/achievements/achievement.test.ts
index 36c20ae2248..24d00a3e77b 100644
--- a/src/test/achievements/achievement.test.ts
+++ b/src/test/achievements/achievement.test.ts
@@ -224,7 +224,7 @@ describe("achvs", () => {
expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._100_RIBBONS).toBeInstanceOf(RibbonAchv);
- expect(achvs.TRANSFER_MAX_BATTLE_STAT).toBeInstanceOf(Achv);
+ expect(achvs.TRANSFER_MAX_STAT_STAGE).toBeInstanceOf(Achv);
expect(achvs.MAX_FRIENDSHIP).toBeInstanceOf(Achv);
expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv);
expect(achvs.GIGANTAMAX).toBeInstanceOf(Achv);
diff --git a/src/test/battle-scene.test.ts b/src/test/battle-scene.test.ts
index 9e28ec99791..4da75cea197 100644
--- a/src/test/battle-scene.test.ts
+++ b/src/test/battle-scene.test.ts
@@ -1,5 +1,5 @@
import { LoadingScene } from "#app/loading-scene";
-import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import GameManager from "./utils/gameManager";
describe("BattleScene", () => {
@@ -24,4 +24,12 @@ describe("BattleScene", () => {
// `BattleScene.create()` is called during the `new GameManager()` call
expect(game.scene.scene.remove).toHaveBeenCalledWith(LoadingScene.KEY);
});
+
+ it("should also reset RNG on reset", () => {
+ vi.spyOn(game.scene, "resetSeed");
+
+ game.scene.reset();
+
+ expect(game.scene.resetSeed).toHaveBeenCalled();
+ });
});
diff --git a/src/test/battle-stat.spec.ts b/src/test/battle-stat.spec.ts
deleted file mode 100644
index 16fce962838..00000000000
--- a/src/test/battle-stat.spec.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "#app/data/battle-stat";
-import { describe, expect, it } from "vitest";
-import { arrayOfRange, mockI18next } from "./utils/testUtils";
-
-const TEST_BATTLE_STAT = -99 as unknown as BattleStat;
-const TEST_POKEMON = "Testmon";
-const TEST_STAT = "Teststat";
-
-describe("battle-stat", () => {
- describe("getBattleStatName", () => {
- it("should return the correct name for each BattleStat", () => {
- mockI18next();
-
- expect(getBattleStatName(BattleStat.ATK)).toBe("pokemonInfo:Stat.ATK");
- expect(getBattleStatName(BattleStat.DEF)).toBe("pokemonInfo:Stat.DEF");
- expect(getBattleStatName(BattleStat.SPATK)).toBe(
- "pokemonInfo:Stat.SPATK"
- );
- expect(getBattleStatName(BattleStat.SPDEF)).toBe(
- "pokemonInfo:Stat.SPDEF"
- );
- expect(getBattleStatName(BattleStat.SPD)).toBe("pokemonInfo:Stat.SPD");
- expect(getBattleStatName(BattleStat.ACC)).toBe("pokemonInfo:Stat.ACC");
- expect(getBattleStatName(BattleStat.EVA)).toBe("pokemonInfo:Stat.EVA");
- });
-
- it("should fall back to ??? for an unknown BattleStat", () => {
- expect(getBattleStatName(TEST_BATTLE_STAT)).toBe("???");
- });
- });
-
- describe("getBattleStatLevelChangeDescription", () => {
- it("should return battle:statRose for +1", () => {
- mockI18next();
-
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- 1,
- true
- );
-
- expect(message).toBe("battle:statRose");
- });
-
- it("should return battle:statSharplyRose for +2", () => {
- mockI18next();
-
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- 2,
- true
- );
-
- expect(message).toBe("battle:statSharplyRose");
- });
-
- it("should return battle:statRoseDrastically for +3 to +6", () => {
- mockI18next();
-
- arrayOfRange(3, 6).forEach((n) => {
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- n,
- true
- );
-
- expect(message).toBe("battle:statRoseDrastically");
- });
- });
-
- it("should return battle:statWontGoAnyHigher for 7 or higher", () => {
- mockI18next();
-
- arrayOfRange(7, 10).forEach((n) => {
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- n,
- true
- );
-
- expect(message).toBe("battle:statWontGoAnyHigher");
- });
- });
-
- it("should return battle:statFell for -1", () => {
- mockI18next();
-
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- 1,
- false
- );
-
- expect(message).toBe("battle:statFell");
- });
-
- it("should return battle:statHarshlyFell for -2", () => {
- mockI18next();
-
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- 2,
- false
- );
-
- expect(message).toBe("battle:statHarshlyFell");
- });
-
- it("should return battle:statSeverelyFell for -3 to -6", () => {
- mockI18next();
-
- arrayOfRange(3, 6).forEach((n) => {
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- n,
- false
- );
-
- expect(message).toBe("battle:statSeverelyFell");
- });
- });
-
- it("should return battle:statWontGoAnyLower for -7 or lower", () => {
- mockI18next();
-
- arrayOfRange(7, 10).forEach((n) => {
- const message = getBattleStatLevelChangeDescription(
- TEST_POKEMON,
- TEST_STAT,
- n,
- false
- );
-
- expect(message).toBe("battle:statWontGoAnyLower");
- });
- });
- });
-});
diff --git a/src/test/battle/battle-order.test.ts b/src/test/battle/battle-order.test.ts
index 0129ecad254..e19168962dc 100644
--- a/src/test/battle/battle-order.test.ts
+++ b/src/test/battle/battle-order.test.ts
@@ -1,4 +1,3 @@
-import { Stat } from "#app/data/pokemon-stat";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { SelectTargetPhase } from "#app/phases/select-target-phase";
import { TurnStartPhase } from "#app/phases/turn-start-phase";
@@ -7,8 +6,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
-import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
-
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Battle order", () => {
let phaserGame: Phaser.Game;
@@ -37,30 +35,42 @@ describe("Battle order", () => {
await game.startBattle([
Species.BULBASAUR,
]);
- game.scene.getParty()[0].stats[Stat.SPD] = 50;
- game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150;
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50]); // set playerPokemon's speed to 50
+ vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set enemyPokemon's speed to 150
game.move.select(Moves.TACKLE);
await game.phaseInterceptor.run(EnemyCommandPhase);
+
+ const playerPokemonIndex = playerPokemon.getBattlerIndex();
+ const enemyPokemonIndex = enemyPokemon.getBattlerIndex();
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
- const order = phase.getOrder();
- expect(order[0]).toBe(2);
- expect(order[1]).toBe(0);
+ const order = phase.getCommandOrder();
+ expect(order[0]).toBe(enemyPokemonIndex);
+ expect(order[1]).toBe(playerPokemonIndex);
}, 20000);
it("Player faster than opponent 150 vs 50", async () => {
await game.startBattle([
Species.BULBASAUR,
]);
- game.scene.getParty()[0].stats[Stat.SPD] = 150;
- game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 50;
+
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ vi.spyOn(playerPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set playerPokemon's speed to 150
+ vi.spyOn(enemyPokemon, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50]); // set enemyPokemon's speed to 50
game.move.select(Moves.TACKLE);
await game.phaseInterceptor.run(EnemyCommandPhase);
+
+ const playerPokemonIndex = playerPokemon.getBattlerIndex();
+ const enemyPokemonIndex = enemyPokemon.getBattlerIndex();
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
- const order = phase.getOrder();
- expect(order[0]).toBe(0);
- expect(order[1]).toBe(2);
+ const order = phase.getCommandOrder();
+ expect(order[0]).toBe(playerPokemonIndex);
+ expect(order[1]).toBe(enemyPokemonIndex);
}, 20000);
it("double - both opponents faster than player 50/50 vs 150/150", async () => {
@@ -69,20 +79,25 @@ describe("Battle order", () => {
Species.BULBASAUR,
Species.BLASTOISE,
]);
- game.scene.getParty()[0].stats[Stat.SPD] = 50;
- game.scene.getParty()[1].stats[Stat.SPD] = 50;
- game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 150;
- game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
+
+ const playerPokemon = game.scene.getPlayerField();
+ const enemyPokemon = game.scene.getEnemyField();
+
+ playerPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 50])); // set both playerPokemons' speed to 50
+ enemyPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150])); // set both enemyPokemons' speed to 150
+ const playerIndices = playerPokemon.map(p => p?.getBattlerIndex());
+ const enemyIndices = enemyPokemon.map(p => p?.getBattlerIndex());
game.move.select(Moves.TACKLE);
game.move.select(Moves.TACKLE, 1);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
+
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
- const order = phase.getOrder();
- expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(2));
- expect(order.indexOf(0)).toBeGreaterThan(order.indexOf(3));
- expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(2));
- expect(order.indexOf(1)).toBeGreaterThan(order.indexOf(3));
+ const order = phase.getCommandOrder();
+ expect(order.slice(0, 2).includes(enemyIndices[0])).toBe(true);
+ expect(order.slice(0, 2).includes(enemyIndices[1])).toBe(true);
+ expect(order.slice(2, 4).includes(playerIndices[0])).toBe(true);
+ expect(order.slice(2, 4).includes(playerIndices[1])).toBe(true);
}, 20000);
it("double - speed tie except 1 - 100/100 vs 100/150", async () => {
@@ -91,19 +106,25 @@ describe("Battle order", () => {
Species.BULBASAUR,
Species.BLASTOISE,
]);
- game.scene.getParty()[0].stats[Stat.SPD] = 100;
- game.scene.getParty()[1].stats[Stat.SPD] = 100;
- game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100;
- game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
+
+ const playerPokemon = game.scene.getPlayerField();
+ const enemyPokemon = game.scene.getEnemyField();
+ playerPokemon.forEach(p => vi.spyOn(p, "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100])); //set both playerPokemons' speed to 100
+ vi.spyOn(enemyPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100]); // set enemyPokemon's speed to 100
+ vi.spyOn(enemyPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set enemyPokemon's speed to 150
+ const playerIndices = playerPokemon.map(p => p?.getBattlerIndex());
+ const enemyIndices = enemyPokemon.map(p => p?.getBattlerIndex());
game.move.select(Moves.TACKLE);
game.move.select(Moves.TACKLE, 1);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
+
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
- const order = phase.getOrder();
- expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
- expect(order.indexOf(3)).toBeLessThan(order.indexOf(1));
- expect(order.indexOf(3)).toBeLessThan(order.indexOf(2));
+ const order = phase.getCommandOrder();
+ expect(order[0]).toBe(enemyIndices[1]);
+ expect(order.slice(1, 4).includes(enemyIndices[0])).toBe(true);
+ expect(order.slice(1, 4).includes(playerIndices[0])).toBe(true);
+ expect(order.slice(1, 4).includes(playerIndices[1])).toBe(true);
}, 20000);
it("double - speed tie 100/150 vs 100/150", async () => {
@@ -112,19 +133,25 @@ describe("Battle order", () => {
Species.BULBASAUR,
Species.BLASTOISE,
]);
- game.scene.getParty()[0].stats[Stat.SPD] = 100;
- game.scene.getParty()[1].stats[Stat.SPD] = 150;
- game.scene.currentBattle.enemyParty[0].stats[Stat.SPD] = 100;
- game.scene.currentBattle.enemyParty[1].stats[Stat.SPD] = 150;
+
+ const playerPokemon = game.scene.getPlayerField();
+ const enemyPokemon = game.scene.getEnemyField();
+ vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100]); // set one playerPokemon's speed to 100
+ vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set other playerPokemon's speed to 150
+ vi.spyOn(enemyPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 100]); // set one enemyPokemon's speed to 100
+ vi.spyOn(enemyPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, 150]); // set other enemyPokemon's speed to 150
+ const playerIndices = playerPokemon.map(p => p?.getBattlerIndex());
+ const enemyIndices = enemyPokemon.map(p => p?.getBattlerIndex());
game.move.select(Moves.TACKLE);
game.move.select(Moves.TACKLE, 1);
await game.phaseInterceptor.runFrom(SelectTargetPhase).to(TurnStartPhase, false);
+
const phase = game.scene.getCurrentPhase() as TurnStartPhase;
- const order = phase.getOrder();
- expect(order.indexOf(1)).toBeLessThan(order.indexOf(0));
- expect(order.indexOf(1)).toBeLessThan(order.indexOf(2));
- expect(order.indexOf(3)).toBeLessThan(order.indexOf(0));
- expect(order.indexOf(3)).toBeLessThan(order.indexOf(2));
+ const order = phase.getCommandOrder();
+ expect(order.slice(0, 2).includes(playerIndices[1])).toBe(true);
+ expect(order.slice(0, 2).includes(enemyIndices[1])).toBe(true);
+ expect(order.slice(2, 4).includes(playerIndices[0])).toBe(true);
+ expect(order.slice(2, 4).includes(enemyIndices[0])).toBe(true);
}, 20000);
});
diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts
index be89fdeb2af..25dfbc765bd 100644
--- a/src/test/battle/battle.test.ts
+++ b/src/test/battle/battle.test.ts
@@ -1,5 +1,5 @@
import { allSpecies } from "#app/data/pokemon-species";
-import { TempBattleStat } from "#app/data/temp-battle-stat";
+import { Stat } from "#enums/stat";
import { GameModes, getGameMode } from "#app/game-mode";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { CommandPhase } from "#app/phases/command-phase";
@@ -320,7 +320,7 @@ describe("Test Battle Phase", () => {
.startingLevel(100)
.moveset([moveToUse])
.enemyMoveset(SPLASH_ONLY)
- .startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: TempBattleStat.ACC }]);
+ .startingHeldItems([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }]);
await game.startBattle();
game.scene.getPlayerPokemon()!.hp = 1;
diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts
index fa491589f09..7b1f9264370 100644
--- a/src/test/battlerTags/octolock.test.ts
+++ b/src/test/battlerTags/octolock.test.ts
@@ -1,16 +1,16 @@
import BattleScene from "#app/battle-scene";
-import { BattleStat } from "#app/data/battle-stat";
-import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags";
-import { BattlerTagType } from "#app/enums/battler-tag-type";
-import Pokemon from "#app/field/pokemon";
-import { StatChangePhase } from "#app/phases/stat-change-phase";
import { describe, expect, it, vi } from "vitest";
+import Pokemon from "#app/field/pokemon";
+import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
+import { BattlerTagType } from "#app/enums/battler-tag-type";
+import { Stat } from "#enums/stat";
vi.mock("#app/battle-scene.js");
describe("BattlerTag - OctolockTag", () => {
describe("lapse behavior", () => {
- it("unshifts a StatChangePhase with expected stat changes", { timeout: 10000 }, async () => {
+ it("unshifts a StatStageChangePhase with expected stat stage changes", { timeout: 10000 }, async () => {
const mockPokemon = {
scene: new BattleScene(),
getBattlerIndex: () => 0,
@@ -19,9 +19,9 @@ describe("BattlerTag - OctolockTag", () => {
const subject = new OctolockTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(-1);
- expect((phase as StatChangePhase)["stats"]).toEqual([BattleStat.DEF, BattleStat.SPDEF]);
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(-1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual([ Stat.DEF, Stat.SPDEF ]);
});
subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END);
diff --git a/src/test/battlerTags/stockpiling.test.ts b/src/test/battlerTags/stockpiling.test.ts
index fef1e938c09..e568016dfef 100644
--- a/src/test/battlerTags/stockpiling.test.ts
+++ b/src/test/battlerTags/stockpiling.test.ts
@@ -1,10 +1,10 @@
import BattleScene from "#app/battle-scene";
-import { BattleStat } from "#app/data/battle-stat";
-import { StockpilingTag } from "#app/data/battler-tags";
-import Pokemon, { PokemonSummonData } from "#app/field/pokemon";
-import * as messages from "#app/messages";
-import { StatChangePhase } from "#app/phases/stat-change-phase";
import { beforeEach, describe, expect, it, vi } from "vitest";
+import Pokemon, { PokemonSummonData } from "#app/field/pokemon";
+import { StockpilingTag } from "#app/data/battler-tags";
+import { Stat } from "#enums/stat";
+import * as messages from "#app/messages";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
beforeEach(() => {
vi.spyOn(messages, "getPokemonNameWithAffix").mockImplementation(() => "");
@@ -12,7 +12,7 @@ beforeEach(() => {
describe("BattlerTag - StockpilingTag", () => {
describe("onAdd", () => {
- it("unshifts a StatChangePhase with expected stat changes on add", { timeout: 10000 }, async () => {
+ it("unshifts a StatStageChangePhase with expected stat stage changes on add", { timeout: 10000 }, async () => {
const mockPokemon = {
scene: vi.mocked(new BattleScene()) as BattleScene,
getBattlerIndex: () => 0,
@@ -23,11 +23,11 @@ describe("BattlerTag - StockpilingTag", () => {
const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(1);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
- (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
+ (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [Stat.DEF, Stat.SPDEF], [1, 1]);
});
subject.onAdd(mockPokemon);
@@ -35,7 +35,7 @@ describe("BattlerTag - StockpilingTag", () => {
expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1);
});
- it("unshifts a StatChangePhase with expected stat changes on add (one stat maxed)", { timeout: 10000 }, async () => {
+ it("unshifts a StatStageChangePhase with expected stat changes on add (one stat maxed)", { timeout: 10000 }, async () => {
const mockPokemon = {
scene: new BattleScene(),
summonData: new PokemonSummonData(),
@@ -44,17 +44,17 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {});
- mockPokemon.summonData.battleStats[BattleStat.DEF] = 6;
- mockPokemon.summonData.battleStats[BattleStat.SPDEF] = 5;
+ mockPokemon.summonData.statStages[Stat.DEF - 1] = 6;
+ mockPokemon.summonData.statStages[Stat.SPD - 1] = 5;
const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(1);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF]));
- (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
+ (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]);
});
subject.onAdd(mockPokemon);
@@ -64,7 +64,7 @@ describe("BattlerTag - StockpilingTag", () => {
});
describe("onOverlap", () => {
- it("unshifts a StatChangePhase with expected stat changes on overlap", { timeout: 10000 }, async () => {
+ it("unshifts a StatStageChangePhase with expected stat changes on overlap", { timeout: 10000 }, async () => {
const mockPokemon = {
scene: new BattleScene(),
getBattlerIndex: () => 0,
@@ -75,11 +75,11 @@ describe("BattlerTag - StockpilingTag", () => {
const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(1);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
- (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
+ (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]);
});
subject.onOverlap(mockPokemon);
@@ -98,39 +98,39 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {});
- mockPokemon.summonData.battleStats[BattleStat.DEF] = 5;
- mockPokemon.summonData.battleStats[BattleStat.SPDEF] = 4;
+ mockPokemon.summonData.statStages[Stat.DEF - 1] = 5;
+ mockPokemon.summonData.statStages[Stat.SPD - 1] = 4;
const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(1);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// def doesn't change
- (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]);
+ (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]);
});
subject.onAdd(mockPokemon);
expect(subject.stockpiledCount).toBe(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(1);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// def doesn't change
- (phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]);
+ (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]);
});
subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(2);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(1);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(1);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// neither stat changes, stack count should still increase
});
@@ -138,20 +138,20 @@ describe("BattlerTag - StockpilingTag", () => {
subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(3);
- vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
+ vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(_phase => {
throw new Error("Should not be called a fourth time");
});
// fourth stack should not be applied
subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(3);
- expect(subject.statChangeCounts).toMatchObject({ [BattleStat.DEF]: 0, [BattleStat.SPDEF]: 2 });
+ expect(subject.statChangeCounts).toMatchObject({ [ Stat.DEF ]: 0, [Stat.SPDEF]: 2 });
// removing tag should reverse stat changes
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
- expect(phase).toBeInstanceOf(StatChangePhase);
- expect((phase as StatChangePhase)["levels"]).toEqual(-2);
- expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.SPDEF]));
+ expect(phase).toBeInstanceOf(StatStageChangePhase);
+ expect((phase as StatStageChangePhase)["stages"]).toEqual(-2);
+ expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.SPDEF]));
});
subject.onRemove(mockPokemon);
diff --git a/src/test/boss-pokemon.test.ts b/src/test/boss-pokemon.test.ts
new file mode 100644
index 00000000000..c6fc276551f
--- /dev/null
+++ b/src/test/boss-pokemon.test.ts
@@ -0,0 +1,215 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import GameManager from "./utils/gameManager";
+import { Species } from "#app/enums/species";
+import { getPokemonSpecies } from "#app/data/pokemon-species";
+import { SPLASH_ONLY } from "./utils/testUtils";
+import { Abilities } from "#app/enums/abilities";
+import { Moves } from "#app/enums/moves";
+import { EFFECTIVE_STATS } from "#app/enums/stat";
+import { EnemyPokemon } from "#app/field/pokemon";
+import { toDmgValue } from "#app/utils";
+
+describe("Boss Pokemon / Shields", () => {
+ const TIMEOUT = 20 * 1000;
+
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+
+ game.override
+ .battleType("single")
+ .disableTrainerWaves()
+ .disableCrits()
+ .enemySpecies(Species.RATTATA)
+ .enemyMoveset(SPLASH_ONLY)
+ .enemyHeldItems([])
+ .startingLevel(1000)
+ .moveset([Moves.FALSE_SWIPE, Moves.SUPER_FANG, Moves.SPLASH])
+ .ability(Abilities.NO_GUARD);
+ });
+
+ it("Pokemon should get shields based on their Species and level and the current wave", async () => {
+ let level = 50;
+ let wave = 5;
+
+ // On normal waves, no shields...
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(0);
+ // ... expect (sub)-legendary and mythical Pokemon who always get shields
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.MEW))).toBe(2);
+ // Pokemon with 670+ BST get an extra shield
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.MEWTWO))).toBe(3);
+
+ // Every 10 waves will always be a boss Pokemon with shield(s)
+ wave = 50;
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(2);
+ // Every extra 250 waves adds a shield
+ wave += 250;
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(3);
+ wave += 750;
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(6);
+
+ // Pokemon above level 100 get an extra shield
+ level = 100;
+ expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(7);
+ }, TIMEOUT);
+
+ it("should reduce the number of shields if we are in a double battle", async () => {
+ game.override
+ .battleType("double")
+ .startingWave(150); // Floor 150 > 2 shields / 3 health segments
+
+ await game.classicMode.startBattle([ Species.MEWTWO ]);
+
+ const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
+ const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
+ expect(boss1.isBoss()).toBe(true);
+ expect(boss1.bossSegments).toBe(2);
+ expect(boss2.isBoss()).toBe(true);
+ expect(boss2.bossSegments).toBe(2);
+ }, TIMEOUT);
+
+ it("shields should stop overflow damage and give stat stage boosts when broken", async () => {
+ game.override.startingWave(150); // Floor 150 > 2 shields / 3 health segments
+
+ await game.classicMode.startBattle([ Species.MEWTWO ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ const segmentHp = enemyPokemon.getMaxHp() / enemyPokemon.bossSegments;
+ expect(enemyPokemon.isBoss()).toBe(true);
+ expect(enemyPokemon.bossSegments).toBe(3);
+ expect(getTotalStatStageBoosts(enemyPokemon)).toBe(0);
+
+ game.move.select(Moves.SUPER_FANG); // Enough to break the first shield
+ await game.toNextTurn();
+
+ // Broke 1st of 2 shields, health at 2/3rd
+ expect(enemyPokemon.bossSegmentIndex).toBe(1);
+ expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - toDmgValue(segmentHp));
+ // Breaking the shield gives a +1 boost to ATK, DEF, SP ATK, SP DEF or SPD
+ expect(getTotalStatStageBoosts(enemyPokemon)).toBe(1);
+
+ game.move.select(Moves.FALSE_SWIPE); // Enough to break last shield but not kill
+ await game.toNextTurn();
+
+ expect(enemyPokemon.bossSegmentIndex).toBe(0);
+ expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - toDmgValue(2 * segmentHp));
+ // Breaking the last shield gives a +2 boost to ATK, DEF, SP ATK, SP DEF or SPD
+ expect(getTotalStatStageBoosts(enemyPokemon)).toBe(3);
+
+ }, TIMEOUT);
+
+ it("breaking multiple shields at once requires extra damage", async () => {
+ game.override
+ .battleType("double")
+ .enemyHealthSegments(5);
+
+ await game.classicMode.startBattle([ Species.MEWTWO ]);
+
+ // In this test we want to break through 3 shields at once
+ const brokenShields = 3;
+
+ const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
+ const boss1SegmentHp = boss1.getMaxHp() / boss1.bossSegments;
+ const requiredDamageBoss1 = boss1SegmentHp * (1 + Math.pow(2, brokenShields));
+ expect(boss1.isBoss()).toBe(true);
+ expect(boss1.bossSegments).toBe(5);
+ expect(boss1.bossSegmentIndex).toBe(4);
+
+ // Not enough damage to break through all shields
+ boss1.damageAndUpdate(Math.floor(requiredDamageBoss1 - 5));
+ expect(boss1.bossSegmentIndex).toBe(1);
+ expect(boss1.hp).toBe(boss1.getMaxHp() - toDmgValue(boss1SegmentHp * 3));
+
+ const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
+ const boss2SegmentHp = boss2.getMaxHp() / boss2.bossSegments;
+ const requiredDamageBoss2 = boss2SegmentHp * (1 + Math.pow(2, brokenShields));
+
+ expect(boss2.isBoss()).toBe(true);
+ expect(boss2.bossSegments).toBe(5);
+
+ // Enough damage to break through all shields
+ boss2.damageAndUpdate(Math.ceil(requiredDamageBoss2));
+ expect(boss2.bossSegmentIndex).toBe(0);
+ expect(boss2.hp).toBe(boss2.getMaxHp() - toDmgValue(boss2SegmentHp * 4));
+
+ }, TIMEOUT);
+
+ it("the number of stat stage boosts is consistent when several shields are broken at once", async () => {
+ const shieldsToBreak = 4;
+
+ game.override
+ .battleType("double")
+ .enemyHealthSegments(shieldsToBreak + 1);
+
+ await game.classicMode.startBattle([ Species.MEWTWO ]);
+
+ const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
+ const boss1SegmentHp = boss1.getMaxHp() / boss1.bossSegments;
+ const singleShieldDamage = Math.ceil(boss1SegmentHp);
+ expect(boss1.isBoss()).toBe(true);
+ expect(boss1.bossSegments).toBe(shieldsToBreak + 1);
+ expect(boss1.bossSegmentIndex).toBe(shieldsToBreak);
+ expect(getTotalStatStageBoosts(boss1)).toBe(0);
+
+
+ let totalStatStages = 0;
+
+ // Break the shields one by one
+ for (let i = 1; i <= shieldsToBreak; i++) {
+ boss1.damageAndUpdate(singleShieldDamage);
+ expect(boss1.bossSegmentIndex).toBe(shieldsToBreak - i);
+ expect(boss1.hp).toBe(boss1.getMaxHp() - toDmgValue(boss1SegmentHp * i));
+ // Do nothing and go to next turn so that the StatStageChangePhase gets applied
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+ // All broken shields give +1 stat boost, except the last two that gives +2
+ totalStatStages += i >= shieldsToBreak -1? 2 : 1;
+ expect(getTotalStatStageBoosts(boss1)).toBe(totalStatStages);
+ }
+
+ const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
+ const boss2SegmentHp = boss2.getMaxHp() / boss2.bossSegments;
+ const requiredDamage = boss2SegmentHp * (1 + Math.pow(2, shieldsToBreak - 1));
+
+ expect(boss2.isBoss()).toBe(true);
+ expect(boss2.bossSegments).toBe(shieldsToBreak + 1);
+ expect(boss2.bossSegmentIndex).toBe(shieldsToBreak);
+ expect(getTotalStatStageBoosts(boss2)).toBe(0);
+
+ // Enough damage to break all shields at once
+ boss2.damageAndUpdate(Math.ceil(requiredDamage));
+ expect(boss2.bossSegmentIndex).toBe(0);
+ expect(boss2.hp).toBe(boss2.getMaxHp() - toDmgValue(boss2SegmentHp * shieldsToBreak));
+ // Do nothing and go to next turn so that the StatStageChangePhase gets applied
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+ expect(getTotalStatStageBoosts(boss2)).toBe(totalStatStages);
+
+ }, TIMEOUT);
+
+ /**
+ * Gets the sum of the effective stat stage boosts for the given Pokemon
+ * @param enemyPokemon the pokemon to get stats from
+ * @returns the total stats boosts
+ */
+ function getTotalStatStageBoosts(enemyPokemon: EnemyPokemon): number {
+ let boosts = 0;
+ for (const s of EFFECTIVE_STATS) {
+ boosts += enemyPokemon.getStatStage(s);
+ }
+ return boosts;
+ }
+});
+
diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts
index 28f1b7f0a6c..4f00e843b47 100644
--- a/src/test/eggs/egg.test.ts
+++ b/src/test/eggs/egg.test.ts
@@ -12,6 +12,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite
describe("Egg Generation Tests", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
+ const EGG_HATCH_COUNT: integer = 1000;
beforeAll(() => {
phaserGame = new Phaser.Game({
@@ -47,14 +48,21 @@ describe("Egg Generation Tests", () => {
expect(result).toBe(expectedSpecies);
});
- it("should hatch an Arceus. Set from legendary gacha", async () => {
+ it("should hatch an Arceus around half the time. Set from legendary gacha", async () => {
const scene = game.scene;
const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime();
const expectedSpecies = Species.ARCEUS;
+ let gachaSpeciesCount = 0;
- const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId;
+ for (let i = 0; i < EGG_HATCH_COUNT; i++) {
+ const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId;
+ if (result === expectedSpecies) {
+ gachaSpeciesCount++;
+ }
+ }
- expect(result).toBe(expectedSpecies);
+ expect(gachaSpeciesCount).toBeGreaterThan(0.4 * EGG_HATCH_COUNT);
+ expect(gachaSpeciesCount).toBeLessThan(0.6 * EGG_HATCH_COUNT);
});
it("should hatch an Arceus. Set from species", () => {
const scene = game.scene;
@@ -156,7 +164,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene;
const eggMoveIndex = new Egg({ scene }).eggMoveIndex;
- const result = eggMoveIndex && eggMoveIndex >= 0 && eggMoveIndex <= 3;
+ const result = !Utils.isNullOrUndefined(eggMoveIndex) && eggMoveIndex >= 0 && eggMoveIndex <= 3;
expect(result).toBe(true);
});
@@ -309,4 +317,63 @@ describe("Egg Generation Tests", () => {
expect(egg.variantTier).toBe(VariantTier.EPIC);
});
+
+ it("should generate egg moves, species, shinyness, and ability unpredictably for the egg gacha", () => {
+ const scene = game.scene;
+ scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ scene.resetSeed();
+
+ const firstEgg = new Egg({scene, sourceType: EggSourceType.GACHA_SHINY, tier: EggTier.COMMON});
+ const firstHatch = firstEgg.generatePlayerPokemon(scene);
+ let diffEggMove = false;
+ let diffSpecies = false;
+ let diffShiny = false;
+ let diffAbility = false;
+ for (let i = 0; i < EGG_HATCH_COUNT; i++) {
+ scene.gameData.unlockPity[EggTier.COMMON] = 0;
+ scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ scene.resetSeed(); // Make sure that eggs are unpredictable even if using same seed
+
+ const newEgg = new Egg({scene, sourceType: EggSourceType.GACHA_SHINY, tier: EggTier.COMMON});
+ const newHatch = newEgg.generatePlayerPokemon(scene);
+ diffEggMove = diffEggMove || (newEgg.eggMoveIndex !== firstEgg.eggMoveIndex);
+ diffSpecies = diffSpecies || (newHatch.species.speciesId !== firstHatch.species.speciesId);
+ diffShiny = diffShiny || (newHatch.shiny !== firstHatch.shiny);
+ diffAbility = diffAbility || (newHatch.abilityIndex !== firstHatch.abilityIndex);
+ }
+
+ expect(diffEggMove).toBe(true);
+ expect(diffSpecies).toBe(true);
+ expect(diffShiny).toBe(true);
+ expect(diffAbility).toBe(true);
+ });
+
+ it("should generate egg moves, shinyness, and ability unpredictably for species eggs", () => {
+ const scene = game.scene;
+ scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ scene.resetSeed();
+
+ const firstEgg = new Egg({scene, species: Species.BULBASAUR});
+ const firstHatch = firstEgg.generatePlayerPokemon(scene);
+ let diffEggMove = false;
+ let diffSpecies = false;
+ let diffShiny = false;
+ let diffAbility = false;
+ for (let i = 0; i < EGG_HATCH_COUNT; i++) {
+ scene.setSeed("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ scene.resetSeed(); // Make sure that eggs are unpredictable even if using same seed
+
+ const newEgg = new Egg({scene, species: Species.BULBASAUR});
+ const newHatch = newEgg.generatePlayerPokemon(scene);
+ diffEggMove = diffEggMove || (newEgg.eggMoveIndex !== firstEgg.eggMoveIndex);
+ diffSpecies = diffSpecies || (newHatch.species.speciesId !== firstHatch.species.speciesId);
+ diffShiny = diffShiny || (newHatch.shiny !== firstHatch.shiny);
+ diffAbility = diffAbility || (newHatch.abilityIndex !== firstHatch.abilityIndex);
+ }
+
+ expect(diffEggMove).toBe(true);
+ expect(diffSpecies).toBe(false);
+ expect(diffShiny).toBe(true);
+ expect(diffAbility).toBe(true);
+ });
});
diff --git a/src/test/endless_boss.test.ts b/src/test/endless_boss.test.ts
index e983be245b6..8a564695e42 100644
--- a/src/test/endless_boss.test.ts
+++ b/src/test/endless_boss.test.ts
@@ -32,7 +32,7 @@ describe("Endless Boss", () => {
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Endless`, async () => {
game.override.startingWave(EndlessBossWave.Minor);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.ENDLESS);
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
expect(game.scene.arena.biomeType).toBe(Biome.END);
@@ -44,7 +44,7 @@ describe("Endless Boss", () => {
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Endless`, async () => {
game.override.startingWave(EndlessBossWave.Major);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.ENDLESS);
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
expect(game.scene.arena.biomeType).toBe(Biome.END);
@@ -56,7 +56,7 @@ describe("Endless Boss", () => {
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Spliced Endless`, async () => {
game.override.startingWave(EndlessBossWave.Minor);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.SPLICED_ENDLESS);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.SPLICED_ENDLESS);
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
expect(game.scene.arena.biomeType).toBe(Biome.END);
@@ -68,7 +68,7 @@ describe("Endless Boss", () => {
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Spliced Endless`, async () => {
game.override.startingWave(EndlessBossWave.Major);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.SPLICED_ENDLESS);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.SPLICED_ENDLESS);
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
expect(game.scene.arena.biomeType).toBe(Biome.END);
@@ -80,7 +80,7 @@ describe("Endless Boss", () => {
it(`should NOT spawn major or minor boss outside wave ${EndlessBossWave.Minor}s in END biome`, async () => {
game.override.startingWave(EndlessBossWave.Minor - 1);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.ENDLESS);
expect(game.scene.currentBattle.waveIndex).not.toBe(EndlessBossWave.Minor);
expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
diff --git a/src/test/evolution.test.ts b/src/test/evolution.test.ts
index f9123cf6d9a..9f0806b8e24 100644
--- a/src/test/evolution.test.ts
+++ b/src/test/evolution.test.ts
@@ -2,9 +2,10 @@ import { pokemonEvolutions, SpeciesFormEvolution, SpeciesWildEvolutionDelay } fr
import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
+import * as Utils from "#app/utils";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
-import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { SPLASH_ONLY } from "./utils/testUtils";
describe("Evolution", () => {
@@ -78,12 +79,15 @@ describe("Evolution", () => {
const nincada = game.scene.getPlayerPokemon()!;
nincada.abilityIndex = 2;
+ nincada.metBiome = -1;
nincada.evolve(pokemonEvolutions[Species.NINCADA][0], nincada.getSpeciesForm());
const ninjask = game.scene.getParty()[0];
const shedinja = game.scene.getParty()[1];
expect(ninjask.abilityIndex).toBe(2);
expect(shedinja.abilityIndex).toBe(1);
+ // Regression test for https://github.com/pagefaultgames/pokerogue/issues/3842
+ expect(shedinja.metBiome).toBe(-1);
}, TIMEOUT);
it("should set wild delay to NONE by default", () => {
@@ -148,4 +152,28 @@ describe("Evolution", () => {
expect(cyndaquil.hp).toBeGreaterThan(hpBefore);
expect(cyndaquil.hp).toBeLessThan(cyndaquil.getMaxHp());
}, TIMEOUT);
+
+ it("should handle rng-based split evolution", async () => {
+ /* this test checks to make sure that tandemaus will
+ * evolve into a 3 family maushold 25% of the time
+ * and a 4 family maushold the other 75% of the time
+ * This is done by using the getEvolution method in pokemon.ts
+ * getEvolution will give back the form that the pokemon can evolve into
+ * It does this by checking the pokemon conditions in pokemon-forms.ts
+ * For tandemaus, the conditions are random due to a randSeedInt(4)
+ * If the value is 0, it's a 3 family maushold, whereas if the value is
+ * 1, 2 or 3, it's a 4 family maushold
+ */
+ await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+ playerPokemon.level = 25; // tandemaus evolves at level 25
+ vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); // setting the random generator to be 0 to force a three family maushold
+ const threeForm = playerPokemon.getEvolution()!;
+ expect(threeForm.evoFormKey).toBe("three"); // as per pokemon-forms, the evoFormKey for 3 family mausholds is "three"
+ for (let f = 1; f < 4; f++) {
+ vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds
+ const fourForm = playerPokemon.getEvolution()!;
+ expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null
+ }
+ }, TIMEOUT);
});
diff --git a/src/test/evolutions/evolutions.test.ts b/src/test/evolutions/evolutions.test.ts
deleted file mode 100644
index 2028764115c..00000000000
--- a/src/test/evolutions/evolutions.test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import * as Utils from "#app/utils";
-import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
-import Phaser from "phaser";
-import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
-
-describe("Evolution tests", () => {
- let phaserGame: Phaser.Game;
- let game: GameManager;
-
- beforeAll(() => {
- phaserGame = new Phaser.Game({
- type: Phaser.HEADLESS,
- });
- });
-
- afterEach(() => {
- game.phaseInterceptor.restoreOg();
- });
-
- beforeEach(() => {
- game = new GameManager(phaserGame);
- });
-
- it("tandemaus evolution form test", async () => {
- /* this test checks to make sure that tandemaus will
- * evolve into a 3 family maushold 25% of the time
- * and a 4 family maushold the other 75% of the time
- * This is done by using the getEvolution method in pokemon.ts
- * getEvolution will give back the form that the pokemon can evolve into
- * It does this by checking the pokemon conditions in pokemon-forms.ts
- * For tandemaus, the conditions are random due to a randSeedInt(4)
- * If the value is 0, it's a 3 family maushold, whereas if the value is
- * 1, 2 or 3, it's a 4 family maushold
- */
- await game.startBattle([Species.TANDEMAUS]); // starts us off with a tandemaus
- const playerPokemon = game.scene.getPlayerPokemon()!;
- playerPokemon.level = 25; // tandemaus evolves at level 25
- vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); // setting the random generator to be 0 to force a three family maushold
- const threeForm = playerPokemon.getEvolution()!;
- expect(threeForm.evoFormKey).toBe("three"); // as per pokemon-forms, the evoFormKey for 3 family mausholds is "three"
- for (let f = 1; f < 4; f++) {
- vi.spyOn(Utils, "randSeedInt").mockReturnValue(f); // setting the random generator to 1, 2 and 3 to force 4 family mausholds
- const fourForm = playerPokemon.getEvolution()!;
- expect(fourForm.evoFormKey).toBe(null); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is null
- }
- }, 5000);
-});
diff --git a/src/test/final_boss.test.ts b/src/test/final_boss.test.ts
index 0f59572619b..5d006998a0b 100644
--- a/src/test/final_boss.test.ts
+++ b/src/test/final_boss.test.ts
@@ -1,8 +1,13 @@
+import { StatusEffect } from "#app/data/status-effect";
+import { Abilities } from "#app/enums/abilities";
import { Biome } from "#app/enums/biome";
+import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
import { GameModes } from "#app/game-mode";
+import { TurnHeldItemTransferModifier } from "#app/modifier/modifier";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "./utils/gameManager";
+import { SPLASH_ONLY } from "./utils/testUtils";
const FinalWave = {
Classic: 200,
@@ -20,7 +25,13 @@ describe("Final Boss", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.startingWave(FinalWave.Classic).startingBiome(Biome.END).disableCrits();
+ game.override
+ .startingWave(FinalWave.Classic)
+ .startingBiome(Biome.END)
+ .disableCrits()
+ .enemyMoveset(SPLASH_ONLY)
+ .moveset([ Moves.SPLASH, Moves.WILL_O_WISP, Moves.DRAGON_PULSE ])
+ .startingLevel(10000);
});
afterEach(() => {
@@ -28,7 +39,7 @@ describe("Final Boss", () => {
});
it("should spawn Eternatus on wave 200 in END biome", async () => {
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
expect(game.scene.arena.biomeType).toBe(Biome.END);
@@ -37,7 +48,7 @@ describe("Final Boss", () => {
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
game.override.startingWave(FinalWave.Classic - 1);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
expect(game.scene.currentBattle.waveIndex).not.toBe(FinalWave.Classic);
expect(game.scene.arena.biomeType).toBe(Biome.END);
@@ -46,7 +57,7 @@ describe("Final Boss", () => {
it("should NOT spawn Eternatus outside of END biome", async () => {
game.override.startingBiome(Biome.FOREST);
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
expect(game.scene.arena.biomeType).not.toBe(Biome.END);
@@ -54,12 +65,81 @@ describe("Final Boss", () => {
});
it("should not have passive enabled on Eternatus", async () => {
- await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
- const eternatus = game.scene.getEnemyPokemon();
- expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
- expect(eternatus?.hasPassive()).toBe(false);
+ const eternatus = game.scene.getEnemyPokemon()!;
+ expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
+ expect(eternatus.hasPassive()).toBe(false);
+ });
+
+ it("should change form on direct hit down to last boss fragment", async () => {
+ await game.runToFinalBossEncounter([Species.KYUREM], GameModes.CLASSIC);
+ await game.phaseInterceptor.to("CommandPhase");
+
+ // Eternatus phase 1
+ const eternatus = game.scene.getEnemyPokemon()!;
+ const phase1Hp = eternatus.getMaxHp();
+ expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
+ expect(eternatus.formIndex).toBe(0);
+ expect(eternatus.bossSegments).toBe(4);
+ expect(eternatus.bossSegmentIndex).toBe(3);
+
+ game.move.select(Moves.DRAGON_PULSE);
+ await game.toNextTurn();
+
+ // Eternatus phase 2: changed form, healed and restored its shields
+ expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
+ expect(eternatus.hp).toBeGreaterThan(phase1Hp);
+ expect(eternatus.hp).toBe(eternatus.getMaxHp());
+ expect(eternatus.formIndex).toBe(1);
+ expect(eternatus.bossSegments).toBe(5);
+ expect(eternatus.bossSegmentIndex).toBe(4);
+ const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier);
+ expect(miniBlackHole).toBeDefined();
+ expect(miniBlackHole?.stackCount).toBe(1);
+ });
+
+ it("should change form on status damage down to last boss fragment", async () => {
+ game.override.ability(Abilities.NO_GUARD);
+
+ await game.runToFinalBossEncounter([Species.BIDOOF], GameModes.CLASSIC);
+ await game.phaseInterceptor.to("CommandPhase");
+
+ // Eternatus phase 1
+ const eternatus = game.scene.getEnemyPokemon()!;
+ const phase1Hp = eternatus.getMaxHp();
+ expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
+ expect(eternatus.formIndex).toBe(0);
+ expect(eternatus.bossSegments).toBe(4);
+ expect(eternatus.bossSegmentIndex).toBe(3);
+
+ game.move.select(Moves.WILL_O_WISP);
+ await game.toNextTurn();
+ expect(eternatus.status?.effect).toBe(StatusEffect.BURN);
+
+ const tickDamage = phase1Hp - eternatus.hp;
+ const lastShieldHp = Math.ceil(phase1Hp / eternatus.bossSegments);
+ // Stall until the burn is one hit away from breaking the last shield
+ while (eternatus.hp - tickDamage > lastShieldHp) {
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+ }
+
+ expect(eternatus.bossSegmentIndex).toBe(1);
+
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+
+ // Eternatus phase 2: changed form, healed and restored its shields
+ expect(eternatus.hp).toBeGreaterThan(phase1Hp);
+ expect(eternatus.hp).toBe(eternatus.getMaxHp());
+ expect(eternatus.status).toBeFalsy();
+ expect(eternatus.formIndex).toBe(1);
+ expect(eternatus.bossSegments).toBe(5);
+ expect(eternatus.bossSegmentIndex).toBe(4);
+ const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier);
+ expect(miniBlackHole).toBeDefined();
+ expect(miniBlackHole?.stackCount).toBe(1);
});
- it.todo("should change form on direct hit down to last boss fragment", () => {});
});
diff --git a/src/test/items/dire_hit.test.ts b/src/test/items/dire_hit.test.ts
new file mode 100644
index 00000000000..02f7c0d06a4
--- /dev/null
+++ b/src/test/items/dire_hit.test.ts
@@ -0,0 +1,98 @@
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import GameManager from "#test/utils/gameManager";
+import Phase from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+import { BattleEndPhase } from "#app/phases/battle-end-phase";
+import { TempCritBoosterModifier } from "#app/modifier/modifier";
+import { Mode } from "#app/ui/ui";
+import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
+import { Button } from "#app/enums/buttons";
+import { CommandPhase } from "#app/phases/command-phase";
+import { NewBattlePhase } from "#app/phases/new-battle-phase";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
+
+describe("Items - Dire Hit", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phase.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+
+ game.override
+ .enemySpecies(Species.MAGIKARP)
+ .enemyMoveset(SPLASH_ONLY)
+ .moveset([ Moves.POUND ])
+ .startingHeldItems([{ name: "DIRE_HIT" }])
+ .battleType("single")
+ .disableCrits();
+
+ }, 20000);
+
+ it("should raise CRIT stage by 1", async () => {
+ await game.startBattle([
+ Species.GASTLY
+ ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ vi.spyOn(enemyPokemon, "getCritStage");
+
+ game.move.select(Moves.POUND);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(enemyPokemon.getCritStage).toHaveReturnedWith(1);
+ }, 20000);
+
+ it("should renew how many battles are left of existing DIRE_HIT when picking up new DIRE_HIT", async() => {
+ game.override.itemRewards([{ name: "DIRE_HIT" }]);
+
+ await game.startBattle([
+ Species.PIKACHU
+ ]);
+
+ game.move.select(Moves.SPLASH);
+
+ await game.doKillOpponents();
+
+ await game.phaseInterceptor.to(BattleEndPhase);
+
+ const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier;
+ expect(modifier.getBattlesLeft()).toBe(4);
+
+ // Forced DIRE_HIT to spawn in the first slot with override
+ game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
+ const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler;
+ // Traverse to first modifier slot
+ handler.setCursor(0);
+ handler.setRowCursor(ShopCursorTarget.REWARDS);
+ handler.processInput(Button.ACTION);
+ }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(NewBattlePhase), true);
+
+ await game.phaseInterceptor.to(TurnInitPhase);
+
+ // Making sure only one booster is in the modifier list even after picking up another
+ let count = 0;
+ for (const m of game.scene.modifiers) {
+ if (m instanceof TempCritBoosterModifier) {
+ count++;
+ expect((m as TempCritBoosterModifier).getBattlesLeft()).toBe(5);
+ }
+ }
+ expect(count).toBe(1);
+ }, 20000);
+});
diff --git a/src/test/items/eviolite.test.ts b/src/test/items/eviolite.test.ts
index e491784acec..83b00583893 100644
--- a/src/test/items/eviolite.test.ts
+++ b/src/test/items/eviolite.test.ts
@@ -1,4 +1,4 @@
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { EvolutionStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
@@ -37,29 +37,29 @@ describe("Items - Eviolite", () => {
const partyMember = game.scene.getParty()[0];
- // Checking consoe log to make sure Eviolite is applied when getBattleStat (with the appropriate stat) is called
- partyMember.getBattleStat(Stat.DEF);
+ // Checking console log to make sure Eviolite is applied when getEffectiveStat (with the appropriate stat) is called
+ partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
- partyMember.getBattleStat(Stat.SPDEF);
+ partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.ATK);
+ partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPATK);
+ partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPD);
+ partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
});
diff --git a/src/test/items/leek.test.ts b/src/test/items/leek.test.ts
index 7505b6374a0..af20516ef83 100644
--- a/src/test/items/leek.test.ts
+++ b/src/test/items/leek.test.ts
@@ -1,7 +1,4 @@
-import { BattlerIndex } from "#app/battle";
-import { CritBoosterModifier } from "#app/modifier/modifier";
-import { modifierTypes } from "#app/modifier/modifier-type";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
import * as Utils from "#app/utils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@@ -26,91 +23,64 @@ describe("Items - Leek", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.enemySpecies(Species.MAGIKARP);
- game.override.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
- game.override.disableCrits();
-
- game.override.battleType("single");
+ game.override
+ .enemySpecies(Species.MAGIKARP)
+ .enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH])
+ .startingHeldItems([{ name: "LEEK" }])
+ .moveset([ Moves.TACKLE ])
+ .disableCrits()
+ .battleType("single");
});
- it("LEEK activates in battle correctly", async () => {
- game.override.startingHeldItems([{ name: "LEEK" }]);
- game.override.moveset([Moves.POUND]);
- const consoleSpy = vi.spyOn(console, "log");
+ it("should raise CRIT stage by 2 when held by FARFETCHD", async () => {
await game.startBattle([
Species.FARFETCHD
]);
- game.move.select(Moves.POUND);
+ const enemyMember = game.scene.getEnemyPokemon()!;
- await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ vi.spyOn(enemyMember, "getCritStage");
- await game.phaseInterceptor.to(MoveEffectPhase);
+ game.move.select(Moves.TACKLE);
- expect(consoleSpy).toHaveBeenCalledWith("Applied", "Leek", "");
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(enemyMember.getCritStage).toHaveReturnedWith(2);
}, 20000);
- it("LEEK held by FARFETCHD", async () => {
- await game.startBattle([
- Species.FARFETCHD
- ]);
-
- const partyMember = game.scene.getPlayerPokemon()!;
-
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
-
- expect(critLevel.value).toBe(0);
-
- // Giving Leek to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
-
- expect(critLevel.value).toBe(2);
- }, 20000);
-
- it("LEEK held by GALAR_FARFETCHD", async () => {
+ it("should raise CRIT stage by 2 when held by GALAR_FARFETCHD", async () => {
await game.startBattle([
Species.GALAR_FARFETCHD
]);
- const partyMember = game.scene.getPlayerPokemon()!;
+ const enemyMember = game.scene.getEnemyPokemon()!;
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ vi.spyOn(enemyMember, "getCritStage");
- expect(critLevel.value).toBe(0);
+ game.move.select(Moves.TACKLE);
- // Giving Leek to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ await game.phaseInterceptor.to(TurnEndPhase);
- expect(critLevel.value).toBe(2);
+ expect(enemyMember.getCritStage).toHaveReturnedWith(2);
}, 20000);
- it("LEEK held by SIRFETCHD", async () => {
+ it("should raise CRIT stage by 2 when held by SIRFETCHD", async () => {
await game.startBattle([
Species.SIRFETCHD
]);
- const partyMember = game.scene.getPlayerPokemon()!;
+ const enemyMember = game.scene.getEnemyPokemon()!;
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ vi.spyOn(enemyMember, "getCritStage");
- expect(critLevel.value).toBe(0);
+ game.move.select(Moves.TACKLE);
- // Giving Leek to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ await game.phaseInterceptor.to(TurnEndPhase);
- expect(critLevel.value).toBe(2);
+ expect(enemyMember.getCritStage).toHaveReturnedWith(2);
}, 20000);
- it("LEEK held by fused FARFETCHD line (base)", async () => {
+ it("should raise CRIT stage by 2 when held by FARFETCHD line fused with Pokemon", async () => {
// Randomly choose from the Farfetch'd line
const species = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD];
@@ -119,9 +89,7 @@ describe("Items - Leek", () => {
Species.PIKACHU,
]);
- const party = game.scene.getParty();
- const partyMember = party[0];
- const ally = party[1];
+ const [ partyMember, ally ] = game.scene.getParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@@ -132,20 +100,18 @@ describe("Items - Leek", () => {
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ const enemyMember = game.scene.getEnemyPokemon()!;
- expect(critLevel.value).toBe(0);
+ vi.spyOn(enemyMember, "getCritStage");
- // Giving Leek to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ game.move.select(Moves.TACKLE);
- expect(critLevel.value).toBe(2);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(enemyMember.getCritStage).toHaveReturnedWith(2);
}, 20000);
- it("LEEK held by fused FARFETCHD line (part)", async () => {
+ it("should raise CRIT stage by 2 when held by Pokemon fused with FARFETCHD line", async () => {
// Randomly choose from the Farfetch'd line
const species = [Species.FARFETCHD, Species.GALAR_FARFETCHD, Species.SIRFETCHD];
@@ -154,9 +120,7 @@ describe("Items - Leek", () => {
species[Utils.randInt(species.length)]
]);
- const party = game.scene.getParty();
- const partyMember = party[0];
- const ally = party[1];
+ const [ partyMember, ally ] = game.scene.getParty();
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
@@ -167,36 +131,31 @@ describe("Items - Leek", () => {
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
- expect(critLevel.value).toBe(0);
+ const enemyMember = game.scene.getEnemyPokemon()!;
- // Giving Leek to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ vi.spyOn(enemyMember, "getCritStage");
- expect(critLevel.value).toBe(2);
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(enemyMember.getCritStage).toHaveReturnedWith(2);
}, 20000);
- it("LEEK not held by FARFETCHD line", async () => {
+ it("should not raise CRIT stage when held by a Pokemon outside of FARFETCHD line", async () => {
await game.startBattle([
Species.PIKACHU
]);
- const partyMember = game.scene.getPlayerPokemon()!;
+ const enemyMember = game.scene.getEnemyPokemon()!;
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ vi.spyOn(enemyMember, "getCritStage");
- expect(critLevel.value).toBe(0);
+ game.move.select(Moves.TACKLE);
- // Giving Leek to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.LEEK().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
+ await game.phaseInterceptor.to(TurnEndPhase);
- expect(critLevel.value).toBe(0);
+ expect(enemyMember.getCritStage).toHaveReturnedWith(0);
}, 20000);
});
diff --git a/src/test/items/light_ball.test.ts b/src/test/items/light_ball.test.ts
index cf4f5c9e22f..673348e7b7a 100644
--- a/src/test/items/light_ball.test.ts
+++ b/src/test/items/light_ball.test.ts
@@ -1,4 +1,4 @@
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
@@ -37,29 +37,29 @@ describe("Items - Light Ball", () => {
const partyMember = game.scene.getParty()[0];
- // Checking consoe log to make sure Light Ball is applied when getBattleStat (with the appropriate stat) is called
- partyMember.getBattleStat(Stat.DEF);
+ // Checking console log to make sure Light Ball is applied when getEffectiveStat (with the appropriate stat) is called
+ partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
- partyMember.getBattleStat(Stat.SPDEF);
+ partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.ATK);
+ partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPATK);
+ partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPD);
+ partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
});
diff --git a/src/test/items/metal_powder.test.ts b/src/test/items/metal_powder.test.ts
index a3a4936532f..0206fd1f471 100644
--- a/src/test/items/metal_powder.test.ts
+++ b/src/test/items/metal_powder.test.ts
@@ -1,4 +1,4 @@
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
@@ -37,29 +37,29 @@ describe("Items - Metal Powder", () => {
const partyMember = game.scene.getParty()[0];
- // Checking consoe log to make sure Metal Powder is applied when getBattleStat (with the appropriate stat) is called
- partyMember.getBattleStat(Stat.DEF);
+ // Checking console log to make sure Metal Powder is applied when getEffectiveStat (with the appropriate stat) is called
+ partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
- partyMember.getBattleStat(Stat.SPDEF);
+ partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.ATK);
+ partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPATK);
+ partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPD);
+ partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
});
diff --git a/src/test/items/quick_powder.test.ts b/src/test/items/quick_powder.test.ts
index 53521ba78f1..344b772feb4 100644
--- a/src/test/items/quick_powder.test.ts
+++ b/src/test/items/quick_powder.test.ts
@@ -1,4 +1,4 @@
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
@@ -37,29 +37,29 @@ describe("Items - Quick Powder", () => {
const partyMember = game.scene.getParty()[0];
- // Checking consoe log to make sure Quick Powder is applied when getBattleStat (with the appropriate stat) is called
- partyMember.getBattleStat(Stat.DEF);
+ // Checking console log to make sure Quick Powder is applied when getEffectiveStat (with the appropriate stat) is called
+ partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
- partyMember.getBattleStat(Stat.SPDEF);
+ partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.ATK);
+ partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPATK);
+ partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPD);
+ partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
});
diff --git a/src/test/items/scope_lens.test.ts b/src/test/items/scope_lens.test.ts
index 85673218762..c8629093ab5 100644
--- a/src/test/items/scope_lens.test.ts
+++ b/src/test/items/scope_lens.test.ts
@@ -1,13 +1,10 @@
-import { BattlerIndex } from "#app/battle";
-import { CritBoosterModifier } from "#app/modifier/modifier";
-import { modifierTypes } from "#app/modifier/modifier-type";
-import { MoveEffectPhase } from "#app/phases/move-effect-phase";
-import * as Utils from "#app/utils";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phase from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
describe("Items - Scope Lens", () => {
let phaserGame: Phaser.Game;
@@ -26,47 +23,29 @@ describe("Items - Scope Lens", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.override.enemySpecies(Species.MAGIKARP);
- game.override.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
- game.override.disableCrits();
+ game.override
+ .enemySpecies(Species.MAGIKARP)
+ .enemyMoveset(SPLASH_ONLY)
+ .moveset([ Moves.POUND ])
+ .startingHeldItems([{ name: "SCOPE_LENS" }])
+ .battleType("single")
+ .disableCrits();
- game.override.battleType("single");
}, 20000);
- it("SCOPE_LENS activates in battle correctly", async () => {
- game.override.startingHeldItems([{ name: "SCOPE_LENS" }]);
- game.override.moveset([Moves.POUND]);
- const consoleSpy = vi.spyOn(console, "log");
+ it("should raise CRIT stage by 1", async () => {
await game.startBattle([
Species.GASTLY
]);
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ vi.spyOn(enemyPokemon, "getCritStage");
+
game.move.select(Moves.POUND);
- await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to(TurnEndPhase);
- await game.phaseInterceptor.to(MoveEffectPhase);
-
- expect(consoleSpy).toHaveBeenCalledWith("Applied", "Scope Lens", "");
- }, 20000);
-
- it("SCOPE_LENS held by random pokemon", async () => {
- await game.startBattle([
- Species.GASTLY
- ]);
-
- const partyMember = game.scene.getPlayerPokemon()!;
-
- // Making sure modifier is not applied without holding item
- const critLevel = new Utils.IntegerHolder(0);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
-
- expect(critLevel.value).toBe(0);
-
- // Giving Scope Lens to party member and testing if it applies
- partyMember.scene.addModifier(modifierTypes.SCOPE_LENS().newModifier(partyMember), true);
- partyMember.scene.applyModifiers(CritBoosterModifier, true, partyMember, critLevel);
-
- expect(critLevel.value).toBe(1);
+ expect(enemyPokemon.getCritStage).toHaveReturnedWith(1);
}, 20000);
});
diff --git a/src/test/items/temp_stat_stage_booster.test.ts b/src/test/items/temp_stat_stage_booster.test.ts
new file mode 100644
index 00000000000..c81703220db
--- /dev/null
+++ b/src/test/items/temp_stat_stage_booster.test.ts
@@ -0,0 +1,175 @@
+import { BATTLE_STATS, Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
+import { Species } from "#enums/species";
+import Phase from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { Moves } from "#app/enums/moves";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { SPLASH_ONLY } from "../utils/testUtils";
+import { Abilities } from "#app/enums/abilities";
+import { TempStatStageBoosterModifier } from "#app/modifier/modifier";
+import { Mode } from "#app/ui/ui";
+import { Button } from "#app/enums/buttons";
+import { CommandPhase } from "#app/phases/command-phase";
+import { NewBattlePhase } from "#app/phases/new-battle-phase";
+import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { BattleEndPhase } from "#app/phases/battle-end-phase";
+import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
+import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
+
+
+describe("Items - Temporary Stat Stage Boosters", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phase.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.SHUCKLE)
+ .enemyMoveset(SPLASH_ONLY)
+ .enemyAbility(Abilities.BALL_FETCH)
+ .moveset([ Moves.TACKLE, Moves.SPLASH, Moves.HONE_CLAWS, Moves.BELLY_DRUM ])
+ .startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
+ });
+
+ it("should provide a x1.3 stat stage multiplier", async() => {
+ await game.startBattle([
+ Species.PIKACHU
+ ]);
+
+ const partyMember = game.scene.getPlayerPokemon()!;
+
+ vi.spyOn(partyMember, "getStatStageMultiplier");
+
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
+
+ expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3);
+ }, 20000);
+
+ it("should increase existing ACC stat stage by 1 for X_ACCURACY only", async() => {
+ game.override
+ .startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }])
+ .ability(Abilities.SIMPLE);
+
+ await game.startBattle([
+ Species.PIKACHU
+ ]);
+
+ const partyMember = game.scene.getPlayerPokemon()!;
+
+ vi.spyOn(partyMember, "getAccuracyMultiplier");
+
+ // Raise ACC by +2 stat stages
+ game.move.select(Moves.HONE_CLAWS);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ // ACC at +3 stat stages yields a x2 multiplier
+ expect(partyMember.getAccuracyMultiplier).toHaveReturnedWith(2);
+ }, 20000);
+
+
+ it("should increase existing stat stage multiplier by 3/10 for the rest of the boosters", async() => {
+ await game.startBattle([
+ Species.PIKACHU
+ ]);
+
+ const partyMember = game.scene.getPlayerPokemon()!;
+
+ vi.spyOn(partyMember, "getStatStageMultiplier");
+
+ // Raise ATK by +1 stat stage
+ game.move.select(Moves.HONE_CLAWS);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ // ATK at +1 stat stage yields a x1.5 multiplier, add 0.3 from X_ATTACK
+ expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.8);
+ }, 20000);
+
+ it("should not increase past maximum stat stage multiplier", async() => {
+ game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }, { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
+
+ await game.startBattle([
+ Species.PIKACHU
+ ]);
+
+ const partyMember = game.scene.getPlayerPokemon()!;
+
+ vi.spyOn(partyMember, "getStatStageMultiplier");
+ vi.spyOn(partyMember, "getAccuracyMultiplier");
+
+ // Set all stat stages to 6
+ vi.spyOn(partyMember.summonData, "statStages", "get").mockReturnValue(new Array(BATTLE_STATS.length).fill(6));
+
+ game.move.select(Moves.TACKLE);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(partyMember.getAccuracyMultiplier).toHaveReturnedWith(3);
+ expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(4);
+ }, 20000);
+
+ it("should renew how many battles are left of existing booster when picking up new booster of same type", async() => {
+ game.override
+ .startingLevel(200)
+ .itemRewards([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
+
+ await game.startBattle([
+ Species.PIKACHU
+ ]);
+
+ game.move.select(Moves.SPLASH);
+
+ await game.doKillOpponents();
+
+ await game.phaseInterceptor.to(BattleEndPhase);
+
+ const modifier = game.scene.findModifier(m => m instanceof TempStatStageBoosterModifier) as TempStatStageBoosterModifier;
+ expect(modifier.getBattlesLeft()).toBe(4);
+
+ // Forced X_ATTACK to spawn in the first slot with override
+ game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
+ const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler;
+ // Traverse to first modifier slot
+ handler.setCursor(0);
+ handler.setRowCursor(ShopCursorTarget.REWARDS);
+ handler.processInput(Button.ACTION);
+ }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(NewBattlePhase), true);
+
+ await game.phaseInterceptor.to(TurnInitPhase);
+
+ // Making sure only one booster is in the modifier list even after picking up another
+ let count = 0;
+ for (const m of game.scene.modifiers) {
+ if (m instanceof TempStatStageBoosterModifier) {
+ count++;
+ expect((m as TempStatStageBoosterModifier).getBattlesLeft()).toBe(5);
+ }
+ }
+ expect(count).toBe(1);
+ }, 20000);
+});
diff --git a/src/test/items/thick_club.test.ts b/src/test/items/thick_club.test.ts
index 347921446e6..bcb6b371264 100644
--- a/src/test/items/thick_club.test.ts
+++ b/src/test/items/thick_club.test.ts
@@ -1,4 +1,4 @@
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
@@ -37,29 +37,29 @@ describe("Items - Thick Club", () => {
const partyMember = game.scene.getParty()[0];
- // Checking consoe log to make sure Thick Club is applied when getBattleStat (with the appropriate stat) is called
- partyMember.getBattleStat(Stat.DEF);
+ // Checking console log to make sure Thick Club is applied when getEffectiveStat (with the appropriate stat) is called
+ partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
- partyMember.getBattleStat(Stat.SPDEF);
+ partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.ATK);
+ partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPATK);
+ partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
- partyMember.getBattleStat(Stat.SPD);
+ partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
});
diff --git a/src/test/localization/battle-stat.test.ts b/src/test/localization/battle-stat.test.ts
deleted file mode 100644
index b5ba698c4b6..00000000000
--- a/src/test/localization/battle-stat.test.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "#app/data/battle-stat";
-import deBattleStat from "#app/locales/de/battle.json";
-import dePokemonInfo from "#app/locales/de/pokemon-info.json";
-import enBattleStat from "#app/locales/en/battle.json";
-import enPokemonInfo from "#app/locales/en/pokemon-info.json";
-import esBattleStat from "#app/locales/es/battle.json";
-import esPokemonInfo from "#app/locales/es/pokemon-info.json";
-import frBattleStat from "#app/locales/fr/battle.json";
-import frPokemonInfo from "#app/locales/fr/pokemon-info.json";
-import itBattleStat from "#app/locales/it/battle.json";
-import itPokemonInfo from "#app/locales/it/pokemon-info.json";
-import koBattleStat from "#app/locales/ko/battle.json";
-import koPokemonInfo from "#app/locales/ko/pokemon-info.json";
-import ptBrBattleStat from "#app/locales/pt_BR/battle.json";
-import ptBrPokemonInfo from "#app/locales/pt_BR/pokemon-info.json";
-import zhCnBattleStat from "#app/locales/zh_CN/battle.json";
-import zhCnPokemonInfo from "#app/locales/zh_CN/pokemon-info.json";
-import zhTwBattleStat from "#app/locales/zh_TW/battle.json";
-import zhTwPokemonInfo from "#app/locales/zh_TW/pokemon-info.json";
-import i18next, { initI18n } from "#app/plugins/i18n";
-import { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor";
-import { beforeAll, describe, expect, it } from "vitest";
-
-interface BattleStatTestUnit {
- stat: BattleStat,
- key: string
-}
-
-interface BattleStatLevelTestUnit {
- levels: integer,
- up: boolean,
- key: string
- changedStats: integer
-}
-
-function testBattleStatName(stat: BattleStat, expectMessage: string) {
- if (!expectMessage) {
- return;
- } // not translated yet!
- const message = getBattleStatName(stat);
- console.log(`message ${message}, expected ${expectMessage}`);
- expect(message).toBe(expectMessage);
-}
-
-function testBattleStatLevelChangeDescription(levels: integer, up: boolean, expectMessage: string, changedStats: integer) {
- if (!expectMessage) {
- return;
- } // not translated yet!
- const message = getBattleStatLevelChangeDescription("{{pokemonNameWithAffix}}", "{{stats}}", levels, up, changedStats);
- console.log(`message ${message}, expected ${expectMessage}`);
- expect(message).toBe(expectMessage);
-}
-
-describe("Test for BattleStat Localization", () => {
- const battleStatUnits: BattleStatTestUnit[] = [];
- const battleStatLevelUnits: BattleStatLevelTestUnit[] = [];
-
- beforeAll(() => {
- initI18n();
-
- battleStatUnits.push({stat: BattleStat.ATK, key: "Stat.ATK"});
- battleStatUnits.push({stat: BattleStat.DEF, key: "Stat.DEF"});
- battleStatUnits.push({stat: BattleStat.SPATK, key: "Stat.SPATK"});
- battleStatUnits.push({stat: BattleStat.SPDEF, key: "Stat.SPDEF"});
- battleStatUnits.push({stat: BattleStat.SPD, key: "Stat.SPD"});
- battleStatUnits.push({stat: BattleStat.ACC, key: "Stat.ACC"});
- battleStatUnits.push({stat: BattleStat.EVA, key: "Stat.EVA"});
-
- battleStatLevelUnits.push({levels: 1, up: true, key: "statRose_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 2, up: true, key: "statSharplyRose_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 3, up: true, key: "statRoseDrastically_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 4, up: true, key: "statRoseDrastically_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 5, up: true, key: "statRoseDrastically_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 6, up: true, key: "statRoseDrastically_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 7, up: true, key: "statWontGoAnyHigher_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 1, up: false, key: "statFell_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 2, up: false, key: "statHarshlyFell_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 3, up: false, key: "statSeverelyFell_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 4, up: false, key: "statSeverelyFell_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 5, up: false, key: "statSeverelyFell_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 6, up: false, key: "statSeverelyFell_one", changedStats: 1});
- battleStatLevelUnits.push({levels: 7, up: false, key: "statWontGoAnyLower_one", changedStats: 1});
- });
-
- it("Test getBattleStatName() in English", async () => {
- i18next.changeLanguage("en");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, enPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in English", async () => {
- i18next.changeLanguage("en");
- battleStatLevelUnits.forEach(unit => {
- testBattleStatLevelChangeDescription(unit.levels, unit.up, enBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in Español", async () => {
- i18next.changeLanguage("es");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, esPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in Español", async () => {
- i18next.changeLanguage("es");
- battleStatLevelUnits.forEach(unit => {
- testBattleStatLevelChangeDescription(unit.levels, unit.up, esBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in Italiano", async () => {
- i18next.changeLanguage("it");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, itPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in Italiano", async () => {
- i18next.changeLanguage("it");
- battleStatLevelUnits.forEach(unit => {
- testBattleStatLevelChangeDescription(unit.levels, unit.up, itBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in Français", async () => {
- i18next.changeLanguage("fr");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, frPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in Français", async () => {
- i18next.changeLanguage("fr");
- battleStatLevelUnits.forEach(unit => {
- testBattleStatLevelChangeDescription(unit.levels, unit.up, frBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in Deutsch", async () => {
- i18next.changeLanguage("de");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, dePokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in Deutsch", async () => {
- i18next.changeLanguage("de");
- battleStatLevelUnits.forEach(unit => {
- testBattleStatLevelChangeDescription(unit.levels, unit.up, deBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in Português (BR)", async () => {
- i18next.changeLanguage("pt-BR");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, ptBrPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in Português (BR)", async () => {
- i18next.changeLanguage("pt-BR");
- battleStatLevelUnits.forEach(unit => {
- testBattleStatLevelChangeDescription(unit.levels, unit.up, ptBrBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in 简体中文", async () => {
- i18next.changeLanguage("zh-CN");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, zhCnPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in 简体中文", async () => {
- i18next.changeLanguage("zh-CN");
- battleStatLevelUnits.forEach(unit => {
- // In i18next, the pluralization rules are language-specific, and Chinese only supports the _other suffix.
- unit.key = unit.key.replace("one", "other");
- testBattleStatLevelChangeDescription(unit.levels, unit.up, zhCnBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in 繁體中文", async () => {
- i18next.changeLanguage("zh-TW");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, zhTwPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in 繁體中文", async () => {
- i18next.changeLanguage("zh-TW");
- battleStatLevelUnits.forEach(unit => {
- // In i18next, the pluralization rules are language-specific, and Chinese only supports the _other suffix.
- unit.key = unit.key.replace("one", "other");
- testBattleStatLevelChangeDescription(unit.levels, unit.up, zhTwBattleStat[unit.key], unit.changedStats);
- });
- });
-
- it("Test getBattleStatName() in 한국어", async () => {
- await i18next.changeLanguage("ko");
- battleStatUnits.forEach(unit => {
- testBattleStatName(unit.stat, koPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]);
- });
- });
-
- it("Test getBattleStatLevelChangeDescription() in 한국어", async () => {
- i18next.changeLanguage("ko", () => {
- battleStatLevelUnits.forEach(unit => {
- const processor = new KoreanPostpositionProcessor();
- const message = processor.process(koBattleStat[unit.key]);
- testBattleStatLevelChangeDescription(unit.levels, unit.up, message, unit.changedStats);
- });
- });
- });
-});
diff --git a/src/test/utils/misc.test.ts b/src/test/misc.test.ts
similarity index 90%
rename from src/test/utils/misc.test.ts
rename to src/test/misc.test.ts
index d7c10144ead..1052a282a64 100644
--- a/src/test/utils/misc.test.ts
+++ b/src/test/misc.test.ts
@@ -30,7 +30,7 @@ describe("Test misc", () => {
return response.json();
}).then(data => {
spy(); // Call the spy function
- expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
+ expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
});
expect(spy).toHaveBeenCalled();
});
@@ -43,7 +43,7 @@ describe("Test misc", () => {
return response.json();
}).then(data => {
spy(); // Call the spy function
- expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
+ expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
});
expect(spy).toHaveBeenCalled();
});
@@ -54,7 +54,7 @@ describe("Test misc", () => {
expect(response.ok).toBe(true);
expect(response.status).toBe(200);
- expect(data).toEqual({"username":"greenlamp", "lastSessionSlot":0});
+ expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 });
});
it("test apifetch mock sync", async () => {
diff --git a/src/test/moves/alluring_voice.test.ts b/src/test/moves/alluring_voice.test.ts
new file mode 100644
index 00000000000..9807d1bce85
--- /dev/null
+++ b/src/test/moves/alluring_voice.test.ts
@@ -0,0 +1,54 @@
+import { BattlerIndex } from "#app/battle";
+import { Abilities } from "#app/enums/abilities";
+import { BattlerTagType } from "#app/enums/battler-tag-type";
+import { BerryPhase } from "#app/phases/berry-phase";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import GameManager from "#test/utils/gameManager";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+
+const TIMEOUT = 20 * 1000;
+
+describe("Moves - Alluring Voice", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .disableCrits()
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.ICE_SCALES)
+ .enemyMoveset(Array(4).fill(Moves.HOWL))
+ .startingLevel(10)
+ .enemyLevel(10)
+ .starterSpecies(Species.FEEBAS)
+ .ability(Abilities.BALL_FETCH)
+ .moveset([Moves.ALLURING_VOICE]);
+
+ });
+
+ it("should confuse the opponent if their stat stages were raised", async () => {
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.ALLURING_VOICE);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.phaseInterceptor.to(BerryPhase);
+
+ expect(enemy.getTag(BattlerTagType.CONFUSED)?.tagType).toBe("CONFUSED");
+ }, TIMEOUT);
+});
diff --git a/src/test/moves/baton_pass.test.ts b/src/test/moves/baton_pass.test.ts
index 602da9e37f8..1a4edafdd36 100644
--- a/src/test/moves/baton_pass.test.ts
+++ b/src/test/moves/baton_pass.test.ts
@@ -1,13 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { PostSummonPhase } from "#app/phases/post-summon-phase";
-import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { BattlerIndex } from "#app/battle";
import GameManager from "#app/test/utils/gameManager";
+import { Abilities } from "#enums/abilities";
+import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
+import { Stat } from "#enums/stat";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
-import { SPLASH_ONLY } from "../utils/testUtils";
-
describe("Moves - Baton Pass", () => {
let phaserGame: Phaser.Game;
@@ -27,35 +27,35 @@ describe("Moves - Baton Pass", () => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
- .enemySpecies(Species.DUGTRIO)
- .startingLevel(1)
- .startingWave(97)
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.BALL_FETCH)
.moveset([Moves.BATON_PASS, Moves.NASTY_PLOT, Moves.SPLASH])
+ .ability(Abilities.BALL_FETCH)
.enemyMoveset(SPLASH_ONLY)
.disableCrits();
});
- it("passes stat stage buffs when player uses it", async () => {
+ it("transfers all stat stages when player uses it", async() => {
// arrange
- await game.startBattle([
- Species.RAICHU,
- Species.SHUCKLE
- ]);
+ await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]);
// round 1 - buff
game.move.select(Moves.NASTY_PLOT);
await game.toNextTurn();
- expect(game.scene.getPlayerPokemon()!.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
+
+ let playerPokemon = game.scene.getPlayerPokemon()!;
+
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toEqual(2);
// round 2 - baton pass
game.move.select(Moves.BATON_PASS);
game.doSelectPartyPokemon(1);
- await game.phaseInterceptor.to(TurnEndPhase);
+ await game.phaseInterceptor.to("TurnEndPhase");
// assert
- const playerPkm = game.scene.getPlayerPokemon()!;
- expect(playerPkm.species.speciesId).toEqual(Species.SHUCKLE);
- expect(playerPkm.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
+ playerPokemon = game.scene.getPlayerPokemon()!;
+ expect(playerPokemon.species.speciesId).toEqual(Species.SHUCKLE);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toEqual(2);
}, 20000);
it("passes stat stage buffs when AI uses it", async () => {
@@ -63,10 +63,7 @@ describe("Moves - Baton Pass", () => {
game.override
.startingWave(5)
.enemyMoveset(new Array(4).fill([Moves.NASTY_PLOT]));
- await game.startBattle([
- Species.RAICHU,
- Species.SHUCKLE
- ]);
+ await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]);
// round 1 - ai buffs
game.move.select(Moves.SPLASH);
@@ -76,11 +73,11 @@ describe("Moves - Baton Pass", () => {
game.scene.getEnemyPokemon()!.hp = 100;
game.override.enemyMoveset(new Array(4).fill(Moves.BATON_PASS));
game.move.select(Moves.SPLASH);
- await game.phaseInterceptor.to(PostSummonPhase, false);
+ await game.phaseInterceptor.to("PostSummonPhase", false);
// assert
// check buffs are still there
- expect(game.scene.getEnemyPokemon()!.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
+ expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPATK)).toEqual(2);
// confirm that a switch actually happened. can't use species because I
// can't find a way to override trainer parties with more than 1 pokemon species
expect(game.scene.getEnemyPokemon()!.hp).not.toEqual(100);
@@ -91,4 +88,20 @@ describe("Moves - Baton Pass", () => {
"PostSummonPhase"
]);
}, 20000);
+
+ it("doesn't transfer effects that aren't transferrable", async() => {
+ game.override.enemyMoveset(Array(4).fill(Moves.SALT_CURE));
+ await game.classicMode.startBattle([Species.PIKACHU, Species.FEEBAS]);
+
+ const [player1, player2] = game.scene.getParty();
+
+ game.move.select(Moves.BATON_PASS);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.phaseInterceptor.to("MoveEndPhase");
+ expect(player1.findTag((t) => t.tagType === BattlerTagType.SALT_CURED)).toBeTruthy();
+ game.doSelectPartyPokemon(1);
+ await game.toNextTurn();
+
+ expect(player2.findTag((t) => t.tagType === BattlerTagType.SALT_CURED)).toBeUndefined();
+ }, 20000);
});
diff --git a/src/test/moves/belly_drum.test.ts b/src/test/moves/belly_drum.test.ts
index e4956c6e83a..7024deb3f18 100644
--- a/src/test/moves/belly_drum.test.ts
+++ b/src/test/moves/belly_drum.test.ts
@@ -1,8 +1,8 @@
-import { BattleStat } from "#app/data/battle-stat";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { toDmgValue } from "#app/utils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
+import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
@@ -43,8 +43,8 @@ describe("Moves - BELLY DRUM", () => {
// Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Belly_Drum_(move)
- test("Belly Drum raises the user's Attack to its max, at the cost of 1/2 of its maximum HP",
- async () => {
+ test("raises the user's ATK stat stage to its max, at the cost of 1/2 of its maximum HP",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
@@ -54,48 +54,48 @@ describe("Moves - BELLY DRUM", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
}, TIMEOUT
);
- test("Belly Drum will still take effect if an uninvolved stat is at max",
- async () => {
+ test("will still take effect if an uninvolved stat stage is at max",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
- // Here - BattleStat.ATK -> -3 and BattleStat.SPATK -> 6
- leadPokemon.summonData.battleStats[BattleStat.ATK] = -3;
- leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
+ // Here - Stat.ATK -> -3 and Stat.SPATK -> 6
+ leadPokemon.setStatStage(Stat.ATK, -3);
+ leadPokemon.setStatStage(Stat.SPATK, 6);
game.move.select(Moves.BELLY_DRUM);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
}, TIMEOUT
);
- test("Belly Drum fails if the pokemon's attack stat is at its maximum",
- async () => {
+ test("fails if the pokemon's ATK stat stage is at its maximum",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
- leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
+ leadPokemon.setStatStage(Stat.ATK, 6);
game.move.select(Moves.BELLY_DRUM);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
}, TIMEOUT
);
- test("Belly Drum fails if the user's health is less than 1/2",
- async () => {
+ test("fails if the user's health is less than 1/2",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
@@ -106,7 +106,7 @@ describe("Moves - BELLY DRUM", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
}, TIMEOUT
);
});
diff --git a/src/test/moves/burning_jealousy.test.ts b/src/test/moves/burning_jealousy.test.ts
new file mode 100644
index 00000000000..2cb6a0bc52a
--- /dev/null
+++ b/src/test/moves/burning_jealousy.test.ts
@@ -0,0 +1,103 @@
+import { BattlerIndex } from "#app/battle";
+import { allMoves } from "#app/data/move";
+import { Abilities } from "#app/enums/abilities";
+import { StatusEffect } from "#app/enums/status-effect";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import GameManager from "#test/utils/gameManager";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+const TIMEOUT = 20 * 1000;
+
+describe("Moves - Burning Jealousy", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .disableCrits()
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.ICE_SCALES)
+ .enemyMoveset(Array(4).fill(Moves.HOWL))
+ .startingLevel(10)
+ .enemyLevel(10)
+ .starterSpecies(Species.FEEBAS)
+ .ability(Abilities.BALL_FETCH)
+ .moveset([Moves.BURNING_JEALOUSY, Moves.GROWL]);
+
+ });
+
+ it("should burn the opponent if their stat stages were raised", async () => {
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.BURNING_JEALOUSY);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ expect(enemy.status?.effect).toBe(StatusEffect.BURN);
+ }, TIMEOUT);
+
+ it("should still burn the opponent if their stat stages were both raised and lowered in the same turn", async () => {
+ game.override
+ .starterSpecies(0)
+ .battleType("double");
+ await game.classicMode.startBattle([Species.FEEBAS, Species.ABRA]);
+
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.BURNING_JEALOUSY);
+ game.move.select(Moves.GROWL, 1);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2]);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ expect(enemy.status?.effect).toBe(StatusEffect.BURN);
+ }, TIMEOUT);
+
+ it("should ignore stat stages raised by IMPOSTER", async () => {
+ game.override
+ .enemySpecies(Species.DITTO)
+ .enemyAbility(Abilities.IMPOSTER)
+ .enemyMoveset(SPLASH_ONLY);
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.BURNING_JEALOUSY);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ expect(enemy.status?.effect).toBeUndefined();
+ }, TIMEOUT);
+
+ it.skip("should ignore weakness policy", async () => { // TODO: Make this test if WP is implemented
+ await game.classicMode.startBattle();
+ }, TIMEOUT);
+
+ it("should be boosted by Sheer Force even if opponent didn't raise stat stages", async () => {
+ game.override
+ .ability(Abilities.SHEER_FORCE)
+ .enemyMoveset(SPLASH_ONLY);
+ vi.spyOn(allMoves[Moves.BURNING_JEALOUSY], "calculateBattlePower");
+ await game.classicMode.startBattle();
+
+ game.move.select(Moves.BURNING_JEALOUSY);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ expect(allMoves[Moves.BURNING_JEALOUSY].calculateBattlePower).toHaveReturnedWith(allMoves[Moves.BURNING_JEALOUSY].power * 5461 / 4096);
+ }, TIMEOUT);
+});
diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts
index 34ecf8f39f6..8511b3179c6 100644
--- a/src/test/moves/ceaseless_edge.test.ts
+++ b/src/test/moves/ceaseless_edge.test.ts
@@ -110,7 +110,7 @@ describe("Moves - Ceaseless Edge", () => {
const hpBeforeSpikes = game.scene.currentBattle.enemyParty[1].hp;
// Check HP of pokemon that WILL BE switched in (index 1)
- game.forceOpponentToSwitch();
+ game.forceEnemyToSwitch();
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(hpBeforeSpikes);
diff --git a/src/test/moves/clangorous_soul.test.ts b/src/test/moves/clangorous_soul.test.ts
index 9ea6da91595..9bd3bc2379e 100644
--- a/src/test/moves/clangorous_soul.test.ts
+++ b/src/test/moves/clangorous_soul.test.ts
@@ -1,12 +1,11 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#test/utils/gameManager";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
-import { toDmgValue } from "#app/utils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
+import { Stat } from "#enums/stat";
import { SPLASH_ONLY } from "#test/utils/testUtils";
-import Phaser from "phaser";
-import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
const TIMEOUT = 20 * 1000;
/** HP Cost of Move */
@@ -14,7 +13,7 @@ const RATIO = 3;
/** Amount of extra HP lost */
const PREDAMAGE = 15;
-describe("Moves - CLANGOROUS_SOUL", () => {
+describe("Moves - Clangorous Soul", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@@ -40,91 +39,91 @@ describe("Moves - CLANGOROUS_SOUL", () => {
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Clangorous_Soul_(move)
- test("Clangorous Soul raises the user's Attack, Defense, Special Attack, Special Defense and Speed by one stage each, at the cost of 1/3 of its maximum HP",
- async () => {
+ it("raises the user's ATK, DEF, SPATK, SPDEF, and SPD stat stages by 1 each at the cost of 1/3 of its maximum HP",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
- const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
+ const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
game.move.select(Moves.CLANGOROUS_SOUL);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(1);
- expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(1);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(1);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(1);
+ expect(leadPokemon.getStatStage(Stat.DEF)).toBe(1);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(1);
+ expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(1);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1);
}, TIMEOUT
);
- test("Clangorous Soul will still take effect if one or more of the involved stats are not at max",
- async () => {
+ it("will still take effect if one or more of the involved stat stages are not at max",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
- const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
+ const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
- //Here - BattleStat.SPD -> 0 and BattleStat.SPDEF -> 4
- leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.DEF] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPDEF] = 4;
+ //Here - Stat.SPD -> 0 and Stat.SPDEF -> 4
+ leadPokemon.setStatStage(Stat.ATK, 6);
+ leadPokemon.setStatStage(Stat.DEF, 6);
+ leadPokemon.setStatStage(Stat.SPATK, 6);
+ leadPokemon.setStatStage(Stat.SPDEF, 4);
game.move.select(Moves.CLANGOROUS_SOUL);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(5);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(1);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.DEF)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(5);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1);
}, TIMEOUT
);
- test("Clangorous Soul fails if all stats involved are at max",
- async () => {
+ it("fails if all stat stages involved are at max",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
- leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.DEF] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPDEF] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPD] = 6;
+ leadPokemon.setStatStage(Stat.ATK, 6);
+ leadPokemon.setStatStage(Stat.DEF, 6);
+ leadPokemon.setStatStage(Stat.SPATK, 6);
+ leadPokemon.setStatStage(Stat.SPDEF, 6);
+ leadPokemon.setStatStage(Stat.SPD, 6);
game.move.select(Moves.CLANGOROUS_SOUL);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.DEF)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6);
}, TIMEOUT
);
- test("Clangorous Soul fails if the user's health is less than 1/3",
- async () => {
+ it("fails if the user's health is less than 1/3",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
- const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
+ const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
leadPokemon.hp = hpLost - PREDAMAGE;
game.move.select(Moves.CLANGOROUS_SOUL);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.DEF)).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0);
}, TIMEOUT
);
});
diff --git a/src/test/moves/crafty_shield.test.ts b/src/test/moves/crafty_shield.test.ts
index a341a50b0b9..e73a1fd256d 100644
--- a/src/test/moves/crafty_shield.test.ts
+++ b/src/test/moves/crafty_shield.test.ts
@@ -1,13 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { BattlerTagType } from "#app/enums/battler-tag-type";
-import { BerryPhase } from "#app/phases/berry-phase";
-import { CommandPhase } from "#app/phases/command-phase";
-import { Abilities } from "#enums/abilities";
-import { Moves } from "#enums/moves";
-import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import GameManager from "../utils/gameManager";
+import { Species } from "#enums/species";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { BattlerTagType } from "#app/enums/battler-tag-type";
+import { BerryPhase } from "#app/phases/berry-phase";
+import { CommandPhase } from "#app/phases/command-phase";
const TIMEOUT = 20 * 1000;
@@ -55,7 +55,7 @@ describe("Moves - Crafty Shield", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- leadPokemon.forEach(p => expect(p.summonData.battleStats[BattleStat.ATK]).toBe(0));
+ leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
}, TIMEOUT
);
@@ -117,8 +117,8 @@ describe("Moves - Crafty Shield", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- expect(leadPokemon[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(leadPokemon[1].summonData.battleStats[BattleStat.ATK]).toBe(2);
+ expect(leadPokemon[0].getStatStage(Stat.ATK)).toBe(0);
+ expect(leadPokemon[1].getStatStage(Stat.ATK)).toBe(2);
}
);
});
diff --git a/src/test/moves/double_team.test.ts b/src/test/moves/double_team.test.ts
index c45c8bd8516..fa224c8df9e 100644
--- a/src/test/moves/double_team.test.ts
+++ b/src/test/moves/double_team.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { Abilities } from "#app/enums/abilities";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
@@ -32,20 +32,20 @@ describe("Moves - Double Team", () => {
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
- it("increases the user's evasion by one stage.", async () => {
+ it("raises the user's EVA stat stage by 1", async () => {
await game.startBattle([Species.MAGIKARP]);
const ally = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(enemy, "getAccuracyMultiplier");
- expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(0);
+ expect(ally.getStatStage(Stat.EVA)).toBe(0);
game.move.select(Moves.DOUBLE_TEAM);
await game.phaseInterceptor.to(TurnEndPhase);
await game.toNextTurn();
- expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(1);
+ expect(ally.getStatStage(Stat.EVA)).toBe(1);
expect(enemy.getAccuracyMultiplier).toHaveReturnedWith(.75);
});
});
diff --git a/src/test/moves/dragon_rage.test.ts b/src/test/moves/dragon_rage.test.ts
index 223635575ab..5da6e082ce5 100644
--- a/src/test/moves/dragon_rage.test.ts
+++ b/src/test/moves/dragon_rage.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { Type } from "#app/data/type";
import { Species } from "#app/enums/species";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
@@ -63,9 +63,8 @@ describe("Moves - Dragon Rage", () => {
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores resistances", async () => {
@@ -74,20 +73,18 @@ describe("Moves - Dragon Rage", () => {
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
- it("ignores stat changes", async () => {
+ it("ignores SPATK stat stages", async () => {
game.override.disableCrits();
- partyPokemon.summonData.battleStats[BattleStat.SPATK] = 2;
+ partyPokemon.setStatStage(Stat.SPATK, 2);
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores stab", async () => {
@@ -96,9 +93,8 @@ describe("Moves - Dragon Rage", () => {
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores criticals", async () => {
@@ -106,20 +102,18 @@ describe("Moves - Dragon Rage", () => {
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
- it("ignores damage modification from abilities such as ice scales", async () => {
+ it("ignores damage modification from abilities, for example ICE_SCALES", async () => {
game.override.disableCrits();
game.override.enemyAbility(Abilities.ICE_SCALES);
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores multi hit", async () => {
@@ -128,8 +122,7 @@ describe("Moves - Dragon Rage", () => {
game.move.select(Moves.DRAGON_RAGE);
await game.phaseInterceptor.to(TurnEndPhase);
- const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
- expect(damageDealt).toBe(dragonRageDamage);
+ expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
});
diff --git a/src/test/moves/fake_out.test.ts b/src/test/moves/fake_out.test.ts
new file mode 100644
index 00000000000..ac09917daea
--- /dev/null
+++ b/src/test/moves/fake_out.test.ts
@@ -0,0 +1,82 @@
+import GameManager from "#app/test/utils/gameManager";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Moves - Fake Out", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.CORVIKNIGHT)
+ .starterSpecies(Species.FEEBAS)
+ .moveset([Moves.FAKE_OUT, Moves.SPLASH])
+ .enemyMoveset(SPLASH_ONLY)
+ .disableCrits();
+ });
+
+ it("can only be used on the first turn a pokemon is sent out", async() => {
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.FAKE_OUT);
+ await game.toNextTurn();
+
+ expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
+ const postTurnOneHp = enemy.hp;
+
+ game.move.select(Moves.FAKE_OUT);
+ await game.toNextTurn();
+
+ expect(enemy.hp).toBe(postTurnOneHp);
+
+ game.move.select(Moves.SPLASH);
+ await game.doKillOpponents();
+ await game.toNextWave();
+
+ const newEnemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.FAKE_OUT);
+ await game.toNextTurn();
+
+ expect(newEnemy.hp).toBe(newEnemy.getMaxHp());
+ }, 20000);
+
+ it("can be used again if recalled and sent back out", async() => {
+ game.override.startingWave(4);
+ await game.classicMode.startBattle();
+
+ const enemy1 = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.FAKE_OUT);
+ await game.phaseInterceptor.to("MoveEndPhase");
+
+ expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp());
+
+ await game.doKillOpponents();
+ await game.toNextWave();
+
+ game.move.select(Moves.FAKE_OUT);
+ await game.toNextTurn();
+
+ const enemy2 = game.scene.getEnemyPokemon()!;
+
+ expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp());
+ }, 20000);
+});
diff --git a/src/test/moves/fillet_away.test.ts b/src/test/moves/fillet_away.test.ts
index b2ff9e25dba..a639a86c5c1 100644
--- a/src/test/moves/fillet_away.test.ts
+++ b/src/test/moves/fillet_away.test.ts
@@ -1,8 +1,8 @@
-import { BattleStat } from "#app/data/battle-stat";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { toDmgValue } from "#app/utils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
+import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
@@ -40,8 +40,8 @@ describe("Moves - FILLET AWAY", () => {
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/fillet_away_(move)
- test("Fillet Away raises the user's Attack, Special Attack, and Speed by two stages each, at the cost of 1/2 of its maximum HP",
- async () => {
+ test("raises the user's ATK, SPATK, and SPD stat stages by 2 each, at the cost of 1/2 of its maximum HP",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
@@ -51,55 +51,55 @@ describe("Moves - FILLET AWAY", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(2);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(2);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(2);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2);
}, TIMEOUT
);
- test("Fillet Away will still take effect if one or more of the involved stats are not at max",
- async () => {
+ test("still takes effect if one or more of the involved stat stages are not at max",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = toDmgValue(leadPokemon.getMaxHp() / RATIO);
- //Here - BattleStat.SPD -> 0 and BattleStat.SPATK -> 3
- leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPATK] = 3;
+ //Here - Stat.SPD -> 0 and Stat.SPATK -> 3
+ leadPokemon.setStatStage(Stat.ATK, 6);
+ leadPokemon.setStatStage(Stat.SPATK, 3);
game.move.select(Moves.FILLET_AWAY);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(5);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(2);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(5);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2);
}, TIMEOUT
);
- test("Fillet Away fails if all stats involved are at max",
- async () => {
+ test("fails if all stat stages involved are at max",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
- leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
- leadPokemon.summonData.battleStats[BattleStat.SPD] = 6;
+ leadPokemon.setStatStage(Stat.ATK, 6);
+ leadPokemon.setStatStage(Stat.SPATK, 6);
+ leadPokemon.setStatStage(Stat.SPD, 6);
game.move.select(Moves.FILLET_AWAY);
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6);
}, TIMEOUT
);
- test("Fillet Away fails if the user's health is less than 1/2",
- async () => {
+ test("fails if the user's health is less than 1/2",
+ async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
@@ -110,9 +110,9 @@ describe("Moves - FILLET AWAY", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0);
- expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0);
}, TIMEOUT
);
});
diff --git a/src/test/moves/fissure.test.ts b/src/test/moves/fissure.test.ts
index 51122b269b8..34612d1fb18 100644
--- a/src/test/moves/fissure.test.ts
+++ b/src/test/moves/fissure.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { Species } from "#app/enums/species";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import { DamagePhase } from "#app/phases/damage-phase";
@@ -52,7 +52,7 @@ describe("Moves - Fissure", () => {
game.scene.clearEnemyHeldItemModifiers();
});
- it("ignores damage modification from abilities such as fur coat", async () => {
+ it("ignores damage modification from abilities, for example FUR_COAT", async () => {
game.override.ability(Abilities.NO_GUARD);
game.override.enemyAbility(Abilities.FUR_COAT);
@@ -62,10 +62,10 @@ describe("Moves - Fissure", () => {
expect(enemyPokemon.isFainted()).toBe(true);
});
- it("ignores accuracy stat", async () => {
+ it("ignores user's ACC stat stage", async () => {
vi.spyOn(partyPokemon, "getAccuracyMultiplier");
- enemyPokemon.summonData.battleStats[BattleStat.ACC] = -6;
+ partyPokemon.setStatStage(Stat.ACC, -6);
game.move.select(Moves.FISSURE);
@@ -75,10 +75,10 @@ describe("Moves - Fissure", () => {
expect(partyPokemon.getAccuracyMultiplier).toHaveReturnedWith(1);
});
- it("ignores evasion stat", async () => {
+ it("ignores target's EVA stat stage", async () => {
vi.spyOn(partyPokemon, "getAccuracyMultiplier");
- enemyPokemon.summonData.battleStats[BattleStat.EVA] = 6;
+ enemyPokemon.setStatStage(Stat.EVA, 6);
game.move.select(Moves.FISSURE);
diff --git a/src/test/moves/flower_shield.test.ts b/src/test/moves/flower_shield.test.ts
index b3e50219aec..ffe8ae995d3 100644
--- a/src/test/moves/flower_shield.test.ts
+++ b/src/test/moves/flower_shield.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { SemiInvulnerableTag } from "#app/data/battler-tags";
import { Type } from "#app/data/type";
import { Biome } from "#app/enums/biome";
@@ -34,24 +34,24 @@ describe("Moves - Flower Shield", () => {
game.override.enemyMoveset(SPLASH_ONLY);
});
- it("increases defense of all Grass-type Pokemon on the field by one stage - single battle", async () => {
+ it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - single battle", async () => {
game.override.enemySpecies(Species.CHERRIM);
await game.startBattle([Species.MAGIKARP]);
const cherrim = game.scene.getEnemyPokemon()!;
const magikarp = game.scene.getPlayerPokemon()!;
- expect(magikarp.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(0);
+ expect(magikarp.getStatStage(Stat.DEF)).toBe(0);
+ expect(cherrim.getStatStage(Stat.DEF)).toBe(0);
game.move.select(Moves.FLOWER_SHIELD);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(magikarp.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(1);
+ expect(magikarp.getStatStage(Stat.DEF)).toBe(0);
+ expect(cherrim.getStatStage(Stat.DEF)).toBe(1);
});
- it("increases defense of all Grass-type Pokemon on the field by one stage - double battle", async () => {
+ it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - double battle", async () => {
game.override.enemySpecies(Species.MAGIKARP).startingBiome(Biome.GRASS).battleType("double");
await game.startBattle([Species.CHERRIM, Species.MAGIKARP]);
@@ -60,21 +60,21 @@ describe("Moves - Flower Shield", () => {
const grassPokemons = field.filter(p => p.getTypes().includes(Type.GRASS));
const nonGrassPokemons = field.filter(pokemon => !grassPokemons.includes(pokemon));
- grassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0));
- nonGrassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0));
+ grassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
+ nonGrassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
game.move.select(Moves.FLOWER_SHIELD);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(TurnEndPhase);
- grassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(1));
- nonGrassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0));
+ grassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(1));
+ nonGrassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
});
/**
* See semi-vulnerable state tags. {@linkcode SemiInvulnerableTag}
*/
- it("does not increase defense of a pokemon in semi-vulnerable state", async () => {
+ it("does not raise DEF stat stage for a Pokemon in semi-vulnerable state", async () => {
game.override.enemySpecies(Species.PARAS);
game.override.enemyMoveset([Moves.DIG, Moves.DIG, Moves.DIG, Moves.DIG]);
game.override.enemyLevel(50);
@@ -83,32 +83,32 @@ describe("Moves - Flower Shield", () => {
const paras = game.scene.getEnemyPokemon()!;
const cherrim = game.scene.getPlayerPokemon()!;
- expect(paras.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(0);
+ expect(paras.getStatStage(Stat.DEF)).toBe(0);
+ expect(cherrim.getStatStage(Stat.DEF)).toBe(0);
expect(paras.getTag(SemiInvulnerableTag)).toBeUndefined;
game.move.select(Moves.FLOWER_SHIELD);
await game.phaseInterceptor.to(TurnEndPhase);
expect(paras.getTag(SemiInvulnerableTag)).toBeDefined();
- expect(paras.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(1);
+ expect(paras.getStatStage(Stat.DEF)).toBe(0);
+ expect(cherrim.getStatStage(Stat.DEF)).toBe(1);
});
- it("does nothing if there are no Grass-type pokemon on the field", async () => {
+ it("does nothing if there are no Grass-type Pokemon on the field", async () => {
game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.MAGIKARP]);
const enemy = game.scene.getEnemyPokemon()!;
const ally = game.scene.getPlayerPokemon()!;
- expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(ally.summonData.battleStats[BattleStat.DEF]).toBe(0);
+ expect(enemy.getStatStage(Stat.DEF)).toBe(0);
+ expect(ally.getStatStage(Stat.DEF)).toBe(0);
game.move.select(Moves.FLOWER_SHIELD);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(ally.summonData.battleStats[BattleStat.DEF]).toBe(0);
+ expect(enemy.getStatStage(Stat.DEF)).toBe(0);
+ expect(ally.getStatStage(Stat.DEF)).toBe(0);
});
});
diff --git a/src/test/moves/focus_punch.test.ts b/src/test/moves/focus_punch.test.ts
index 99399623a1c..249647f0294 100644
--- a/src/test/moves/focus_punch.test.ts
+++ b/src/test/moves/focus_punch.test.ts
@@ -123,7 +123,7 @@ describe("Moves - Focus Punch", () => {
await game.startBattle([Species.CHARIZARD]);
- game.forceOpponentToSwitch();
+ game.forceEnemyToSwitch();
game.move.select(Moves.FOCUS_PUNCH);
await game.phaseInterceptor.to(TurnStartPhase);
diff --git a/src/test/moves/follow_me.test.ts b/src/test/moves/follow_me.test.ts
index d7ef199df3e..7d0c4fdb546 100644
--- a/src/test/moves/follow_me.test.ts
+++ b/src/test/moves/follow_me.test.ts
@@ -1,5 +1,5 @@
+import { Stat } from "#enums/stat";
import { BattlerIndex } from "#app/battle";
-import { Stat } from "#app/data/pokemon-stat";
import { Abilities } from "#app/enums/abilities";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
@@ -28,48 +28,55 @@ describe("Moves - Follow Me", () => {
game = new GameManager(phaserGame);
game.override.battleType("double");
game.override.starterSpecies(Species.AMOONGUSS);
+ game.override.ability(Abilities.BALL_FETCH);
game.override.enemySpecies(Species.SNORLAX);
game.override.startingLevel(100);
game.override.enemyLevel(100);
game.override.moveset([Moves.FOLLOW_ME, Moves.RAGE_POWDER, Moves.SPOTLIGHT, Moves.QUICK_ATTACK]);
- game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
+ game.override.enemyMoveset([Moves.TACKLE, Moves.FOLLOW_ME, Moves.SPLASH]);
});
test(
"move should redirect enemy attacks to the user",
async () => {
- await game.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerField();
- const playerStartingHp = playerPokemon.map(p => p.hp);
-
game.move.select(Moves.FOLLOW_ME);
game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY);
+
+ // Force both enemies to target the player Pokemon that did not use Follow Me
+ await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
+ await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
+
await game.phaseInterceptor.to(TurnEndPhase, false);
- expect(playerPokemon[0].hp).toBeLessThan(playerStartingHp[0]);
- expect(playerPokemon[1].hp).toBe(playerStartingHp[1]);
+ expect(playerPokemon[0].hp).toBeLessThan(playerPokemon[0].getMaxHp());
+ expect(playerPokemon[1].hp).toBe(playerPokemon[1].getMaxHp());
}, TIMEOUT
);
test(
"move should redirect enemy attacks to the first ally that uses it",
async () => {
- await game.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerField();
- const playerStartingHp = playerPokemon.map(p => p.hp);
-
game.move.select(Moves.FOLLOW_ME);
game.move.select(Moves.FOLLOW_ME, 1);
+
+ // Each player is targeted by an enemy
+ await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER);
+ await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
+
await game.phaseInterceptor.to(TurnEndPhase, false);
- playerPokemon.sort((a, b) => a.getBattleStat(Stat.SPD) - b.getBattleStat(Stat.SPD));
+ playerPokemon.sort((a, b) => a.getEffectiveStat(Stat.SPD) - b.getEffectiveStat(Stat.SPD));
- expect(playerPokemon[1].hp).toBeLessThan(playerStartingHp[1]);
- expect(playerPokemon[0].hp).toBe(playerStartingHp[0]);
+ expect(playerPokemon[1].hp).toBeLessThan(playerPokemon[1].getMaxHp());
+ expect(playerPokemon[0].hp).toBe(playerPokemon[0].getMaxHp());
}, TIMEOUT
);
@@ -78,21 +85,23 @@ describe("Moves - Follow Me", () => {
async () => {
game.override.ability(Abilities.STALWART);
game.override.moveset([Moves.QUICK_ATTACK]);
- game.override.enemyMoveset([Moves.FOLLOW_ME, Moves.FOLLOW_ME, Moves.FOLLOW_ME, Moves.FOLLOW_ME]);
- await game.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
const enemyPokemon = game.scene.getEnemyField();
- const enemyStartingHp = enemyPokemon.map(p => p.hp);
-
game.move.select(Moves.QUICK_ATTACK, 0, BattlerIndex.ENEMY);
game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2);
+
+ // Target doesn't need to be specified if the move is self-targeted
+ await game.forceEnemyMove(Moves.FOLLOW_ME);
+ await game.forceEnemyMove(Moves.SPLASH);
+
await game.phaseInterceptor.to(TurnEndPhase, false);
// If redirection was bypassed, both enemies should be damaged
- expect(enemyPokemon[0].hp).toBeLessThan(enemyStartingHp[0]);
- expect(enemyPokemon[1].hp).toBeLessThan(enemyStartingHp[1]);
+ expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp());
+ expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[1].getMaxHp());
}, TIMEOUT
);
@@ -100,21 +109,22 @@ describe("Moves - Follow Me", () => {
"move effect should be bypassed by Snipe Shot",
async () => {
game.override.moveset([Moves.SNIPE_SHOT]);
- game.override.enemyMoveset([Moves.FOLLOW_ME, Moves.FOLLOW_ME, Moves.FOLLOW_ME, Moves.FOLLOW_ME]);
- await game.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
const enemyPokemon = game.scene.getEnemyField();
- const enemyStartingHp = enemyPokemon.map(p => p.hp);
-
game.move.select(Moves.SNIPE_SHOT, 0, BattlerIndex.ENEMY);
game.move.select(Moves.SNIPE_SHOT, 1, BattlerIndex.ENEMY_2);
+
+ await game.forceEnemyMove(Moves.FOLLOW_ME);
+ await game.forceEnemyMove(Moves.SPLASH);
+
await game.phaseInterceptor.to(TurnEndPhase, false);
// If redirection was bypassed, both enemies should be damaged
- expect(enemyPokemon[0].hp).toBeLessThan(enemyStartingHp[0]);
- expect(enemyPokemon[1].hp).toBeLessThan(enemyStartingHp[1]);
+ expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp());
+ expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[1].getMaxHp());
}, TIMEOUT
);
});
diff --git a/src/test/moves/freeze_dry.test.ts b/src/test/moves/freeze_dry.test.ts
new file mode 100644
index 00000000000..445a432a812
--- /dev/null
+++ b/src/test/moves/freeze_dry.test.ts
@@ -0,0 +1,107 @@
+import { BattlerIndex } from "#app/battle";
+import { Abilities } from "#app/enums/abilities";
+import { Moves } from "#app/enums/moves";
+import { Species } from "#app/enums/species";
+import GameManager from "#test/utils/gameManager";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+
+describe("Moves - Freeze-Dry", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+ const TIMEOUT = 20 * 1000;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.BALL_FETCH)
+ .enemyMoveset(SPLASH_ONLY)
+ .starterSpecies(Species.FEEBAS)
+ .ability(Abilities.BALL_FETCH)
+ .moveset([Moves.FREEZE_DRY]);
+ });
+
+ it("should deal 2x damage to pure water types", async () => {
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+ vi.spyOn(enemy, "getMoveEffectiveness");
+
+ game.move.select(Moves.FREEZE_DRY);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to("MoveEffectPhase");
+
+ expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
+ }, TIMEOUT);
+
+ it("should deal 4x damage to water/flying types", async () => {
+ game.override.enemySpecies(Species.WINGULL);
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+ vi.spyOn(enemy, "getMoveEffectiveness");
+
+ game.move.select(Moves.FREEZE_DRY);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to("MoveEffectPhase");
+
+ expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4);
+ }, TIMEOUT);
+
+ it("should deal 1x damage to water/fire types", async () => {
+ game.override.enemySpecies(Species.VOLCANION);
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+ vi.spyOn(enemy, "getMoveEffectiveness");
+
+ game.move.select(Moves.FREEZE_DRY);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to("MoveEffectPhase");
+
+ expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1);
+ }, TIMEOUT);
+
+ // enable if this is ever fixed (lol)
+ it.todo("should deal 2x damage to water types under Normalize", async () => {
+ game.override.ability(Abilities.NORMALIZE);
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+ vi.spyOn(enemy, "getMoveEffectiveness");
+
+ game.move.select(Moves.FREEZE_DRY);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to("MoveEffectPhase");
+
+ expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
+ }, TIMEOUT);
+
+ // enable once Electrify is implemented (and the interaction is fixed, as above)
+ it.todo("should deal 2x damage to water types under Electrify", async () => {
+ game.override.enemyMoveset(Array(4).fill(Moves.ELECTRIFY));
+ await game.classicMode.startBattle();
+
+ const enemy = game.scene.getEnemyPokemon()!;
+ vi.spyOn(enemy, "getMoveEffectiveness");
+
+ game.move.select(Moves.FREEZE_DRY);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
+ }, TIMEOUT);
+});
diff --git a/src/test/moves/freezy_frost.test.ts b/src/test/moves/freezy_frost.test.ts
index 00d7104d373..ae42d5b6dc6 100644
--- a/src/test/moves/freezy_frost.test.ts
+++ b/src/test/moves/freezy_frost.test.ts
@@ -1,82 +1,61 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { allMoves } from "#app/data/move";
-import { MoveEndPhase } from "#app/phases/move-end-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
-import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import { allMoves } from "#app/data/move";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
describe("Moves - Freezy Frost", () => {
- describe("integration tests", () => {
- let phaserGame: Phaser.Game;
- let game: GameManager;
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
- beforeAll(() => {
- phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
- });
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
+ });
- afterEach(() => {
- game.phaseInterceptor.restoreOg();
- });
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
- beforeEach(() => {
- game = new GameManager(phaserGame);
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
- game.override.battleType("single");
+ game.override.battleType("single");
- game.override.enemySpecies(Species.RATTATA);
- game.override.enemyLevel(100);
- game.override.enemyMoveset(SPLASH_ONLY);
- game.override.enemyAbility(Abilities.NONE);
+ game.override.enemySpecies(Species.RATTATA);
+ game.override.enemyLevel(100);
+ game.override.enemyMoveset(SPLASH_ONLY);
+ game.override.enemyAbility(Abilities.NONE);
- game.override.startingLevel(100);
- game.override.moveset([Moves.FREEZY_FROST, Moves.SWORDS_DANCE, Moves.CHARM, Moves.SPLASH]);
- vi.spyOn(allMoves[Moves.FREEZY_FROST], "accuracy", "get").mockReturnValue(100);
- game.override.ability(Abilities.NONE);
- });
+ game.override.startingLevel(100);
+ game.override.moveset([Moves.FREEZY_FROST, Moves.SWORDS_DANCE, Moves.CHARM, Moves.SPLASH]);
+ vi.spyOn(allMoves[Moves.FREEZY_FROST], "accuracy", "get").mockReturnValue(100);
+ game.override.ability(Abilities.NONE);
+ });
- it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, player uses Freezy Frost to clear all stat changes", { timeout: 10000 }, async () => {
- await game.startBattle([Species.RATTATA]);
- const user = game.scene.getPlayerPokemon()!;
- const enemy = game.scene.getEnemyPokemon()!;
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ it("should clear all stat stage changes", { timeout: 10000 }, async () => {
+ await game.startBattle([Species.RATTATA]);
+ const user = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
- game.move.select(Moves.SWORDS_DANCE);
- await game.phaseInterceptor.to(TurnInitPhase);
+ expect(user.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(0);
- game.move.select(Moves.CHARM);
- await game.phaseInterceptor.to(TurnInitPhase);
- const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
- const enemyAtkBefore = enemy.summonData.battleStats[BattleStat.ATK];
- expect(userAtkBefore).toBe(2);
- expect(enemyAtkBefore).toBe(-2);
+ game.move.select(Moves.SWORDS_DANCE);
+ await game.phaseInterceptor.to(TurnInitPhase);
- game.move.select(Moves.FREEZY_FROST);
- await game.phaseInterceptor.to(TurnInitPhase);
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
- });
+ game.move.select(Moves.CHARM);
+ await game.phaseInterceptor.to(TurnInitPhase);
+ expect(user.getStatStage(Stat.ATK)).toBe(2);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
- it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, enemy uses Freezy Frost to clear all stat changes", { timeout: 10000 }, async () => {
- game.override.enemyMoveset([Moves.FREEZY_FROST, Moves.FREEZY_FROST, Moves.FREEZY_FROST, Moves.FREEZY_FROST]);
- await game.startBattle([Species.SHUCKLE]); // Shuckle for slower Swords Dance on first turn so Freezy Frost doesn't affect it.
- const user = game.scene.getPlayerPokemon()!;
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
-
- game.move.select(Moves.SWORDS_DANCE);
- await game.phaseInterceptor.to(TurnInitPhase);
-
- const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
- expect(userAtkBefore).toBe(2);
-
- game.move.select(Moves.SPLASH);
- await game.phaseInterceptor.to(MoveEndPhase);
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
- });
+ game.move.select(Moves.FREEZY_FROST);
+ await game.phaseInterceptor.to(TurnInitPhase);
+ expect(user.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(0);
});
});
diff --git a/src/test/moves/fusion_flare.test.ts b/src/test/moves/fusion_flare.test.ts
index 471f6a2ac7b..0a8f6f9115d 100644
--- a/src/test/moves/fusion_flare.test.ts
+++ b/src/test/moves/fusion_flare.test.ts
@@ -27,7 +27,7 @@ describe("Moves - Fusion Flare", () => {
game.override.moveset([fusionFlare]);
game.override.startingLevel(1);
- game.override.enemySpecies(Species.RESHIRAM);
+ game.override.enemySpecies(Species.RATTATA);
game.override.enemyMoveset([Moves.REST, Moves.REST, Moves.REST, Moves.REST]);
game.override.battleType("single");
diff --git a/src/test/moves/fusion_flare_bolt.test.ts b/src/test/moves/fusion_flare_bolt.test.ts
index ebef5148778..a8372fcaaab 100644
--- a/src/test/moves/fusion_flare_bolt.test.ts
+++ b/src/test/moves/fusion_flare_bolt.test.ts
@@ -1,6 +1,6 @@
+import { Stat } from "#enums/stat";
import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/move";
-import { Stat } from "#app/data/pokemon-stat";
import { DamagePhase } from "#app/phases/damage-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
diff --git a/src/test/moves/gigaton_hammer.test.ts b/src/test/moves/gigaton_hammer.test.ts
new file mode 100644
index 00000000000..9379e9d98b2
--- /dev/null
+++ b/src/test/moves/gigaton_hammer.test.ts
@@ -0,0 +1,80 @@
+import { BattlerIndex } from "#app/battle.js";
+import GameManager from "#app/test/utils/gameManager";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Moves - Gigaton Hammer", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MAGIKARP)
+ .starterSpecies(Species.FEEBAS)
+ .moveset([Moves.GIGATON_HAMMER])
+ .startingLevel(10)
+ .enemyLevel(100)
+ .enemyMoveset(SPLASH_ONLY)
+ .disableCrits();
+ });
+
+ it("can't be used two turns in a row", async() => {
+ await game.classicMode.startBattle();
+
+ const enemy1 = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.GIGATON_HAMMER);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to("MoveEndPhase");
+
+ expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp());
+
+ await game.doKillOpponents();
+ await game.toNextWave();
+
+ game.move.select(Moves.GIGATON_HAMMER);
+ await game.toNextTurn();
+
+ const enemy2 = game.scene.getEnemyPokemon()!;
+
+ expect(enemy2.hp).toBe(enemy2.getMaxHp());
+ }, 20000);
+
+ it("can be used again if recalled and sent back out", async() => {
+ game.override.startingWave(4);
+ await game.classicMode.startBattle();
+
+ const enemy1 = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.GIGATON_HAMMER);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.phaseInterceptor.to("MoveEndPhase");
+
+ expect(enemy1.hp).toBeLessThan(enemy1.getMaxHp());
+
+ await game.doKillOpponents();
+ await game.toNextWave();
+
+ game.move.select(Moves.GIGATON_HAMMER);
+ await game.toNextTurn();
+
+ const enemy2 = game.scene.getEnemyPokemon()!;
+
+ expect(enemy2.hp).toBeLessThan(enemy2.getMaxHp());
+ }, 20000);
+});
diff --git a/src/test/moves/growth.test.ts b/src/test/moves/growth.test.ts
index dfbf5406351..defe5e26f41 100644
--- a/src/test/moves/growth.test.ts
+++ b/src/test/moves/growth.test.ts
@@ -1,14 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { Stat } from "#app/data/pokemon-stat";
-import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
-
+import { SPLASH_ONLY } from "../utils/testUtils";
+import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
describe("Moves - Growth", () => {
let phaserGame: Phaser.Game;
@@ -26,31 +25,25 @@ describe("Moves - Growth", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- const moveToUse = Moves.GROWTH;
game.override.battleType("single");
- game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(Abilities.MOXIE);
game.override.ability(Abilities.INSOMNIA);
- game.override.startingLevel(2000);
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
+ game.override.moveset([ Moves.GROWTH ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
});
- it("GROWTH", async () => {
- const moveToUse = Moves.GROWTH;
+ it("should raise SPATK stat stage by 1", async() => {
await game.startBattle([
- Species.MIGHTYENA,
- Species.MIGHTYENA,
+ Species.MIGHTYENA
]);
- let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[Stat.SPATK]).toBe(0);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
+ const playerPokemon = game.scene.getPlayerPokemon()!;
- game.move.select(moveToUse);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
+
+ game.move.select(Moves.GROWTH);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
- battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
- expect(battleStatsPokemon[BattleStat.SPATK]).toBe(1);
+
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, 20000);
});
diff --git a/src/test/moves/guard_split.test.ts b/src/test/moves/guard_split.test.ts
new file mode 100644
index 00000000000..f95d09f726c
--- /dev/null
+++ b/src/test/moves/guard_split.test.ts
@@ -0,0 +1,82 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Moves - Guard Split", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemyAbility(Abilities.NONE)
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .moveset([ Moves.GUARD_SPLIT ])
+ .ability(Abilities.NONE);
+ });
+
+ it("should average the user's DEF and SPDEF stats with those of the target", async () => {
+ game.override.enemyMoveset(SPLASH_ONLY);
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const avgDef = Math.floor((player.getStat(Stat.DEF, false) + enemy.getStat(Stat.DEF, false)) / 2);
+ const avgSpDef = Math.floor((player.getStat(Stat.SPDEF, false) + enemy.getStat(Stat.SPDEF, false)) / 2);
+
+ game.move.select(Moves.GUARD_SPLIT);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.DEF, false)).toBe(avgDef);
+ expect(enemy.getStat(Stat.DEF, false)).toBe(avgDef);
+
+ expect(player.getStat(Stat.SPDEF, false)).toBe(avgSpDef);
+ expect(enemy.getStat(Stat.SPDEF, false)).toBe(avgSpDef);
+ }, 20000);
+
+ it("should be idempotent", async () => {
+ game.override.enemyMoveset(new Array(4).fill(Moves.GUARD_SPLIT));
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const avgDef = Math.floor((player.getStat(Stat.DEF, false) + enemy.getStat(Stat.DEF, false)) / 2);
+ const avgSpDef = Math.floor((player.getStat(Stat.SPDEF, false) + enemy.getStat(Stat.SPDEF, false)) / 2);
+
+ game.move.select(Moves.GUARD_SPLIT);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ game.move.select(Moves.GUARD_SPLIT);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.DEF, false)).toBe(avgDef);
+ expect(enemy.getStat(Stat.DEF, false)).toBe(avgDef);
+
+ expect(player.getStat(Stat.SPDEF, false)).toBe(avgSpDef);
+ expect(enemy.getStat(Stat.SPDEF, false)).toBe(avgSpDef);
+ }, 20000);
+});
diff --git a/src/test/moves/guard_swap.test.ts b/src/test/moves/guard_swap.test.ts
new file mode 100644
index 00000000000..407d475de09
--- /dev/null
+++ b/src/test/moves/guard_swap.test.ts
@@ -0,0 +1,63 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { MoveEndPhase } from "#app/phases/move-end-phase";
+
+describe("Moves - Guard Swap", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemyAbility(Abilities.BALL_FETCH)
+ .enemyMoveset(new Array(4).fill(Moves.SHELL_SMASH))
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .moveset([ Moves.GUARD_SWAP ])
+ .ability(Abilities.NONE);
+ });
+
+ it("should swap the user's DEF AND SPDEF stat stages with the target's", async () => {
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ // Should start with no stat stages
+ const player = game.scene.getPlayerPokemon()!;
+ // After Shell Smash, should have +2 in ATK and SPATK, -1 in DEF and SPDEF
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.GUARD_SWAP);
+
+ await game.phaseInterceptor.to(MoveEndPhase);
+
+ expect(player.getStatStage(Stat.DEF)).toBe(0);
+ expect(player.getStatStage(Stat.SPDEF)).toBe(0);
+ expect(enemy.getStatStage(Stat.DEF)).toBe(-1);
+ expect(enemy.getStatStage(Stat.SPDEF)).toBe(-1);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStatStage(Stat.DEF)).toBe(-1);
+ expect(player.getStatStage(Stat.SPDEF)).toBe(-1);
+ expect(enemy.getStatStage(Stat.DEF)).toBe(0);
+ expect(enemy.getStatStage(Stat.SPDEF)).toBe(0);
+ }, 20000);
+});
diff --git a/src/test/moves/haze.test.ts b/src/test/moves/haze.test.ts
index 8a32a40cb32..42081ce74e8 100644
--- a/src/test/moves/haze.test.ts
+++ b/src/test/moves/haze.test.ts
@@ -1,13 +1,12 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { MoveEndPhase } from "#app/phases/move-end-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
-import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
describe("Moves - Haze", () => {
describe("integration tests", () => {
@@ -37,44 +36,28 @@ describe("Moves - Haze", () => {
game.override.ability(Abilities.NONE);
});
- it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, player uses Haze to clear all stat changes", { timeout: 10000 }, async () => {
+ it("should reset all stat changes of all Pokemon on field", { timeout: 10000 }, async () => {
await game.startBattle([Species.RATTATA]);
const user = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
+
+ expect(user.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(0);
game.move.select(Moves.SWORDS_DANCE);
await game.phaseInterceptor.to(TurnInitPhase);
game.move.select(Moves.CHARM);
await game.phaseInterceptor.to(TurnInitPhase);
- const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
- const enemyAtkBefore = enemy.summonData.battleStats[BattleStat.ATK];
- expect(userAtkBefore).toBe(2);
- expect(enemyAtkBefore).toBe(-2);
+
+ expect(user.getStatStage(Stat.ATK)).toBe(2);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
game.move.select(Moves.HAZE);
await game.phaseInterceptor.to(TurnInitPhase);
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
- expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
- });
- it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, enemy uses Haze to clear all stat changes", { timeout: 10000 }, async () => {
- game.override.enemyMoveset([Moves.HAZE, Moves.HAZE, Moves.HAZE, Moves.HAZE]);
- await game.startBattle([Species.SHUCKLE]); // Shuckle for slower Swords Dance on first turn so Haze doesn't affect it.
- const user = game.scene.getPlayerPokemon()!;
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
-
- game.move.select(Moves.SWORDS_DANCE);
- await game.phaseInterceptor.to(TurnInitPhase);
-
- const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
- expect(userAtkBefore).toBe(2);
-
- game.move.select(Moves.SPLASH);
- await game.phaseInterceptor.to(MoveEndPhase);
- expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(user.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(0);
});
});
});
diff --git a/src/test/moves/lash_out.test.ts b/src/test/moves/lash_out.test.ts
new file mode 100644
index 00000000000..74d9fcd66c0
--- /dev/null
+++ b/src/test/moves/lash_out.test.ts
@@ -0,0 +1,52 @@
+import { BattlerIndex } from "#app/battle";
+import { allMoves } from "#app/data/move";
+import { Abilities } from "#app/enums/abilities";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import GameManager from "#test/utils/gameManager";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+
+const TIMEOUT = 20 * 1000;
+
+describe("Moves - Lash Out", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .disableCrits()
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.FUR_COAT)
+ .enemyMoveset(Array(4).fill(Moves.GROWL))
+ .startingLevel(10)
+ .enemyLevel(10)
+ .starterSpecies(Species.FEEBAS)
+ .ability(Abilities.BALL_FETCH)
+ .moveset([Moves.LASH_OUT]);
+
+ });
+
+ it("should deal double damage if the user's stat stages were lowered this turn", async () => {
+ vi.spyOn(allMoves[Moves.LASH_OUT], "calculateBattlePower");
+ await game.classicMode.startBattle();
+
+ game.move.select(Moves.LASH_OUT);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.phaseInterceptor.to("BerryPhase");
+
+ expect(allMoves[Moves.LASH_OUT].calculateBattlePower).toHaveReturnedWith(150);
+ }, TIMEOUT);
+});
diff --git a/src/test/moves/make_it_rain.test.ts b/src/test/moves/make_it_rain.test.ts
index 0af7763f175..e41472d7561 100644
--- a/src/test/moves/make_it_rain.test.ts
+++ b/src/test/moves/make_it_rain.test.ts
@@ -1,13 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { MoveEndPhase } from "#app/phases/move-end-phase";
-import { StatChangePhase } from "#app/phases/stat-change-phase";
+import { Stat } from "#enums/stat";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
-import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import { MoveEndPhase } from "#app/phases/move-end-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
const TIMEOUT = 20 * 1000;
@@ -36,17 +36,17 @@ describe("Moves - Make It Rain", () => {
game.override.enemyLevel(100);
});
- it("should only reduce Sp. Atk. once in a double battle", async () => {
+ it("should only lower SPATK stat stage by 1 once in a double battle", async () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
- const playerPokemon = game.scene.getPlayerField();
+ const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.MAKE_IT_RAIN);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(MoveEndPhase);
- expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
it("should apply effects even if the target faints", async () => {
@@ -60,10 +60,10 @@ describe("Moves - Make It Rain", () => {
game.move.select(Moves.MAKE_IT_RAIN);
- await game.phaseInterceptor.to(StatChangePhase);
+ await game.phaseInterceptor.to(StatStageChangePhase);
expect(enemyPokemon.isFainted()).toBe(true);
- expect(playerPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
it("should reduce Sp. Atk. once after KOing two enemies", async () => {
@@ -71,22 +71,22 @@ describe("Moves - Make It Rain", () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
- const playerPokemon = game.scene.getPlayerField();
+ const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyField();
game.move.select(Moves.MAKE_IT_RAIN);
game.move.select(Moves.SPLASH, 1);
- await game.phaseInterceptor.to(StatChangePhase);
+ await game.phaseInterceptor.to(StatStageChangePhase);
enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true));
- expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
- it("should reduce Sp. Atk if it only hits the second target", async () => {
+ it("should lower SPATK stat stage by 1 if it only hits the second target", async () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
- const playerPokemon = game.scene.getPlayerField();
+ const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.MAKE_IT_RAIN);
game.move.select(Moves.SPLASH, 1);
@@ -96,6 +96,6 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(MoveEndPhase);
- expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
});
diff --git a/src/test/moves/mat_block.test.ts b/src/test/moves/mat_block.test.ts
index 29a97806242..4a95985eb92 100644
--- a/src/test/moves/mat_block.test.ts
+++ b/src/test/moves/mat_block.test.ts
@@ -1,13 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { BerryPhase } from "#app/phases/berry-phase";
-import { CommandPhase } from "#app/phases/command-phase";
-import { TurnEndPhase } from "#app/phases/turn-end-phase";
-import { Abilities } from "#enums/abilities";
-import { Moves } from "#enums/moves";
-import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import GameManager from "../utils/gameManager";
+import { Species } from "#enums/species";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { BerryPhase } from "#app/phases/berry-phase";
+import { CommandPhase } from "#app/phases/command-phase";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
const TIMEOUT = 20 * 1000;
@@ -76,7 +76,7 @@ describe("Moves - Mat Block", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- leadPokemon.forEach(p => expect(p.summonData.battleStats[BattleStat.ATK]).toBe(-2));
+ leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(-2));
}, TIMEOUT
);
diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts
index 389e4a4c4cf..c86906ea240 100644
--- a/src/test/moves/octolock.test.ts
+++ b/src/test/moves/octolock.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { TrappedTag } from "#app/data/battler-tags";
import { CommandPhase } from "#app/phases/command-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
@@ -12,68 +12,106 @@ import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Octolock", () => {
- describe("integration tests", () => {
- let phaserGame: Phaser.Game;
- let game: GameManager;
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
- beforeAll(() => {
- phaserGame = new Phaser.Game({
- type: Phaser.HEADLESS,
- });
- });
-
- afterEach(() => {
- game.phaseInterceptor.restoreOg();
- });
-
- beforeEach(() => {
- game = new GameManager(phaserGame);
-
- game.override.battleType("single");
-
- game.override.enemySpecies(Species.RATTATA);
- game.override.enemyMoveset(SPLASH_ONLY);
- game.override.enemyAbility(Abilities.BALL_FETCH);
-
- game.override.startingLevel(2000);
- game.override.moveset([Moves.OCTOLOCK, Moves.SPLASH]);
- game.override.ability(Abilities.BALL_FETCH);
- });
-
- it("Reduces DEf and SPDEF by 1 each turn", { timeout: 10000 }, async () => {
- await game.startBattle([Species.GRAPPLOCT]);
-
- const enemyPokemon = game.scene.getEnemyField();
-
- // use Octolock and advance to init phase of next turn to check for stat changes
- game.move.select(Moves.OCTOLOCK);
- await game.phaseInterceptor.to(TurnInitPhase);
-
- expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-1);
- expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-1);
-
- // take a second turn to make sure stat changes occur again
- await game.phaseInterceptor.to(CommandPhase);
- game.move.select(Moves.SPLASH);
-
- await game.phaseInterceptor.to(TurnInitPhase);
- expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-2);
- expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
- });
-
- it("Traps the target pokemon", { timeout: 10000 }, async () => {
- await game.startBattle([Species.GRAPPLOCT]);
-
- const enemyPokemon = game.scene.getEnemyField();
-
- // before Octolock - enemy should not be trapped
- expect(enemyPokemon[0].findTag(t => t instanceof TrappedTag)).toBeUndefined();
-
- game.move.select(Moves.OCTOLOCK);
-
- // after Octolock - enemy should be trapped
- await game.phaseInterceptor.to(MoveEndPhase);
- expect(enemyPokemon[0].findTag(t => t instanceof TrappedTag)).toBeDefined();
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
});
});
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+
+ game.override.battleType("single")
+ .enemySpecies(Species.RATTATA)
+ .enemyMoveset(SPLASH_ONLY)
+ .enemyAbility(Abilities.BALL_FETCH)
+ .startingLevel(2000)
+ .moveset([ Moves.OCTOLOCK, Moves.SPLASH ])
+ .ability(Abilities.BALL_FETCH);
+ });
+
+ it("lowers DEF and SPDEF stat stages of the target Pokemon by 1 each turn", { timeout: 10000 }, async () => {
+ await game.classicMode.startBattle([ Species.GRAPPLOCT ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ // use Octolock and advance to init phase of next turn to check for stat changes
+ game.move.select(Moves.OCTOLOCK);
+ await game.phaseInterceptor.to(TurnInitPhase);
+
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1);
+ expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1);
+
+ // take a second turn to make sure stat changes occur again
+ await game.phaseInterceptor.to(CommandPhase);
+ game.move.select(Moves.SPLASH);
+
+ await game.phaseInterceptor.to(TurnInitPhase);
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-2);
+ expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-2);
+ });
+
+ it("if target pokemon has BIG_PECKS, should only lower SPDEF stat stage by 1", { timeout: 10000 }, async () => {
+ game.override.enemyAbility(Abilities.BIG_PECKS);
+ await game.classicMode.startBattle([ Species.GRAPPLOCT ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ // use Octolock and advance to init phase of next turn to check for stat changes
+ game.move.select(Moves.OCTOLOCK);
+ await game.phaseInterceptor.to(TurnInitPhase);
+
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1);
+ });
+
+ it("if target pokemon has WHITE_SMOKE, should not reduce any stat stages", { timeout: 10000 }, async () => {
+ game.override.enemyAbility(Abilities.WHITE_SMOKE);
+ await game.classicMode.startBattle([ Species.GRAPPLOCT ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ // use Octolock and advance to init phase of next turn to check for stat changes
+ game.move.select(Moves.OCTOLOCK);
+ await game.phaseInterceptor.to(TurnInitPhase);
+
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0);
+ });
+
+ it("if target pokemon has CLEAR_BODY, should not reduce any stat stages", { timeout: 10000 }, async () => {
+ game.override.enemyAbility(Abilities.CLEAR_BODY);
+ await game.classicMode.startBattle([ Species.GRAPPLOCT ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ // use Octolock and advance to init phase of next turn to check for stat changes
+ game.move.select(Moves.OCTOLOCK);
+ await game.phaseInterceptor.to(TurnInitPhase);
+
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0);
+ });
+
+ it("traps the target pokemon", { timeout: 10000 }, async () => {
+ await game.classicMode.startBattle([ Species.GRAPPLOCT ]);
+
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ // before Octolock - enemy should not be trapped
+ expect(enemyPokemon.findTag(t => t instanceof TrappedTag)).toBeUndefined();
+
+ game.move.select(Moves.OCTOLOCK);
+
+ // after Octolock - enemy should be trapped
+ await game.phaseInterceptor.to(MoveEndPhase);
+ expect(enemyPokemon.findTag(t => t instanceof TrappedTag)).toBeDefined();
+ });
});
diff --git a/src/test/moves/parting_shot.test.ts b/src/test/moves/parting_shot.test.ts
index 7c2ca3f334c..d9535ca6482 100644
--- a/src/test/moves/parting_shot.test.ts
+++ b/src/test/moves/parting_shot.test.ts
@@ -1,14 +1,14 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { BerryPhase } from "#app/phases/berry-phase";
-import { FaintPhase } from "#app/phases/faint-phase";
-import { MessagePhase } from "#app/phases/message-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, test } from "vitest";
import GameManager from "../utils/gameManager";
+import { Stat } from "#enums/stat";
+import { BerryPhase } from "#app/phases/berry-phase";
+import { FaintPhase } from "#app/phases/faint-phase";
+import { MessagePhase } from "#app/phases/message-phase";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { SPLASH_ONLY } from "../utils/testUtils";
const TIMEOUT = 20 * 1000;
@@ -51,9 +51,8 @@ describe("Moves - Parting Shot", () => {
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- const battleStatsOpponent = enemyPokemon.summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW);
}, TIMEOUT
);
@@ -72,9 +71,8 @@ describe("Moves - Parting Shot", () => {
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- const battleStatsOpponent = enemyPokemon.summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW);
}, TIMEOUT
);
@@ -108,16 +106,15 @@ describe("Moves - Parting Shot", () => {
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon).toBeDefined();
- const battleStatsOpponent = enemyPokemon.summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-6);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(-6);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6);
// now parting shot should fail
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-6);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(-6);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-6);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-6);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW);
}, TIMEOUT
);
@@ -137,9 +134,8 @@ describe("Moves - Parting Shot", () => {
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- const battleStatsOpponent = enemyPokemon.summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW);
}, TIMEOUT
);
@@ -158,9 +154,8 @@ describe("Moves - Parting Shot", () => {
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- const battleStatsOpponent = enemyPokemon.summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW);
}, TIMEOUT
);
@@ -176,9 +171,8 @@ describe("Moves - Parting Shot", () => {
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- const battleStatsOpponent = enemyPokemon.summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(-1);
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MURKROW);
}, TIMEOUT
);
@@ -199,9 +193,9 @@ describe("Moves - Parting Shot", () => {
game.move.select(Moves.PARTING_SHOT);
await game.phaseInterceptor.to(BerryPhase, false);
- const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
- expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH);
}, TIMEOUT
);
diff --git a/src/test/moves/power_split.test.ts b/src/test/moves/power_split.test.ts
new file mode 100644
index 00000000000..a532a90a54d
--- /dev/null
+++ b/src/test/moves/power_split.test.ts
@@ -0,0 +1,82 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Moves - Power Split", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemyAbility(Abilities.NONE)
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .moveset([ Moves.POWER_SPLIT ])
+ .ability(Abilities.NONE);
+ });
+
+ it("should average the user's ATK and SPATK stats with those of the target", async () => {
+ game.override.enemyMoveset(SPLASH_ONLY);
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
+ const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
+
+ game.move.select(Moves.POWER_SPLIT);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
+ expect(enemy.getStat(Stat.ATK, false)).toBe(avgAtk);
+
+ expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ }, 20000);
+
+ it("should be idempotent", async () => {
+ game.override.enemyMoveset(new Array(4).fill(Moves.POWER_SPLIT));
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
+ const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
+
+ game.move.select(Moves.POWER_SPLIT);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ game.move.select(Moves.POWER_SPLIT);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
+ expect(enemy.getStat(Stat.ATK, false)).toBe(avgAtk);
+
+ expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ }, 20000);
+});
diff --git a/src/test/moves/power_swap.test.ts b/src/test/moves/power_swap.test.ts
new file mode 100644
index 00000000000..f1efeaa3af3
--- /dev/null
+++ b/src/test/moves/power_swap.test.ts
@@ -0,0 +1,62 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { MoveEndPhase } from "#app/phases/move-end-phase";
+
+describe("Moves - Power Swap", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemyAbility(Abilities.BALL_FETCH)
+ .enemyMoveset(new Array(4).fill(Moves.SHELL_SMASH))
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .moveset([ Moves.POWER_SWAP ])
+ .ability(Abilities.NONE);
+ });
+
+ it("should swap the user's ATK AND SPATK stat stages with the target's", async () => {
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ // Should start with no stat stages
+ const player = game.scene.getPlayerPokemon()!;
+ // After Shell Smash, should have +2 in ATK and SPATK, -1 in DEF and SPDEF
+ const enemy = game.scene.getEnemyPokemon()!;
+ game.move.select(Moves.POWER_SWAP);
+
+ await game.phaseInterceptor.to(MoveEndPhase);
+
+ expect(player.getStatStage(Stat.ATK)).toBe(0);
+ expect(player.getStatStage(Stat.SPATK)).toBe(0);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(2);
+ expect(enemy.getStatStage(Stat.SPATK)).toBe(2);
+
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStatStage(Stat.ATK)).toBe(2);
+ expect(player.getStatStage(Stat.SPATK)).toBe(2);
+ expect(enemy.getStatStage(Stat.ATK)).toBe(0);
+ expect(enemy.getStatStage(Stat.SPATK)).toBe(0);
+ }, 20000);
+});
diff --git a/src/test/moves/protect.test.ts b/src/test/moves/protect.test.ts
index 3fd51f4bc93..d792f586a37 100644
--- a/src/test/moves/protect.test.ts
+++ b/src/test/moves/protect.test.ts
@@ -1,13 +1,13 @@
-import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
-import { BattleStat } from "#app/data/battle-stat";
-import { allMoves } from "#app/data/move";
-import { BerryPhase } from "#app/phases/berry-phase";
-import { Abilities } from "#enums/abilities";
-import { Moves } from "#enums/moves";
-import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "../utils/gameManager";
+import { Species } from "#enums/species";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { allMoves } from "#app/data/move";
+import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
+import { BerryPhase } from "#app/phases/berry-phase";
const TIMEOUT = 20 * 1000;
@@ -87,7 +87,7 @@ describe("Moves - Protect", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
+ expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
}, TIMEOUT
);
diff --git a/src/test/moves/quick_guard.test.ts b/src/test/moves/quick_guard.test.ts
index 26d9a74e9fd..25f98f8fa61 100644
--- a/src/test/moves/quick_guard.test.ts
+++ b/src/test/moves/quick_guard.test.ts
@@ -1,12 +1,12 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { BerryPhase } from "#app/phases/berry-phase";
-import { CommandPhase } from "#app/phases/command-phase";
-import { Abilities } from "#enums/abilities";
-import { Moves } from "#enums/moves";
-import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import GameManager from "../utils/gameManager";
+import { Species } from "#enums/species";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { BerryPhase } from "#app/phases/berry-phase";
+import { CommandPhase } from "#app/phases/command-phase";
const TIMEOUT = 20 * 1000;
@@ -76,7 +76,7 @@ describe("Moves - Quick Guard", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- leadPokemon.forEach(p => expect(p.summonData.battleStats[BattleStat.ATK]).toBe(0));
+ leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
}, TIMEOUT
);
diff --git a/src/test/moves/rage_powder.test.ts b/src/test/moves/rage_powder.test.ts
index 3e78c6fe0c9..3e9f422fda8 100644
--- a/src/test/moves/rage_powder.test.ts
+++ b/src/test/moves/rage_powder.test.ts
@@ -1,5 +1,4 @@
import { BattlerIndex } from "#app/battle";
-import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@@ -31,27 +30,27 @@ describe("Moves - Rage Powder", () => {
game.override.startingLevel(100);
game.override.enemyLevel(100);
game.override.moveset([Moves.FOLLOW_ME, Moves.RAGE_POWDER, Moves.SPOTLIGHT, Moves.QUICK_ATTACK]);
- game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
+ game.override.enemyMoveset([Moves.RAGE_POWDER, Moves.TACKLE, Moves.SPLASH]);
});
test(
"move effect should be bypassed by Grass type",
async () => {
- game.override.enemyMoveset([Moves.RAGE_POWDER, Moves.RAGE_POWDER, Moves.RAGE_POWDER, Moves.RAGE_POWDER]);
-
- await game.startBattle([Species.AMOONGUSS, Species.VENUSAUR]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.VENUSAUR]);
const enemyPokemon = game.scene.getEnemyField();
- const enemyStartingHp = enemyPokemon.map(p => p.hp);
-
game.move.select(Moves.QUICK_ATTACK, 0, BattlerIndex.ENEMY);
game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2);
- await game.phaseInterceptor.to(TurnEndPhase, false);
+
+ await game.forceEnemyMove(Moves.RAGE_POWDER);
+ await game.forceEnemyMove(Moves.SPLASH);
+
+ await game.phaseInterceptor.to("BerryPhase", false);
// If redirection was bypassed, both enemies should be damaged
- expect(enemyPokemon[0].hp).toBeLessThan(enemyStartingHp[0]);
- expect(enemyPokemon[1].hp).toBeLessThan(enemyStartingHp[1]);
+ expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp());
+ expect(enemyPokemon[1].hp).toBeLessThan(enemyPokemon[0].getMaxHp());
}, TIMEOUT
);
@@ -59,10 +58,9 @@ describe("Moves - Rage Powder", () => {
"move effect should be bypassed by Overcoat",
async () => {
game.override.ability(Abilities.OVERCOAT);
- game.override.enemyMoveset([Moves.RAGE_POWDER, Moves.RAGE_POWDER, Moves.RAGE_POWDER, Moves.RAGE_POWDER]);
// Test with two non-Grass type player Pokemon
- await game.startBattle([Species.BLASTOISE, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.BLASTOISE, Species.CHARIZARD]);
const enemyPokemon = game.scene.getEnemyField();
@@ -70,7 +68,7 @@ describe("Moves - Rage Powder", () => {
game.move.select(Moves.QUICK_ATTACK, 0, BattlerIndex.ENEMY);
game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2);
- await game.phaseInterceptor.to(TurnEndPhase, false);
+ await game.phaseInterceptor.to("BerryPhase", false);
// If redirection was bypassed, both enemies should be damaged
expect(enemyPokemon[0].hp).toBeLessThan(enemyStartingHp[0]);
diff --git a/src/test/moves/safeguard.test.ts b/src/test/moves/safeguard.test.ts
new file mode 100644
index 00000000000..94a7aa6031e
--- /dev/null
+++ b/src/test/moves/safeguard.test.ts
@@ -0,0 +1,150 @@
+import { BattlerIndex } from "#app/battle";
+import { allAbilities, PostDefendContactApplyStatusEffectAbAttr } from "#app/data/ability";
+import { Abilities } from "#app/enums/abilities";
+import { StatusEffect } from "#app/enums/status-effect";
+import GameManager from "#app/test/utils/gameManager";
+import { Moves } from "#enums/moves";
+import { Species } from "#enums/species";
+import Phaser from "phaser";
+import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+
+const TIMEOUT = 20 * 1000;
+
+describe("Moves - Safeguard", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.DRATINI)
+ .enemyMoveset(Array(4).fill(Moves.SAFEGUARD))
+ .enemyAbility(Abilities.BALL_FETCH)
+ .enemyLevel(5)
+ .starterSpecies(Species.DRATINI)
+ .moveset([Moves.NUZZLE, Moves.SPORE, Moves.YAWN, Moves.SPLASH])
+ .ability(Abilities.BALL_FETCH);
+ });
+
+ it("protects from damaging moves with additional effects", async () => {
+ await game.startBattle();
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.NUZZLE);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.toNextTurn();
+
+ expect(enemy.status).toBeUndefined();
+ }, TIMEOUT);
+
+ it("protects from status moves", async () => {
+ await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.SPORE);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.status).toBeUndefined();
+ }, TIMEOUT);
+
+ it("protects from confusion", async () => {
+ game.override.moveset([Moves.CONFUSE_RAY]);
+ await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.CONFUSE_RAY);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.summonData.tags).toEqual([]);
+ }, TIMEOUT);
+
+ it("protects ally from status", async () => {
+ game.override.battleType("double");
+
+ await game.startBattle();
+
+ game.move.select(Moves.SPORE, 0, BattlerIndex.ENEMY_2);
+ game.move.select(Moves.NUZZLE, 1, BattlerIndex.ENEMY_2);
+
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2]);
+
+ await game.phaseInterceptor.to("BerryPhase");
+
+ const enemyPokemon = game.scene.getEnemyField();
+
+ expect(enemyPokemon[0].status).toBeUndefined();
+ expect(enemyPokemon[1].status).toBeUndefined();
+ }, TIMEOUT);
+
+ it("protects from Yawn", async () => {
+ await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.YAWN);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.summonData.tags).toEqual([]);
+ }, TIMEOUT);
+
+ it("doesn't protect from already existing Yawn", async () => {
+ await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.YAWN);
+ await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
+ await game.toNextTurn();
+
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP);
+ }, TIMEOUT);
+
+ it("doesn't protect from self-inflicted via Rest or Flame Orb", async () => {
+ game.override.enemyHeldItems([{name: "FLAME_ORB"}]);
+ await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.SPLASH);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.status?.effect).toEqual(StatusEffect.BURN);
+
+ game.override.enemyMoveset(Array(4).fill(Moves.REST));
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.status?.effect).toEqual(StatusEffect.SLEEP);
+ }, TIMEOUT);
+
+ it("protects from ability-inflicted status", async () => {
+ game.override.ability(Abilities.STATIC);
+ vi.spyOn(allAbilities[Abilities.STATIC].getAttrs(PostDefendContactApplyStatusEffectAbAttr)[0], "chance", "get").mockReturnValue(100);
+ await game.startBattle();
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+
+ game.move.select(Moves.SPLASH);
+ await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
+ await game.toNextTurn();
+ game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
+ game.move.select(Moves.SPLASH);
+ await game.toNextTurn();
+
+ expect(enemyPokemon.status).toBeUndefined();
+ }, TIMEOUT);
+});
diff --git a/src/test/moves/speed_swap.test.ts b/src/test/moves/speed_swap.test.ts
new file mode 100644
index 00000000000..131d506792b
--- /dev/null
+++ b/src/test/moves/speed_swap.test.ts
@@ -0,0 +1,54 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+describe("Moves - Speed Swap", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemyAbility(Abilities.NONE)
+ .enemyMoveset(SPLASH_ONLY)
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .moveset([ Moves.SPEED_SWAP ])
+ .ability(Abilities.NONE);
+ });
+
+ it("should swap the user's SPD and the target's SPD stats", async () => {
+ await game.startBattle([
+ Species.INDEEDEE
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const playerSpd = player.getStat(Stat.SPD, false);
+ const enemySpd = enemy.getStat(Stat.SPD, false);
+
+ game.move.select(Moves.SPEED_SWAP);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.SPD, false)).toBe(enemySpd);
+ expect(enemy.getStat(Stat.SPD, false)).toBe(playerSpd);
+ }, 20000);
+});
diff --git a/src/test/moves/spikes.test.ts b/src/test/moves/spikes.test.ts
index c4096111c6f..fa2e7521152 100644
--- a/src/test/moves/spikes.test.ts
+++ b/src/test/moves/spikes.test.ts
@@ -1,10 +1,10 @@
-import { CommandPhase } from "#app/phases/command-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
describe("Moves - Spikes", () => {
@@ -23,93 +23,61 @@ describe("Moves - Spikes", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
- game.scene.battleStyle = 1;
- game.override.battleType("single");
- game.override.enemySpecies(Species.RATTATA);
- game.override.enemyAbility(Abilities.HYDRATION);
- game.override.enemyPassiveAbility(Abilities.HYDRATION);
- game.override.ability(Abilities.HYDRATION);
- game.override.passiveAbility(Abilities.HYDRATION);
- game.override.startingWave(3);
- game.override.enemyMoveset([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
- game.override.moveset([Moves.SPIKES, Moves.SPLASH, Moves.ROAR]);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MAGIKARP)
+ .enemyAbility(Abilities.BALL_FETCH)
+ .ability(Abilities.BALL_FETCH)
+ .enemyMoveset(SPLASH_ONLY)
+ .moveset([Moves.SPIKES, Moves.SPLASH, Moves.ROAR]);
});
- it("single - wild - stay on field - no damage", async () => {
- await game.classicMode.runToSummon([
- Species.MIGHTYENA,
- Species.POOCHYENA,
- ]);
- await game.phaseInterceptor.to(CommandPhase, true);
- const initialHp = game.scene.getParty()[0].hp;
- expect(game.scene.getParty()[0].hp).toBe(initialHp);
+ it("should not damage the team that set them", async () => {
+ await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
+
game.move.select(Moves.SPIKES);
await game.toNextTurn();
+
game.move.select(Moves.SPLASH);
await game.toNextTurn();
- expect(game.scene.getParty()[0].hp).toBe(initialHp);
- }, 20000);
-
- it("single - wild - take some damage", async () => {
- // player set spikes on the field and switch back to back
- // opponent do splash for 2 turns
- // nobody should take damage
- await game.classicMode.runToSummon([
- Species.MIGHTYENA,
- Species.POOCHYENA,
- ]);
- await game.phaseInterceptor.to(CommandPhase, false);
-
- const initialHp = game.scene.getParty()[0].hp;
- game.doSwitchPokemon(1);
- await game.phaseInterceptor.run(CommandPhase);
- await game.phaseInterceptor.to(CommandPhase, false);
game.doSwitchPokemon(1);
- await game.phaseInterceptor.run(CommandPhase);
- await game.phaseInterceptor.to(CommandPhase, false);
+ await game.toNextTurn();
- expect(game.scene.getParty()[0].hp).toBe(initialHp);
+ game.doSwitchPokemon(1);
+ await game.toNextTurn();
+
+ const player = game.scene.getParty()[0];
+ expect(player.hp).toBe(player.getMaxHp());
}, 20000);
- it("trainer - wild - force switch opponent - should take damage", async () => {
+ it("should damage opposing pokemon that are forced to switch in", async () => {
game.override.startingWave(5);
- // player set spikes on the field and do splash for 3 turns
- // opponent do splash for 4 turns
- // nobody should take damage
- await game.classicMode.runToSummon([
- Species.MIGHTYENA,
- Species.POOCHYENA,
- ]);
- await game.phaseInterceptor.to(CommandPhase, true);
- const initialHpOpponent = game.scene.currentBattle.enemyParty[1].hp;
+ await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
+
game.move.select(Moves.SPIKES);
await game.toNextTurn();
+
game.move.select(Moves.ROAR);
await game.toNextTurn();
- expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(initialHpOpponent);
+
+ const enemy = game.scene.getEnemyParty()[0];
+ expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
}, 20000);
- it("trainer - wild - force switch by himself opponent - should take damage", async () => {
+ it("should damage opposing pokemon that choose to switch in", async () => {
game.override.startingWave(5);
- game.override.startingLevel(5000);
- game.override.enemySpecies(0);
- // turn 1: player set spikes, opponent do splash
- // turn 2: player do splash, opponent switch pokemon
- // opponent pokemon should trigger spikes and lose HP
- await game.classicMode.runToSummon([
- Species.MIGHTYENA,
- Species.POOCHYENA,
- ]);
- await game.phaseInterceptor.to(CommandPhase, true);
- const initialHpOpponent = game.scene.currentBattle.enemyParty[1].hp;
+ await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
+
game.move.select(Moves.SPIKES);
await game.toNextTurn();
- game.forceOpponentToSwitch();
game.move.select(Moves.SPLASH);
+ game.forceEnemyToSwitch();
await game.toNextTurn();
- expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(initialHpOpponent);
+
+ const enemy = game.scene.getEnemyParty()[0];
+ expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
}, 20000);
});
diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts
index ab47e65d653..f88791efb74 100644
--- a/src/test/moves/spit_up.test.ts
+++ b/src/test/moves/spit_up.test.ts
@@ -1,22 +1,24 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { StockpilingTag } from "#app/data/battler-tags";
import { allMoves } from "#app/data/move";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { MoveResult, TurnMove } from "#app/field/pokemon";
-import { MovePhase } from "#app/phases/move-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
-import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+import { SPLASH_ONLY } from "#test/utils/testUtils";
+import { MovePhase } from "#app/phases/move-phase";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
describe("Moves - Spit Up", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
+ const spitUp = allMoves[Moves.SPIT_UP];
+
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
@@ -35,8 +37,10 @@ describe("Moves - Spit Up", () => {
game.override.enemyAbility(Abilities.NONE);
game.override.enemyLevel(2000);
- game.override.moveset([Moves.SPIT_UP, Moves.SPIT_UP, Moves.SPIT_UP, Moves.SPIT_UP]);
+ game.override.moveset(new Array(4).fill(spitUp.id));
game.override.ability(Abilities.NONE);
+
+ vi.spyOn(spitUp, "calculateBattlePower");
});
describe("consumes all stockpile stacks to deal damage (scaling with stacks)", () => {
@@ -53,13 +57,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
- vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
-
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower);
+ expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
+ expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@@ -78,13 +80,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
- vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
-
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower);
+ expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
+ expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@@ -104,13 +104,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
- vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
-
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower);
+ expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
+ expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@@ -124,14 +122,12 @@ describe("Moves - Spit Up", () => {
const stockpilingTag = pokemon.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeUndefined();
- vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
-
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.FAIL });
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).not.toHaveBeenCalled();
+ expect(spitUp.calculateBattlePower).not.toHaveBeenCalled();
});
describe("restores stat boosts granted by stacks", () => {
@@ -144,22 +140,20 @@ describe("Moves - Spit Up", () => {
const stockpilingTag = pokemon.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined();
- vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
-
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(MovePhase);
- expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
- expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1);
+ expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
+ expect(pokemon.getStatStage(Stat.SPDEF)).toBe(1);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
+ expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
- expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
+ expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
+ expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@@ -175,26 +169,19 @@ describe("Moves - Spit Up", () => {
// for the sake of simplicity (and because other tests cover the setup), set boost amounts directly
stockpilingTag.statChangeCounts = {
- [BattleStat.DEF]: -1,
- [BattleStat.SPDEF]: 2,
+ [Stat.DEF]: -1,
+ [Stat.SPDEF]: 2,
};
- expect(stockpilingTag.statChangeCounts).toMatchObject({
- [BattleStat.DEF]: -1,
- [BattleStat.SPDEF]: 2,
- });
-
- vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
-
game.move.select(Moves.SPIT_UP);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
- expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
+ expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
- expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
- expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
+ expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
+ expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
diff --git a/src/test/moves/spotlight.test.ts b/src/test/moves/spotlight.test.ts
index e5f4719d1d3..aef44369642 100644
--- a/src/test/moves/spotlight.test.ts
+++ b/src/test/moves/spotlight.test.ts
@@ -1,5 +1,4 @@
import { BattlerIndex } from "#app/battle";
-import { Stat } from "#app/data/pokemon-stat";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@@ -31,52 +30,46 @@ describe("Moves - Spotlight", () => {
game.override.startingLevel(100);
game.override.enemyLevel(100);
game.override.moveset([Moves.FOLLOW_ME, Moves.RAGE_POWDER, Moves.SPOTLIGHT, Moves.QUICK_ATTACK]);
- game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
+ game.override.enemyMoveset([Moves.FOLLOW_ME, Moves.SPLASH]);
});
test(
"move should redirect attacks to the target",
async () => {
- await game.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
const enemyPokemon = game.scene.getEnemyField();
- const enemyStartingHp = enemyPokemon.map(p => p.hp);
-
game.move.select(Moves.SPOTLIGHT, 0, BattlerIndex.ENEMY);
game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2);
+
+ await game.forceEnemyMove(Moves.SPLASH);
+ await game.forceEnemyMove(Moves.SPLASH);
+
await game.phaseInterceptor.to(TurnEndPhase, false);
- expect(enemyPokemon[0].hp).toBeLessThan(enemyStartingHp[0]);
- expect(enemyPokemon[1].hp).toBe(enemyStartingHp[1]);
+ expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp());
+ expect(enemyPokemon[1].hp).toBe(enemyPokemon[1].getMaxHp());
}, TIMEOUT
);
test(
"move should cause other redirection moves to fail",
async () => {
- game.override.enemyMoveset([Moves.FOLLOW_ME, Moves.FOLLOW_ME, Moves.FOLLOW_ME, Moves.FOLLOW_ME]);
-
- await game.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
+ await game.classicMode.startBattle([Species.AMOONGUSS, Species.CHARIZARD]);
const enemyPokemon = game.scene.getEnemyField();
- /**
- * Spotlight will target the slower enemy. In this situation without Spotlight being used,
- * the faster enemy would normally end up with the Center of Attention tag.
- */
- enemyPokemon.sort((a, b) => b.getBattleStat(Stat.SPD) - a.getBattleStat(Stat.SPD));
- const spotTarget = enemyPokemon[1].getBattlerIndex();
- const attackTarget = enemyPokemon[0].getBattlerIndex();
+ game.move.select(Moves.SPOTLIGHT, 0, BattlerIndex.ENEMY);
+ game.move.select(Moves.QUICK_ATTACK, 1, BattlerIndex.ENEMY_2);
- const enemyStartingHp = enemyPokemon.map(p => p.hp);
+ await game.forceEnemyMove(Moves.SPLASH);
+ await game.forceEnemyMove(Moves.FOLLOW_ME);
- game.move.select(Moves.SPOTLIGHT, 0, spotTarget);
- game.move.select(Moves.QUICK_ATTACK, 1, attackTarget);
- await game.phaseInterceptor.to(TurnEndPhase, false);
+ await game.phaseInterceptor.to("BerryPhase", false);
- expect(enemyPokemon[1].hp).toBeLessThan(enemyStartingHp[1]);
- expect(enemyPokemon[0].hp).toBe(enemyStartingHp[0]);
+ expect(enemyPokemon[0].hp).toBeLessThan(enemyPokemon[0].getMaxHp());
+ expect(enemyPokemon[1].hp).toBe(enemyPokemon[1].getMaxHp());
}, TIMEOUT
);
});
diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts
index b1941b9f9b3..d57768d0ffd 100644
--- a/src/test/moves/stockpile.test.ts
+++ b/src/test/moves/stockpile.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { StockpilingTag } from "#app/data/battler-tags";
import { MoveResult, TurnMove } from "#app/field/pokemon";
import { CommandPhase } from "#app/phases/command-phase";
@@ -38,7 +38,7 @@ describe("Moves - Stockpile", () => {
game.override.ability(Abilities.NONE);
});
- it("Gains a stockpile stack and increases DEF and SPDEF by 1 on each use, fails at max stacks (3)", { timeout: 10000 }, async () => {
+ it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
const user = game.scene.getPlayerPokemon()!;
@@ -47,8 +47,8 @@ describe("Moves - Stockpile", () => {
// we just have to know that they're implemented as a BattlerTag.
expect(user.getTag(StockpilingTag)).toBeUndefined();
- expect(user.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
+ expect(user.getStatStage(Stat.DEF)).toBe(0);
+ expect(user.getStatStage(Stat.SPDEF)).toBe(0);
// use Stockpile four times
for (let i = 0; i < 4; i++) {
@@ -60,18 +60,16 @@ describe("Moves - Stockpile", () => {
await game.phaseInterceptor.to(TurnInitPhase);
const stockpilingTag = user.getTag(StockpilingTag)!;
- const def = user.summonData.battleStats[BattleStat.DEF];
- const spdef = user.summonData.battleStats[BattleStat.SPDEF];
if (i < 3) { // first three uses should behave normally
- expect(def).toBe(i + 1);
- expect(spdef).toBe(i + 1);
+ expect(user.getStatStage(Stat.DEF)).toBe(i + 1);
+ expect(user.getStatStage(Stat.SPDEF)).toBe(i + 1);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(i + 1);
} else { // fourth should have failed
- expect(def).toBe(3);
- expect(spdef).toBe(3);
+ expect(user.getStatStage(Stat.DEF)).toBe(3);
+ expect(user.getStatStage(Stat.SPDEF)).toBe(3);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(3);
expect(user.getMoveHistory().at(-1)).toMatchObject({ result: MoveResult.FAIL, move: Moves.STOCKPILE });
@@ -79,17 +77,17 @@ describe("Moves - Stockpile", () => {
}
});
- it("Gains a stockpile stack even if DEF and SPDEF are at +6", { timeout: 10000 }, async () => {
+ it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
const user = game.scene.getPlayerPokemon()!;
- user.summonData.battleStats[BattleStat.DEF] = 6;
- user.summonData.battleStats[BattleStat.SPDEF] = 6;
+ user.setStatStage(Stat.DEF, 6);
+ user.setStatStage(Stat.SPDEF, 6);
expect(user.getTag(StockpilingTag)).toBeUndefined();
- expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6);
- expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
+ expect(user.getStatStage(Stat.DEF)).toBe(6);
+ expect(user.getStatStage(Stat.SPDEF)).toBe(6);
game.move.select(Moves.STOCKPILE);
await game.phaseInterceptor.to(TurnInitPhase);
@@ -97,8 +95,8 @@ describe("Moves - Stockpile", () => {
const stockpilingTag = user.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(1);
- expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6);
- expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
+ expect(user.getStatStage(Stat.DEF)).toBe(6);
+ expect(user.getStatStage(Stat.SPDEF)).toBe(6);
// do it again, just for good measure
await game.phaseInterceptor.to(CommandPhase);
@@ -109,8 +107,8 @@ describe("Moves - Stockpile", () => {
const stockpilingTagAgain = user.getTag(StockpilingTag)!;
expect(stockpilingTagAgain).toBeDefined();
expect(stockpilingTagAgain.stockpiledCount).toBe(2);
- expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6);
- expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
+ expect(user.getStatStage(Stat.DEF)).toBe(6);
+ expect(user.getStatStage(Stat.SPDEF)).toBe(6);
});
});
});
diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts
index 202f25fee74..9cea7ae8dc9 100644
--- a/src/test/moves/swallow.test.ts
+++ b/src/test/moves/swallow.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { StockpilingTag } from "#app/data/battler-tags";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { MoveResult, TurnMove } from "#app/field/pokemon";
@@ -138,7 +138,7 @@ describe("Moves - Swallow", () => {
expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.FAIL });
});
- describe("restores stat boosts granted by stacks", () => {
+ describe("restores stat stage boosts granted by stacks", () => {
it("decreases stats based on stored values (both boosts equal)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
@@ -151,20 +151,20 @@ describe("Moves - Swallow", () => {
game.move.select(Moves.SWALLOW);
await game.phaseInterceptor.to(MovePhase);
- expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
- expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1);
+ expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
+ expect(pokemon.getStatStage(Stat.SPDEF)).toBe(1);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
- expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(0);
- expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
+ expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
+ expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
- it("decreases stats based on stored values (different boosts)", { timeout: 10000 }, async () => {
+ it("lower stat stages based on stored values (different boosts)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
const pokemon = game.scene.getPlayerPokemon()!;
@@ -175,22 +175,18 @@ describe("Moves - Swallow", () => {
// for the sake of simplicity (and because other tests cover the setup), set boost amounts directly
stockpilingTag.statChangeCounts = {
- [BattleStat.DEF]: -1,
- [BattleStat.SPDEF]: 2,
+ [Stat.DEF]: -1,
+ [Stat.SPDEF]: 2,
};
- expect(stockpilingTag.statChangeCounts).toMatchObject({
- [BattleStat.DEF]: -1,
- [BattleStat.SPDEF]: 2,
- });
-
game.move.select(Moves.SWALLOW);
+
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
- expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
- expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
+ expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
+ expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
diff --git a/src/test/moves/tackle.test.ts b/src/test/moves/tackle.test.ts
index 5eca9e344c8..b25c7524a1a 100644
--- a/src/test/moves/tackle.test.ts
+++ b/src/test/moves/tackle.test.ts
@@ -1,4 +1,4 @@
-import { Stat } from "#app/data/pokemon-stat";
+import { Stat } from "#enums/stat";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
diff --git a/src/test/moves/tail_whip.test.ts b/src/test/moves/tail_whip.test.ts
index 0a999fe1920..04730a04f7a 100644
--- a/src/test/moves/tail_whip.test.ts
+++ b/src/test/moves/tail_whip.test.ts
@@ -1,12 +1,13 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
-import { TurnInitPhase } from "#app/phases/turn-init-phase";
+import { Stat } from "#enums/stat";
+import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
-import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "../utils/testUtils";
+import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
describe("Moves - Tail whip", () => {
@@ -31,23 +32,23 @@ describe("Moves - Tail whip", () => {
game.override.enemyAbility(Abilities.INSOMNIA);
game.override.ability(Abilities.INSOMNIA);
game.override.startingLevel(2000);
- game.override.moveset([moveToUse]);
- game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
+ game.override.moveset([ moveToUse ]);
+ game.override.enemyMoveset(SPLASH_ONLY);
});
- it("TAIL_WHIP", async () => {
+ it("should lower DEF stat stage by 1", async() => {
const moveToUse = Moves.TAIL_WHIP;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
- let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.DEF]).toBe(0);
+ const enemyPokemon = game.scene.getEnemyPokemon()!;
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0);
game.move.select(moveToUse);
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
- battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
- expect(battleStatsOpponent[BattleStat.DEF]).toBe(-1);
+
+ expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1);
}, 20000);
});
diff --git a/src/test/moves/tailwind.test.ts b/src/test/moves/tailwind.test.ts
index 6b70122d08d..d158a9cce86 100644
--- a/src/test/moves/tailwind.test.ts
+++ b/src/test/moves/tailwind.test.ts
@@ -1,5 +1,5 @@
+import { Stat } from "#enums/stat";
import { ArenaTagSide } from "#app/data/arena-tag";
-import { Stat } from "#app/data/pokemon-stat";
import { ArenaTagType } from "#app/enums/arena-tag-type";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Moves } from "#enums/moves";
@@ -38,16 +38,16 @@ describe("Moves - Tailwind", () => {
const magikarpSpd = magikarp.getStat(Stat.SPD);
const meowthSpd = meowth.getStat(Stat.SPD);
- expect(magikarp.getBattleStat(Stat.SPD)).equal(magikarpSpd);
- expect(meowth.getBattleStat(Stat.SPD)).equal(meowthSpd);
+ expect(magikarp.getEffectiveStat(Stat.SPD)).equal(magikarpSpd);
+ expect(meowth.getEffectiveStat(Stat.SPD)).equal(meowthSpd);
game.move.select(Moves.TAILWIND);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(magikarp.getBattleStat(Stat.SPD)).toBe(magikarpSpd * 2);
- expect(meowth.getBattleStat(Stat.SPD)).toBe(meowthSpd * 2);
+ expect(magikarp.getEffectiveStat(Stat.SPD)).toBe(magikarpSpd * 2);
+ expect(meowth.getEffectiveStat(Stat.SPD)).toBe(meowthSpd * 2);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeDefined();
});
@@ -86,8 +86,8 @@ describe("Moves - Tailwind", () => {
const enemySpd = enemy.getStat(Stat.SPD);
- expect(ally.getBattleStat(Stat.SPD)).equal(allySpd);
- expect(enemy.getBattleStat(Stat.SPD)).equal(enemySpd);
+ expect(ally.getEffectiveStat(Stat.SPD)).equal(allySpd);
+ expect(enemy.getEffectiveStat(Stat.SPD)).equal(enemySpd);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeUndefined();
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined();
@@ -95,8 +95,8 @@ describe("Moves - Tailwind", () => {
await game.phaseInterceptor.to(TurnEndPhase);
- expect(ally.getBattleStat(Stat.SPD)).toBe(allySpd * 2);
- expect(enemy.getBattleStat(Stat.SPD)).equal(enemySpd);
+ expect(ally.getEffectiveStat(Stat.SPD)).toBe(allySpd * 2);
+ expect(enemy.getEffectiveStat(Stat.SPD)).equal(enemySpd);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeDefined();
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined();
});
diff --git a/src/test/moves/tera_blast.test.ts b/src/test/moves/tera_blast.test.ts
index bd7df8403d1..fa7a99adc14 100644
--- a/src/test/moves/tera_blast.test.ts
+++ b/src/test/moves/tera_blast.test.ts
@@ -1,9 +1,8 @@
import { BattlerIndex } from "#app/battle";
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { allMoves } from "#app/data/move";
import { Type } from "#app/data/type";
import { Abilities } from "#app/enums/abilities";
-import { Stat } from "#app/enums/stat";
import { HitResult } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@@ -112,7 +111,7 @@ describe("Moves - Tera Blast", () => {
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("MoveEndPhase");
- expect(playerPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(-1);
- expect(playerPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
});
diff --git a/src/test/moves/tidy_up.test.ts b/src/test/moves/tidy_up.test.ts
index 1ef7933c114..5204b06106b 100644
--- a/src/test/moves/tidy_up.test.ts
+++ b/src/test/moves/tidy_up.test.ts
@@ -1,4 +1,4 @@
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import { ArenaTagType } from "#app/enums/arena-tag-type";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
@@ -60,7 +60,6 @@ describe("Moves - Tidy Up", () => {
game.move.select(Moves.TIDY_UP);
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.STEALTH_ROCK)).toBeUndefined();
-
}, 20000);
it("toxic spikes are cleared", async () => {
@@ -73,7 +72,6 @@ describe("Moves - Tidy Up", () => {
game.move.select(Moves.TIDY_UP);
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.TOXIC_SPIKES)).toBeUndefined();
-
}, 20000);
it("sticky webs are cleared", async () => {
@@ -87,7 +85,6 @@ describe("Moves - Tidy Up", () => {
game.move.select(Moves.TIDY_UP);
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.STICKY_WEB)).toBeUndefined();
-
}, 20000);
it.skip("substitutes are cleared", async () => {
@@ -101,22 +98,20 @@ describe("Moves - Tidy Up", () => {
game.move.select(Moves.TIDY_UP);
await game.phaseInterceptor.to(MoveEndPhase);
// TODO: check for subs here once the move is implemented
-
}, 20000);
it("user's stats are raised with no traps set", async () => {
await game.startBattle();
- const player = game.scene.getPlayerPokemon()!.summonData.battleStats;
- expect(player[BattleStat.ATK]).toBe(0);
- expect(player[BattleStat.SPD]).toBe(0);
+ const playerPokemon = game.scene.getPlayerPokemon()!;
+
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
+ expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.move.select(Moves.TIDY_UP);
await game.phaseInterceptor.to(TurnEndPhase);
- expect(player[BattleStat.ATK]).toBe(+1);
- expect(player[BattleStat.SPD]).toBe(+1);
-
+ expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
+ expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
}, 20000);
-
});
diff --git a/src/test/moves/transform.test.ts b/src/test/moves/transform.test.ts
new file mode 100644
index 00000000000..45769447e4d
--- /dev/null
+++ b/src/test/moves/transform.test.ts
@@ -0,0 +1,101 @@
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import Phaser from "phaser";
+import GameManager from "#app/test/utils/gameManager";
+import { Species } from "#enums/species";
+import { TurnEndPhase } from "#app/phases/turn-end-phase";
+import { Moves } from "#enums/moves";
+import { Stat, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
+import { Abilities } from "#enums/abilities";
+import { SPLASH_ONLY } from "../utils/testUtils";
+
+// TODO: Add more tests once Transform is fully implemented
+describe("Moves - Transform", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ game.override
+ .battleType("single")
+ .enemySpecies(Species.MEW)
+ .enemyLevel(200)
+ .enemyAbility(Abilities.BEAST_BOOST)
+ .enemyPassiveAbility(Abilities.BALL_FETCH)
+ .enemyMoveset(SPLASH_ONLY)
+ .ability(Abilities.INTIMIDATE)
+ .moveset([ Moves.TRANSFORM ]);
+ });
+
+ it("should copy species, ability, gender, all stats except HP, all stat stages, moveset, and types of target", async () => {
+ await game.startBattle([
+ Species.DITTO
+ ]);
+
+ game.move.select(Moves.TRANSFORM);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ expect(player.getSpeciesForm().speciesId).toBe(enemy.getSpeciesForm().speciesId);
+ expect(player.getAbility()).toBe(enemy.getAbility());
+ expect(player.getGender()).toBe(enemy.getGender());
+
+ expect(player.getStat(Stat.HP, false)).not.toBe(enemy.getStat(Stat.HP));
+ for (const s of EFFECTIVE_STATS) {
+ expect(player.getStat(s, false)).toBe(enemy.getStat(s, false));
+ }
+
+ for (const s of BATTLE_STATS) {
+ expect(player.getStatStage(s)).toBe(enemy.getStatStage(s));
+ }
+
+ const playerMoveset = player.getMoveset();
+ const enemyMoveset = player.getMoveset();
+
+ for (let i = 0; i < playerMoveset.length && i < enemyMoveset.length; i++) {
+ // TODO: Checks for 5 PP should be done here when that gets addressed
+ expect(playerMoveset[i]?.moveId).toBe(enemyMoveset[i]?.moveId);
+ }
+
+ const playerTypes = player.getTypes();
+ const enemyTypes = enemy.getTypes();
+
+ for (let i = 0; i < playerTypes.length && i < enemyTypes.length; i++) {
+ expect(playerTypes[i]).toBe(enemyTypes[i]);
+ }
+ }, 20000);
+
+ it("should copy in-battle overridden stats", async () => {
+ game.override.enemyMoveset(new Array(4).fill(Moves.POWER_SPLIT));
+
+ await game.startBattle([
+ Species.DITTO
+ ]);
+
+ const player = game.scene.getPlayerPokemon()!;
+ const enemy = game.scene.getEnemyPokemon()!;
+
+ const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
+ const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
+
+ game.move.select(Moves.TRANSFORM);
+ await game.phaseInterceptor.to(TurnEndPhase);
+
+ expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
+ expect(enemy.getStat(Stat.ATK, false)).toBe(avgAtk);
+
+ expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk);
+ });
+});
diff --git a/src/test/moves/wide_guard.test.ts b/src/test/moves/wide_guard.test.ts
index 616972de01b..6feeff815b5 100644
--- a/src/test/moves/wide_guard.test.ts
+++ b/src/test/moves/wide_guard.test.ts
@@ -1,12 +1,12 @@
-import { BattleStat } from "#app/data/battle-stat";
-import { BerryPhase } from "#app/phases/berry-phase";
-import { CommandPhase } from "#app/phases/command-phase";
-import { Abilities } from "#enums/abilities";
-import { Moves } from "#enums/moves";
-import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import GameManager from "../utils/gameManager";
+import { Species } from "#enums/species";
+import { Abilities } from "#enums/abilities";
+import { Moves } from "#enums/moves";
+import { Stat } from "#enums/stat";
+import { BerryPhase } from "#app/phases/berry-phase";
+import { CommandPhase } from "#app/phases/command-phase";
const TIMEOUT = 20 * 1000;
@@ -75,7 +75,7 @@ describe("Moves - Wide Guard", () => {
await game.phaseInterceptor.to(BerryPhase, false);
- leadPokemon.forEach(p => expect(p.summonData.battleStats[BattleStat.ATK]).toBe(0));
+ leadPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
}, TIMEOUT
);
diff --git a/src/test/reload.test.ts b/src/test/reload.test.ts
new file mode 100644
index 00000000000..0a712fcc7df
--- /dev/null
+++ b/src/test/reload.test.ts
@@ -0,0 +1,137 @@
+import { Species } from "#app/enums/species";
+import { GameModes } from "#app/game-mode";
+import GameManager from "#test/utils/gameManager";
+import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
+import { SPLASH_ONLY } from "./utils/testUtils";
+import { Moves } from "#app/enums/moves";
+import { Biome } from "#app/enums/biome";
+
+describe("Reload", () => {
+ let phaserGame: Phaser.Game;
+ let game: GameManager;
+
+ beforeAll(() => {
+ phaserGame = new Phaser.Game({
+ type: Phaser.HEADLESS,
+ });
+ });
+
+ afterEach(() => {
+ game.phaseInterceptor.restoreOg();
+ });
+
+ beforeEach(() => {
+ game = new GameManager(phaserGame);
+ });
+
+ it("should not have RNG inconsistencies in a Classic run", async () => {
+ await game.classicMode.startBattle();
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+
+ it("should not have RNG inconsistencies after a biome switch", async () => {
+ game.override
+ .startingWave(10)
+ .startingBiome(Biome.CAVE) // Will lead to biomes with randomly generated weather
+ .battleType("single")
+ .startingLevel(100)
+ .enemyLevel(1000)
+ .disableTrainerWaves()
+ .moveset([Moves.KOWTOW_CLEAVE])
+ .enemyMoveset(SPLASH_ONLY);
+ await game.dailyMode.startBattle();
+
+ // Transition from Daily Run Wave 10 to Wave 11 in order to trigger biome switch
+ game.move.select(Moves.KOWTOW_CLEAVE);
+ await game.phaseInterceptor.to("DamagePhase");
+ await game.doKillOpponents();
+ await game.toNextWave();
+ expect(game.phaseInterceptor.log).toContain("NewBiomeEncounterPhase");
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+
+ it("should not have RNG inconsistencies at a Daily run wild Pokemon fight", async () => {
+ await game.dailyMode.startBattle();
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+
+ it("should not have RNG inconsistencies at a Daily run double battle", async () => {
+ game.override
+ .battleType("double");
+ await game.dailyMode.startBattle();
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+
+ it("should not have RNG inconsistencies at a Daily run Gym Leader fight", async () => {
+ game.override
+ .battleType("single")
+ .startingWave(40);
+ await game.dailyMode.startBattle();
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+
+ it("should not have RNG inconsistencies at a Daily run regular trainer fight", async () => {
+ game.override
+ .battleType("single")
+ .startingWave(45);
+ await game.dailyMode.startBattle();
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+
+ it("should not have RNG inconsistencies at a Daily run wave 50 Boss fight", async () => {
+ game.override
+ .battleType("single")
+ .startingWave(50);
+ await game.runToFinalBossEncounter([Species.BULBASAUR], GameModes.DAILY);
+
+ const preReloadRngState = Phaser.Math.RND.state();
+
+ await game.reload.reloadSession();
+
+ const postReloadRngState = Phaser.Math.RND.state();
+
+ expect(preReloadRngState).toBe(postReloadRngState);
+ }, 20000);
+});
diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts
index 8ef1ea16b4a..6d26ebfd6b3 100644
--- a/src/test/ui/starter-select.test.ts
+++ b/src/test/ui/starter-select.test.ts
@@ -53,9 +53,6 @@ describe("UI - Starter select", () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
- handler.processInput(Button.CYCLE_SHINY);
- handler.processInput(Button.V);
- handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
@@ -117,9 +114,6 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
handler.processInput(Button.CYCLE_GENDER);
- handler.processInput(Button.CYCLE_SHINY);
- handler.processInput(Button.V);
- handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
@@ -184,9 +178,6 @@ describe("UI - Starter select", () => {
handler.processInput(Button.CYCLE_GENDER);
handler.processInput(Button.CYCLE_NATURE);
handler.processInput(Button.CYCLE_ABILITY);
- handler.processInput(Button.CYCLE_SHINY);
- handler.processInput(Button.V);
- handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
@@ -227,11 +218,12 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
expect(game.scene.getParty()[0].variant).toBe(2);
+ expect(game.scene.getParty()[0].gender).toBe(Gender.FEMALE);
expect(game.scene.getParty()[0].nature).toBe(Nature.LONELY);
expect(game.scene.getParty()[0].getAbility().id).toBe(Abilities.CHLOROPHYLL);
}, 20000);
- it("Bulbasaur - shiny - variant 2 female lonely chlorophyl", async() => {
+ it("Bulbasaur - shiny - variant 2 female", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
@@ -249,9 +241,6 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
handler.processInput(Button.CYCLE_GENDER);
- handler.processInput(Button.CYCLE_SHINY);
- handler.processInput(Button.V);
- handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
});
@@ -313,6 +302,7 @@ describe("UI - Starter select", () => {
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
handler.processInput(Button.ACTION);
+ handler.processInput(Button.CYCLE_SHINY);
game.phaseInterceptor.unlock();
});
await game.phaseInterceptor.run(SelectStarterPhase);
@@ -371,7 +361,7 @@ describe("UI - Starter select", () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
- handler.processInput(Button.CYCLE_SHINY);
+ handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
@@ -415,7 +405,7 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].variant).toBe(1);
}, 20000);
- it("Bulbasaur - shiny - variant 2", async() => {
+ it("Bulbasaur - shiny - variant 0", async() => {
await game.importData("src/test/utils/saves/everything.prsv");
const caughtCount = Object.keys(game.scene.gameData.dexData).filter((key) => {
const species = game.scene.gameData.dexData[key];
@@ -432,8 +422,6 @@ describe("UI - Starter select", () => {
const handler = game.scene.ui.getHandler() as StarterSelectUiHandler;
handler.processInput(Button.RIGHT);
handler.processInput(Button.LEFT);
- handler.processInput(Button.CYCLE_SHINY);
- handler.processInput(Button.V);
handler.processInput(Button.V);
handler.processInput(Button.ACTION);
game.phaseInterceptor.unlock();
@@ -474,7 +462,7 @@ describe("UI - Starter select", () => {
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.BULBASAUR);
expect(game.scene.getParty()[0].shiny).toBe(true);
- expect(game.scene.getParty()[0].variant).toBe(2);
+ expect(game.scene.getParty()[0].variant).toBe(0);
}, 20000);
it("Check if first pokemon in party is caterpie from gen 1 and 1rd row, 3rd column", async() => {
diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts
index 60d07065090..f367fc70936 100644
--- a/src/test/utils/gameManager.ts
+++ b/src/test/utils/gameManager.ts
@@ -2,12 +2,16 @@ import { updateUserInfo } from "#app/account";
import { BattlerIndex } from "#app/battle";
import BattleScene from "#app/battle-scene";
import { BattleStyle } from "#app/enums/battle-style";
+import { Moves } from "#app/enums/moves";
+import { getMoveTargets } from "#app/data/move";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import Trainer from "#app/field/trainer";
import { GameModes, getGameMode } from "#app/game-mode";
import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type";
+import overrides from "#app/overrides";
import { CommandPhase } from "#app/phases/command-phase";
import { EncounterPhase } from "#app/phases/encounter-phase";
+import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { FaintPhase } from "#app/phases/faint-phase";
import { LoginPhase } from "#app/phases/login-phase";
import { MovePhase } from "#app/phases/move-phase";
@@ -44,6 +48,8 @@ import { ChallengeModeHelper } from "./helpers/challengeModeHelper";
import { MoveHelper } from "./helpers/moveHelper";
import { OverridesHelper } from "./helpers/overridesHelper";
import { SettingsHelper } from "./helpers/settingsHelper";
+import { ReloadHelper } from "./helpers/reloadHelper";
+import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
/**
* Class to manage the game state and transitions between phases.
@@ -60,6 +66,7 @@ export default class GameManager {
public readonly dailyMode: DailyModeHelper;
public readonly challengeMode: ChallengeModeHelper;
public readonly settings: SettingsHelper;
+ public readonly reload: ReloadHelper;
/**
* Creates an instance of GameManager.
@@ -81,6 +88,7 @@ export default class GameManager {
this.dailyMode = new DailyModeHelper(this);
this.challengeMode = new ChallengeModeHelper(this);
this.settings = new SettingsHelper(this);
+ this.reload = new ReloadHelper(this);
}
/**
@@ -138,7 +146,7 @@ export default class GameManager {
this.scene.hpBarSpeed = 3;
this.scene.enableTutorials = false;
this.scene.gameData.gender = PlayerGender.MALE; // set initial player gender
-
+ this.scene.battleStyle = this.settings.battleStyle;
}
/**
@@ -148,28 +156,26 @@ export default class GameManager {
* @param species
* @param mode
*/
- async runToFinalBossEncounter(game: GameManager, species: Species[], mode: GameModes) {
+ async runToFinalBossEncounter(species: Species[], mode: GameModes) {
console.log("===to final boss encounter===");
- await game.runToTitle();
+ await this.runToTitle();
- game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
- game.scene.gameMode = getGameMode(mode);
- const starters = generateStarter(game.scene, species);
- const selectStarterPhase = new SelectStarterPhase(game.scene);
- game.scene.pushPhase(new EncounterPhase(game.scene, false));
+ this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
+ this.scene.gameMode = getGameMode(mode);
+ const starters = generateStarter(this.scene, species);
+ const selectStarterPhase = new SelectStarterPhase(this.scene);
+ this.scene.pushPhase(new EncounterPhase(this.scene, false));
selectStarterPhase.initBattle(starters);
});
- game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => {
- // This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler)
- game.setMode(Mode.MESSAGE);
- const encounterPhase = game.scene.getCurrentPhase() as EncounterPhase;
+ // This will consider all battle entry dialog as seens and skip them
+ vi.spyOn(this.scene.ui, "shouldSkipDialogue").mockReturnValue(true);
- // No need to end phase, this will do it for you
- encounterPhase.doEncounterCommon(false);
- });
+ if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) {
+ this.removeEnemyHeldItems();
+ }
- await game.phaseInterceptor.to(EncounterPhase, true);
+ await this.phaseInterceptor.to(EncounterPhase);
console.log("===finished run to final boss encounter===");
}
@@ -232,15 +238,42 @@ export default class GameManager {
this.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
handler.processInput(Button.CANCEL);
- }, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase), true);
+ }, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase) || this.isCurrentPhase(CheckSwitchPhase), true);
this.onNextPrompt("SelectModifierPhase", Mode.CONFIRM, () => {
const handler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
handler.processInput(Button.ACTION);
- }, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase));
+ }, () => this.isCurrentPhase(CommandPhase) || this.isCurrentPhase(NewBattlePhase) || this.isCurrentPhase(CheckSwitchPhase));
}
- forceOpponentToSwitch() {
+ /**
+ * Forces the next enemy selecting a move to use the given move in its moveset against the
+ * given target (if applicable).
+ * @param moveId {@linkcode Moves} the move the enemy will use
+ * @param target {@linkcode BattlerIndex} the target on which the enemy will use the given move
+ */
+ async forceEnemyMove(moveId: Moves, target?: BattlerIndex) {
+ // Wait for the next EnemyCommandPhase to start
+ await this.phaseInterceptor.to(EnemyCommandPhase, false);
+ const enemy = this.scene.getEnemyField()[(this.scene.getCurrentPhase() as EnemyCommandPhase).getFieldIndex()];
+ const legalTargets = getMoveTargets(enemy, moveId);
+
+ vi.spyOn(enemy, "getNextMove").mockReturnValueOnce({
+ move: moveId,
+ targets: (target && !legalTargets.multiple && legalTargets.targets.includes(target))
+ ? [target]
+ : enemy.getNextTargets(moveId)
+ });
+
+ /**
+ * Run the EnemyCommandPhase to completion.
+ * This allows this function to be called consecutively to
+ * force a move for each enemy in a double battle.
+ */
+ await this.phaseInterceptor.to(EnemyCommandPhase);
+ }
+
+ forceEnemyToSwitch() {
const originalMatchupScore = Trainer.prototype.getPartyMemberMatchupScores;
Trainer.prototype.getPartyMemberMatchupScores = () => {
Trainer.prototype.getPartyMemberMatchupScores = originalMatchupScore;
@@ -381,7 +414,7 @@ export default class GameManager {
}
/**
- * Intercepts `TurnStartPhase` and mocks the getOrder's return value {@linkcode TurnStartPhase.getOrder}
+ * Intercepts `TurnStartPhase` and mocks the getSpeedOrder's return value {@linkcode TurnStartPhase.getSpeedOrder}
* Used to modify the turn order.
* @param {BattlerIndex[]} order The turn order to set
* @example
@@ -392,7 +425,7 @@ export default class GameManager {
async setTurnOrder(order: BattlerIndex[]): Promise {
await this.phaseInterceptor.to(TurnStartPhase, false);
- vi.spyOn(this.scene.getCurrentPhase() as TurnStartPhase, "getOrder").mockReturnValue(order);
+ vi.spyOn(this.scene.getCurrentPhase() as TurnStartPhase, "getSpeedOrder").mockReturnValue(order);
}
/**
diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts
index d5eaee003db..cc5f9018325 100644
--- a/src/test/utils/helpers/overridesHelper.ts
+++ b/src/test/utils/helpers/overridesHelper.ts
@@ -281,6 +281,31 @@ export class OverridesHelper extends GameManagerHelper {
return this;
}
+ /**
+ * Override the items rolled at the end of a battle
+ * @param items the items to be rolled
+ * @returns this
+ */
+ itemRewards(items: ModifierOverride[]) {
+ vi.spyOn(Overrides, "ITEM_REWARD_OVERRIDE", "get").mockReturnValue(items);
+ this.log("Item rewards set to:", items);
+ return this;
+ }
+
+ /**
+ * Override the enemy (Pokemon) to have the given amount of health segments
+ * @param healthSegments the number of segments to give
+ * default: 0, the health segments will be handled like in the game based on wave, level and species
+ * 1: the Pokemon will not be a boss
+ * 2+: the Pokemon will be a boss with the given number of health segments
+ * @returns this
+ */
+ enemyHealthSegments(healthSegments: number) {
+ vi.spyOn(Overrides, "OPP_HEALTH_SEGMENTS_OVERRIDE", "get").mockReturnValue(healthSegments);
+ this.log("Enemy Pokemon health segments set to:", healthSegments);
+ return this;
+ }
+
private log(...params: any[]) {
console.log("Overrides:", ...params);
}
diff --git a/src/test/utils/helpers/reloadHelper.ts b/src/test/utils/helpers/reloadHelper.ts
new file mode 100644
index 00000000000..c15347b08c9
--- /dev/null
+++ b/src/test/utils/helpers/reloadHelper.ts
@@ -0,0 +1,53 @@
+import { GameManagerHelper } from "./gameManagerHelper";
+import { TitlePhase } from "#app/phases/title-phase";
+import { Mode } from "#app/ui/ui";
+import { vi } from "vitest";
+import { BattleStyle } from "#app/enums/battle-style";
+import { CommandPhase } from "#app/phases/command-phase";
+import { TurnInitPhase } from "#app/phases/turn-init-phase";
+
+/**
+ * Helper to allow reloading sessions in unit tests.
+ */
+export class ReloadHelper extends GameManagerHelper {
+ /**
+ * Simulate reloading the session from the title screen, until reaching the
+ * beginning of the first turn (equivalent to running `startBattle()`) for
+ * the reloaded session.
+ */
+ async reloadSession() : Promise {
+ const scene = this.game.scene;
+ const sessionData = scene.gameData.getSessionSaveData(scene);
+ const titlePhase = new TitlePhase(scene);
+
+ scene.clearPhaseQueue();
+
+ // Set the last saved session to the desired session data
+ vi.spyOn(scene.gameData, "getSession").mockReturnValue(
+ new Promise((resolve, reject) => {
+ resolve(sessionData);
+ })
+ );
+ scene.unshiftPhase(titlePhase);
+ this.game.endPhase(); // End the currently ongoing battle
+
+ titlePhase.loadSaveSlot(-1); // Load the desired session data
+ this.game.phaseInterceptor.shift(); // Loading the save slot also ended TitlePhase, clean it up
+
+ // Run through prompts for switching Pokemon, copied from classicModeHelper.ts
+ if (this.game.scene.battleStyle === BattleStyle.SWITCH) {
+ this.game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
+ this.game.setMode(Mode.MESSAGE);
+ this.game.endPhase();
+ }, () => this.game.isCurrentPhase(CommandPhase) || this.game.isCurrentPhase(TurnInitPhase));
+
+ this.game.onNextPrompt("CheckSwitchPhase", Mode.CONFIRM, () => {
+ this.game.setMode(Mode.MESSAGE);
+ this.game.endPhase();
+ }, () => this.game.isCurrentPhase(CommandPhase) || this.game.isCurrentPhase(TurnInitPhase));
+ }
+
+ await this.game.phaseInterceptor.to(CommandPhase);
+ console.log("==================[New Turn]==================");
+ }
+}
diff --git a/src/test/utils/helpers/settingsHelper.ts b/src/test/utils/helpers/settingsHelper.ts
index 76ffafdbe10..8fca2a34d47 100644
--- a/src/test/utils/helpers/settingsHelper.ts
+++ b/src/test/utils/helpers/settingsHelper.ts
@@ -1,16 +1,30 @@
import { PlayerGender } from "#app/enums/player-gender";
+import { BattleStyle } from "#app/enums/battle-style";
import { GameManagerHelper } from "./gameManagerHelper";
/**
* Helper to handle settings for tests
*/
export class SettingsHelper extends GameManagerHelper {
+ private _battleStyle: BattleStyle = BattleStyle.SET;
+
+ get battleStyle(): BattleStyle {
+ return this._battleStyle;
+ }
+
+ /**
+ * Change the battle style to Switch or Set mode (tests default to {@linkcode BattleStyle.SET})
+ * @param mode {@linkcode BattleStyle.SWITCH} or {@linkcode BattleStyle.SET}
+ */
+ set battleStyle(mode: BattleStyle.SWITCH | BattleStyle.SET) {
+ this._battleStyle = mode;
+ }
/**
* Disable/Enable type hints settings
* @param enable true to enabled, false to disabled
*/
- typeHints(enable: boolean) {
+ typeHints(enable: boolean): void {
this.game.scene.typeHints = enable;
this.log(`Type Hints ${enable? "enabled" : "disabled"}` );
}
diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts
index de65405abff..1141d0bf0d9 100644
--- a/src/test/utils/phaseInterceptor.ts
+++ b/src/test/utils/phaseInterceptor.ts
@@ -1,4 +1,5 @@
import { Phase } from "#app/phase";
+import ErrorInterceptor from "#app/test/utils/errorInterceptor";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { BerryPhase } from "#app/phases/berry-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
@@ -10,14 +11,15 @@ import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { EvolutionPhase } from "#app/phases/evolution-phase";
import { FaintPhase } from "#app/phases/faint-phase";
+import { LevelCapPhase } from "#app/phases/level-cap-phase";
import { LoginPhase } from "#app/phases/login-phase";
import { MessagePhase } from "#app/phases/message-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { MovePhase } from "#app/phases/move-phase";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
+import { NewBiomeEncounterPhase } from "#app/phases/new-biome-encounter-phase";
import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
-import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { SelectGenderPhase } from "#app/phases/select-gender-phase";
@@ -26,7 +28,7 @@ import { SelectStarterPhase } from "#app/phases/select-starter-phase";
import { SelectTargetPhase } from "#app/phases/select-target-phase";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
-import { StatChangePhase } from "#app/phases/stat-change-phase";
+import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
@@ -37,7 +39,7 @@ import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { TurnStartPhase } from "#app/phases/turn-start-phase";
import { UnavailablePhase } from "#app/phases/unavailable-phase";
import { VictoryPhase } from "#app/phases/victory-phase";
-import ErrorInterceptor from "#app/test/utils/errorInterceptor";
+import { PartyHealPhase } from "#app/phases/party-heal-phase";
import UI, { Mode } from "#app/ui/ui";
export default class PhaseInterceptor {
@@ -62,6 +64,7 @@ export default class PhaseInterceptor {
[TitlePhase, this.startPhase],
[SelectGenderPhase, this.startPhase],
[EncounterPhase, this.startPhase],
+ [NewBiomeEncounterPhase, this.startPhase],
[SelectStarterPhase, this.startPhase],
[PostSummonPhase, this.startPhase],
[SummonPhase, this.startPhase],
@@ -86,7 +89,7 @@ export default class PhaseInterceptor {
[NewBattlePhase, this.startPhase],
[VictoryPhase, this.startPhase],
[MoveEndPhase, this.startPhase],
- [StatChangePhase, this.startPhase],
+ [StatStageChangePhase, this.startPhase],
[ShinySparklePhase, this.startPhase],
[SelectTargetPhase, this.startPhase],
[UnavailablePhase, this.startPhase],
@@ -96,6 +99,7 @@ export default class PhaseInterceptor {
[PartyHealPhase, this.startPhase],
[EvolutionPhase, this.startPhase],
[EndEvolutionPhase, this.startPhase],
+ [LevelCapPhase, this.startPhase],
];
private endBySetMode = [
@@ -237,6 +241,22 @@ export default class PhaseInterceptor {
this.scene.shiftPhase();
}
+ /**
+ * Remove the current phase from the phase interceptor.
+ *
+ * Do not call this unless absolutely necessary. This function is intended
+ * for cleaning up the phase interceptor when, for whatever reason, a phase
+ * is manually ended without using the phase interceptor.
+ *
+ * @param shouldRun Whether or not the current scene should also be run.
+ */
+ shift(shouldRun: boolean = false) : void {
+ this.onHold.shift();
+ if (shouldRun) {
+ this.scene.shiftPhase();
+ }
+ }
+
/**
* Method to initialize phases and their corresponding methods.
*/
diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts
index 18de67bfa86..874bf6a8b46 100644
--- a/src/timed-event-manager.ts
+++ b/src/timed-event-manager.ts
@@ -1,35 +1,39 @@
-import BattleScene from "#app/battle-scene.js";
-import { TextStyle, addTextObject } from "#app/ui/text.js";
+import BattleScene from "#app/battle-scene";
+import { TextStyle, addTextObject } from "#app/ui/text";
+import i18next from "i18next";
export enum EventType {
- SHINY
+ SHINY,
+ GENERIC
}
-interface TimedEvent {
- name: string;
- eventType: EventType;
- shinyMultiplier?: number;
- startDate: Date;
- endDate: Date;
- bannerFilename?: string
+interface EventBanner {
+ bannerKey?: string;
+ xPosition?: number;
+ yPosition?: number;
+ scale?: number;
+ availableLangs?: string[];
+}
+
+interface TimedEvent extends EventBanner {
+ name: string;
+ eventType: EventType;
+ shinyMultiplier?: number;
+ startDate: Date;
+ endDate: Date;
}
const timedEvents: TimedEvent[] = [
{
- name: "Pride Update",
- eventType: EventType.SHINY,
- shinyMultiplier: 2,
- startDate: new Date(Date.UTC(2024, 5, 14, 0)),
- endDate: new Date(Date.UTC(2024, 5, 23, 0)),
- bannerFilename: "pride-update"
- },
- {
- name: "August Variant Update",
- eventType: EventType.SHINY,
- shinyMultiplier: 2,
- startDate: new Date(Date.UTC(2024, 7, 16, 0)),
- endDate: new Date(Date.UTC(2024, 7, 22, 0)),
- bannerFilename: "august-variant-update"
+ name: "September Update",
+ eventType: EventType.GENERIC,
+ startDate: new Date(Date.UTC(2024, 7, 28, 0)),
+ endDate: new Date(Date.UTC(2024, 8, 15, 0)),
+ bannerKey: "september-update",
+ xPosition: 19,
+ yPosition: 115,
+ scale: 0.30,
+ availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"]
}
];
@@ -67,7 +71,7 @@ export class TimedEventManager {
}
getEventBannerFilename(): string {
- return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerFilename!; // TODO: is this bang correct?
+ return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerKey!; // TODO: is this bang correct?
}
}
@@ -85,38 +89,37 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
}
setup() {
- console.log(this.event?.bannerFilename);
- this.banner = new Phaser.GameObjects.Image(this.scene, 29, 64, this.event!.bannerFilename!); // TODO: are the bangs correct here?
- this.banner.setName("img-event-banner");
- this.banner.setOrigin(0.08, -0.35);
- this.banner.setScale(0.18);
- // this.bannerShadow = new Phaser.GameObjects.Rectangle(
- // this.scene,
- // this.banner.x - 2,
- // this.banner.y + 2,
- // this.banner.width,
- // this.banner.height,
- // 0x484848
- // );
- // this.bannerShadow.setName("rect-event-banner-shadow");
- // this.bannerShadow.setScale(0.07);
- // this.bannerShadow.setAlpha(0.5);
- // this.bannerShadow.setOrigin(0,0);
- this.eventTimerText = addTextObject(
- this.scene,
- this.banner.x + 8,
- this.banner.y + 100,
- this.timeToGo(this.event!.endDate), // TODO: is the bang correct here?
- TextStyle.WINDOW
- );
- this.eventTimerText.setName("text-event-timer");
- this.eventTimerText.setScale(0.15);
- this.eventTimerText.setOrigin(0, 0);
+ const lang = i18next.resolvedLanguage;
+ if (this.event && this.event.bannerKey) {
+ let key = this.event.bannerKey;
+ if (lang && this.event.availableLangs && this.event.availableLangs.length > 0) {
+ if (this.event.availableLangs.includes(lang)) {
+ key += "-"+lang;
+ } else {
+ key += "-en";
+ }
+ }
+ console.log(this.event.bannerKey);
+ this.banner = new Phaser.GameObjects.Image(this.scene, this.event.xPosition ?? 29, this.event.yPosition ?? 64, key);
+ this.banner.setName("img-event-banner");
+ this.banner.setOrigin(0.08, -0.35);
+ this.banner.setScale(this.event.scale ?? 0.18);
+ if (this.event.eventType !== EventType.GENERIC) {
+ this.eventTimerText = addTextObject(
+ this.scene,
+ this.banner.x + 8,
+ this.banner.y + 100,
+ this.timeToGo(this.event.endDate),
+ TextStyle.WINDOW
+ );
+ this.eventTimerText.setName("text-event-timer");
+ this.eventTimerText.setScale(0.15);
+ this.eventTimerText.setOrigin(0, 0);
- this.add([
- this.eventTimerText,
- // this.bannerShadow,
- this.banner]);
+ this.add(this.eventTimerText);
+ }
+ this.add(this.banner);
+ }
}
show() {
@@ -157,6 +160,8 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container {
}
updateCountdown() {
- this.eventTimerText.setText(this.timeToGo(this.event!.endDate)); // TODO: is the bang correct here?
+ if (this.event && this.event.eventType !== EventType.GENERIC) {
+ this.eventTimerText.setText(this.timeToGo(this.event.endDate));
+ }
}
}
diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts
index eb4316dc24b..605b8c538a9 100644
--- a/src/ui/achvs-ui-handler.ts
+++ b/src/ui/achvs-ui-handler.ts
@@ -74,7 +74,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
this.headerText = addTextObject(this.scene, 0, 0, "", TextStyle.SETTINGS_LABEL);
this.headerText.setOrigin(0, 0);
this.headerText.setPositionRelative(this.headerBg, 8, 4);
- this.headerActionButton = new Phaser.GameObjects.Sprite(this.scene, 0, 0, "keyboard", "SPACE.png");
+ this.headerActionButton = new Phaser.GameObjects.Sprite(this.scene, 0, 0, "keyboard", "ACTION.png");
this.headerActionButton.setOrigin(0, 0);
this.headerActionButton.setPositionRelative(this.headerBg, 236, 6);
this.headerActionText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW, {fontSize:"60px"});
diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts
index 11b807e8ab7..05c634609f8 100644
--- a/src/ui/battle-info.ts
+++ b/src/ui/battle-info.ts
@@ -7,7 +7,7 @@ import { StatusEffect } from "../data/status-effect";
import BattleScene from "../battle-scene";
import { Type, getTypeRgb } from "../data/type";
import { getVariantTint } from "#app/data/variant";
-import { BattleStat } from "#app/data/battle-stat";
+import { Stat } from "#enums/stat";
import BattleFlyout from "./battle-flyout";
import { WindowVariant, addWindow } from "./ui-theme";
import i18next from "i18next";
@@ -30,7 +30,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private lastLevelExp: integer;
private lastLevel: integer;
private lastLevelCapped: boolean;
- private lastBattleStats: string;
+ private lastStats: string;
private box: Phaser.GameObjects.Sprite;
private nameText: Phaser.GameObjects.Text;
@@ -68,9 +68,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
public flyoutMenu?: BattleFlyout;
- private battleStatOrder: BattleStat[];
- private battleStatOrderPlayer = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD];
- private battleStatOrderEnemy = [BattleStat.HP, BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD];
+ private statOrder: Stat[];
+ private readonly statOrderPlayer = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ];
+ private readonly statOrderEnemy = [ Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ];
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y);
@@ -229,9 +229,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5;
const paddingX = this.player ? 4 : 2;
const statOverflow = this.player ? 1 : 0;
- this.battleStatOrder = this.player ? this.battleStatOrderPlayer : this.battleStatOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order
+ this.statOrder = this.player ? this.statOrderPlayer : this.statOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order
- this.battleStatOrder.map((s, i) => {
+ this.statOrder.map((s, i) => {
// we do a check for i > statOverflow to see when the stat labels go onto the next column
// For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0
// For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1
@@ -239,25 +239,25 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis
let statY: number; // this will be the y-axis placement for the labels
- if (this.battleStatOrder[i] === BattleStat.SPD || this.battleStatOrder[i] === BattleStat.HP) {
+ if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) {
statY = baseY + 5;
} else {
statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us
}
- const statLabel = this.scene.add.sprite(statX, statY, "pbinfo_stat", BattleStat[s]);
+ const statLabel = this.scene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]);
statLabel.setName("icon_stat_label_" + i.toString());
statLabel.setOrigin(0, 0);
statLabels.push(statLabel);
this.statValuesContainer.add(statLabel);
- const statNumber = this.scene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.battleStatOrder[i] !== BattleStat.HP ? "3" : "empty");
+ const statNumber = this.scene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.statOrder[i] !== Stat.HP ? "3" : "empty");
statNumber.setName("icon_stat_number_" + i.toString());
statNumber.setOrigin(0, 0);
this.statNumbers.push(statNumber);
this.statValuesContainer.add(statNumber);
- if (this.battleStatOrder[i] === BattleStat.HP) {
+ if (this.statOrder[i] === Stat.HP) {
statLabel.setVisible(false);
statNumber.setVisible(false);
}
@@ -433,10 +433,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.statValuesContainer.setPosition(8, 7);
}
- const battleStats = this.battleStatOrder.map(() => 0);
+ const stats = this.statOrder.map(() => 0);
- this.lastBattleStats = battleStats.join("");
- this.updateBattleStats(battleStats);
+ this.lastStats = stats.join("");
+ this.updateStats(stats);
}
getTextureName(): string {
@@ -650,14 +650,12 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.lastLevel = pokemon.level;
}
- const battleStats = pokemon.summonData
- ? pokemon.summonData.battleStats
- : this.battleStatOrder.map(() => 0);
- const battleStatsStr = battleStats.join("");
+ const stats = pokemon.getStatStages();
+ const statsStr = stats.join("");
- if (this.lastBattleStats !== battleStatsStr) {
- this.updateBattleStats(battleStats);
- this.lastBattleStats = battleStatsStr;
+ if (this.lastStats !== statsStr) {
+ this.updateStats(stats);
+ this.lastStats = statsStr;
}
this.shinyIcon.setVisible(pokemon.isShiny());
@@ -769,10 +767,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
}
}
- updateBattleStats(battleStats: integer[]): void {
- this.battleStatOrder.map((s, i) => {
- if (s !== BattleStat.HP) {
- this.statNumbers[i].setFrame(battleStats[s].toString());
+ updateStats(stats: integer[]): void {
+ this.statOrder.map((s, i) => {
+ if (s !== Stat.HP) {
+ this.statNumbers[i].setFrame(stats[s - 1].toString());
}
});
}
diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts
index 86f8d9e01a8..3bea0f21433 100644
--- a/src/ui/battle-message-ui-handler.ts
+++ b/src/ui/battle-message-ui-handler.ts
@@ -1,13 +1,12 @@
import BattleScene from "../battle-scene";
import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./text";
import { Mode } from "./ui";
-import * as Utils from "../utils";
import MessageUiHandler from "./message-ui-handler";
-import { getStatName, Stat } from "../data/pokemon-stat";
import { addWindow } from "./ui-theme";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import {Button} from "#enums/buttons";
import i18next from "i18next";
+import { Stat, PERMANENT_STATS, getStatKey } from "#app/enums/stat";
export default class BattleMessageUiHandler extends MessageUiHandler {
private levelUpStatsContainer: Phaser.GameObjects.Container;
@@ -100,9 +99,8 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
const levelUpStatsLabelsContent = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 73, -94, "", TextStyle.WINDOW, { maxLines: 6 });
let levelUpStatsLabelText = "";
- const stats = Utils.getEnumValues(Stat);
- for (const s of stats) {
- levelUpStatsLabelText += `${getStatName(s)}\n`;
+ for (const s of PERMANENT_STATS) {
+ levelUpStatsLabelText += `${i18next.t(getStatKey(s))}\n`;
}
levelUpStatsLabelsContent.text = levelUpStatsLabelText;
levelUpStatsLabelsContent.x -= levelUpStatsLabelsContent.displayWidth;
@@ -176,8 +174,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
}
const newStats = (this.scene as BattleScene).getParty()[partyMemberIndex].stats;
let levelUpStatsValuesText = "";
- const stats = Utils.getEnumValues(Stat);
- for (const s of stats) {
+ for (const s of PERMANENT_STATS) {
levelUpStatsValuesText += `${showTotals ? newStats[s] : newStats[s] - prevStats[s]}\n`;
}
this.levelUpStatsValuesContent.text = levelUpStatsValuesText;
@@ -199,10 +196,9 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
return new Promise(resolve => {
this.scene.executeWithSeedOffset(() => {
let levelUpStatsValuesText = "";
- const stats = Utils.getEnumValues(Stat);
const shownStats = this.getTopIvs(ivs, shownIvsCount);
- for (const s of stats) {
- levelUpStatsValuesText += `${shownStats.indexOf(s) > -1 ? this.getIvDescriptor(ivs[s], s, pokemonId) : "???"}\n`;
+ for (const s of PERMANENT_STATS) {
+ levelUpStatsValuesText += `${shownStats.includes(s) ? this.getIvDescriptor(ivs[s], s, pokemonId) : "???"}\n`;
}
this.levelUpStatsValuesContent.text = levelUpStatsValuesText;
this.levelUpStatsIncrContent.setVisible(false);
@@ -217,26 +213,16 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
}
getTopIvs(ivs: integer[], shownIvsCount: integer): Stat[] {
- const stats = Utils.getEnumValues(Stat);
let shownStats: Stat[] = [];
if (shownIvsCount < 6) {
- const statsPool = stats.slice(0);
+ const statsPool = PERMANENT_STATS.slice();
+ // Sort the stats from highest to lowest iv
+ statsPool.sort((s1, s2) => ivs[s2] - ivs[s1]);
for (let i = 0; i < shownIvsCount; i++) {
- let shownStat: Stat | null = null;
- let highestIv = -1;
- statsPool.map(s => {
- if (ivs[s] > highestIv) {
- shownStat = s as Stat;
- highestIv = ivs[s];
- }
- });
- if (shownStat !== null && shownStat !== undefined) {
- shownStats.push(shownStat);
- statsPool.splice(statsPool.indexOf(shownStat), 1);
- }
+ shownStats.push(statsPool[i]);
}
} else {
- shownStats = stats;
+ shownStats = PERMANENT_STATS.slice();
}
return shownStats;
}
diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts
index f1ba0da6c51..e08736d2b70 100644
--- a/src/ui/challenges-select-ui-handler.ts
+++ b/src/ui/challenges-select-ui-handler.ts
@@ -5,13 +5,13 @@ import UiHandler from "./ui-handler";
import { addWindow } from "./ui-theme";
import {Button} from "#enums/buttons";
import i18next from "i18next";
-import { Challenge } from "#app/data/challenge.js";
+import { Challenge } from "#app/data/challenge";
import * as Utils from "../utils";
-import { Challenges } from "#app/enums/challenges.js";
+import { Challenges } from "#app/enums/challenges";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
-import { Color, ShadowColor } from "#app/enums/color.js";
-import { SelectStarterPhase } from "#app/phases/select-starter-phase.js";
-import { TitlePhase } from "#app/phases/title-phase.js";
+import { Color, ShadowColor } from "#app/enums/color";
+import { SelectStarterPhase } from "#app/phases/select-starter-phase";
+import { TitlePhase } from "#app/phases/title-phase";
/**
* Handles all the UI for choosing optional challenges.
@@ -33,7 +33,10 @@ export default class GameChallengesUiHandler extends UiHandler {
private cursorObj: Phaser.GameObjects.NineSlice | null;
+ private startBg: Phaser.GameObjects.NineSlice;
private startCursor: Phaser.GameObjects.NineSlice;
+ private startText: Phaser.GameObjects.Text;
+ private hasSelectedChallenge: boolean;
private optionsWidth: number;
@@ -104,20 +107,20 @@ export default class GameChallengesUiHandler extends UiHandler {
this.descriptionText.setShadow(4, 5, ShadowColor.ORANGE);
this.descriptionText.setOrigin(0, 0);
- const startBg = addWindow(this.scene, 0, 0, descriptionBg.width, 24);
- startBg.setName("window-start-bg");
- startBg.setOrigin(0, 0);
- startBg.setPositionRelative(descriptionBg, 0, descriptionBg.height);
+ this.startBg = addWindow(this.scene, 0, 0, descriptionBg.width, 24);
+ this.startBg.setName("window-start-bg");
+ this.startBg.setOrigin(0, 0);
+ this.startBg.setPositionRelative(descriptionBg, 0, descriptionBg.height);
- const startText = addTextObject(this.scene, 0, 0, i18next.t("common:start"), TextStyle.SETTINGS_LABEL);
- startText.setName("text-start");
- startText.setOrigin(0, 0);
- startText.setPositionRelative(startBg, (startBg.width - startText.displayWidth) / 2, 4);
+ this.startText = addTextObject(this.scene, 0, 0, i18next.t("challenges:noneSelected"), TextStyle.SETTINGS_LABEL);
+ this.startText.setName("text-start");
+ this.startText.setOrigin(0, 0);
+ this.startText.setPositionRelative(this.startBg, (this.startBg.width - this.startText.displayWidth) / 2, 4);
this.startCursor = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, descriptionBg.width - 8, 16, 1, 1, 1, 1);
this.startCursor.setName("9s-start-cursor");
this.startCursor.setOrigin(0, 0);
- this.startCursor.setPositionRelative(startBg, 4, 3);
+ this.startCursor.setPositionRelative(this.startBg, 4, 3);
this.startCursor.setVisible(false);
this.valuesContainer = this.scene.add.container(0, 0);
@@ -157,8 +160,8 @@ export default class GameChallengesUiHandler extends UiHandler {
this.challengesContainer.add(this.optionsBg);
this.challengesContainer.add(descriptionBg);
this.challengesContainer.add(this.descriptionText);
- this.challengesContainer.add(startBg);
- this.challengesContainer.add(startText);
+ this.challengesContainer.add(this.startBg);
+ this.challengesContainer.add(this.startText);
this.challengesContainer.add(this.startCursor);
this.challengesContainer.add(this.valuesContainer);
@@ -216,6 +219,21 @@ export default class GameChallengesUiHandler extends UiHandler {
this.monoTypeValue.setVisible(false);
}
+ // This checks if a challenge has been selected by the user and updates the text/its opacity accordingly.
+ this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0);
+ if (this.hasSelectedChallenge) {
+
+ this.startText.setText(i18next.t("common:start"));
+ this.startText.setAlpha(1);
+ this.startText.setPositionRelative(this.startBg, (this.startBg.width - this.startText.displayWidth) / 2, 4);
+ } else {
+
+ this.startText.setText(i18next.t("challenges:noneSelected"));
+ this.startText.setAlpha(0.5);
+ this.startText.setPositionRelative(this.startBg, (this.startBg.width - this.startText.displayWidth) / 2, 4);
+ }
+ this.challengesContainer.update();
+
// const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0);
// const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0);
// this.difficultyText.text = `${totalDifficulty}` + (totalMinDifficulty ? `/${totalMinDifficulty}` : "");
@@ -227,6 +245,8 @@ export default class GameChallengesUiHandler extends UiHandler {
this.startCursor.setVisible(false);
this.challengesContainer.setVisible(true);
+ // Should always be false at the start
+ this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0);
this.setCursor(0);
this.initLabels();
@@ -257,6 +277,7 @@ export default class GameChallengesUiHandler extends UiHandler {
if (button === Button.CANCEL) {
if (this.startCursor.visible) {
+ // If the user presses cancel when the start cursor has been activated, the game deactivates the start cursor and allows typical challenge selection behavior
this.startCursor.setVisible(false);
this.cursorObj?.setVisible(true);
} else {
@@ -266,83 +287,82 @@ export default class GameChallengesUiHandler extends UiHandler {
}
success = true;
} else if (button === Button.SUBMIT || button === Button.ACTION) {
- if (this.startCursor.visible) {
- const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0);
- const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0);
- if (totalDifficulty >= totalMinDifficulty) {
+ if (this.hasSelectedChallenge) {
+ if (this.startCursor.visible) {
this.scene.unshiftPhase(new SelectStarterPhase(this.scene));
this.scene.getCurrentPhase()?.end();
- success = true;
} else {
- success = false;
+ this.startCursor.setVisible(true);
+ this.cursorObj?.setVisible(false);
}
- } else {
- this.startCursor.setVisible(true);
- this.cursorObj?.setVisible(false);
success = true;
+ } else {
+ success = false;
}
} else {
- switch (button) {
- case Button.UP:
- if (this.cursor === 0) {
- if (this.scrollCursor === 0) {
- // When at the top of the menu and pressing UP, move to the bottommost item.
- if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom
- // First, set the cursor to the last visible element, preparing for the scroll to the end.
- const successA = this.setCursor(rowsToDisplay - 1);
- // Then, adjust the scroll to display the bottommost elements of the menu.
- const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay);
- success = successA && successB; // success is just there to play the little validation sound effect
- } else { // If there are 9 or less challenges, just move to the bottom one
- success = this.setCursor(this.scene.gameMode.challenges.length - 1);
+ if (this.cursorObj?.visible && !this.startCursor.visible) {
+ switch (button) {
+ case Button.UP:
+ if (this.cursor === 0) {
+ if (this.scrollCursor === 0) {
+ // When at the top of the menu and pressing UP, move to the bottommost item.
+ if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom
+ // First, set the cursor to the last visible element, preparing for the scroll to the end.
+ const successA = this.setCursor(rowsToDisplay - 1);
+ // Then, adjust the scroll to display the bottommost elements of the menu.
+ const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay);
+ success = successA && successB; // success is just there to play the little validation sound effect
+ } else { // If there are 9 or less challenges, just move to the bottom one
+ success = this.setCursor(this.scene.gameMode.challenges.length - 1);
+ }
+ } else {
+ success = this.setScrollCursor(this.scrollCursor - 1);
}
} else {
- success = this.setScrollCursor(this.scrollCursor - 1);
+ success = this.setCursor(this.cursor - 1);
}
- } else {
- success = this.setCursor(this.cursor - 1);
- }
- if (success) {
- this.updateText();
- }
- break;
- case Button.DOWN:
- if (this.cursor === rowsToDisplay - 1) {
- if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) {
- // When at the bottom and pressing DOWN, scroll if possible.
- success = this.setScrollCursor(this.scrollCursor + 1);
+ if (success) {
+ this.updateText();
+ }
+ break;
+ case Button.DOWN:
+ if (this.cursor === rowsToDisplay - 1) {
+ if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) {
+ // When at the bottom and pressing DOWN, scroll if possible.
+ success = this.setScrollCursor(this.scrollCursor + 1);
+ } else {
+ // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item.
+ // First, set the cursor to the first visible element, preparing for the scroll to the top.
+ const successA = this.setCursor(0);
+ // Then, adjust the scroll to display the topmost elements of the menu.
+ const successB = this.setScrollCursor(0);
+ success = successA && successB; // success is just there to play the little validation sound effect
+ }
+ } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) {
+ // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item.
+ success = this.setCursor(0);
} else {
- // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item.
- // First, set the cursor to the first visible element, preparing for the scroll to the top.
- const successA = this.setCursor(0);
- // Then, adjust the scroll to display the topmost elements of the menu.
- const successB = this.setScrollCursor(0);
- success = successA && successB; // success is just there to play the little validation sound effect
+ success = this.setCursor(this.cursor + 1);
}
- } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) {
- // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item.
- success = this.setCursor(0);
- } else {
- success = this.setCursor(this.cursor + 1);
+ if (success) {
+ this.updateText();
+ }
+ break;
+ case Button.LEFT:
+ // Moves the option cursor left, if possible.
+ success = this.getActiveChallenge().decreaseValue();
+ if (success) {
+ this.updateText();
+ }
+ break;
+ case Button.RIGHT:
+ // Moves the option cursor right, if possible.
+ success = this.getActiveChallenge().increaseValue();
+ if (success) {
+ this.updateText();
+ }
+ break;
}
- if (success) {
- this.updateText();
- }
- break;
- case Button.LEFT:
- // Moves the option cursor left, if possible.
- success = this.getActiveChallenge().decreaseValue();
- if (success) {
- this.updateText();
- }
- break;
- case Button.RIGHT:
- // Moves the option cursor right, if possible.
- success = this.getActiveChallenge().increaseValue();
- if (success) {
- this.updateText();
- }
- break;
}
}
@@ -350,7 +370,6 @@ export default class GameChallengesUiHandler extends UiHandler {
if (success) {
ui.playSelect();
}
-
return success;
}
diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts
index b22ea5d20fc..27ff923e9a3 100644
--- a/src/ui/command-ui-handler.ts
+++ b/src/ui/command-ui-handler.ts
@@ -68,7 +68,11 @@ export default class CommandUiHandler extends UiHandler {
messageHandler.movesWindowContainer.setVisible(false);
messageHandler.message.setWordWrapWidth(1110);
messageHandler.showText(i18next.t("commandUiHandler:actionMessage", {pokemonName: getPokemonNameWithAffix(commandPhase.getPokemon())}), 0);
- this.setCursor(this.getCursor());
+ if (this.getCursor() === Command.POKEMON) {
+ this.setCursor(Command.FIGHT);
+ } else {
+ this.setCursor(this.getCursor());
+ }
return true;
}
@@ -85,7 +89,7 @@ export default class CommandUiHandler extends UiHandler {
if (button === Button.ACTION) {
switch (cursor) {
// Fight
- case 0:
+ case Command.FIGHT:
if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) {
return true;
}
@@ -93,17 +97,17 @@ export default class CommandUiHandler extends UiHandler {
success = true;
break;
// Ball
- case 1:
+ case Command.BALL:
ui.setModeWithoutClear(Mode.BALL);
success = true;
break;
// Pokemon
- case 2:
+ case Command.POKEMON:
ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted);
success = true;
break;
// Run
- case 3:
+ case Command.RUN:
(this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0);
success = true;
break;
diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts
index 1fef7259108..08d55b03cdb 100644
--- a/src/ui/dropdown.ts
+++ b/src/ui/dropdown.ts
@@ -23,6 +23,14 @@ export enum SortDirection {
DESC = 1
}
+export enum SortCriteria {
+ NUMBER = 0,
+ COST = 1,
+ CANDY = 2,
+ IV = 3,
+ NAME = 4
+}
+
export class DropDownLabel {
public state: DropDownState;
public text: string;
diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts
new file mode 100644
index 00000000000..af82ab33438
--- /dev/null
+++ b/src/ui/egg-summary-ui-handler.ts
@@ -0,0 +1,320 @@
+import BattleScene from "../battle-scene";
+import { Mode } from "./ui";
+import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler";
+import MessageUiHandler from "./message-ui-handler";
+import { getEggTierForSpecies } from "../data/egg";
+import {Button} from "#enums/buttons";
+import { Gender } from "#app/data/gender";
+import { getVariantTint } from "#app/data/variant";
+import { EggTier } from "#app/enums/egg-type";
+import PokemonHatchInfoContainer from "./pokemon-hatch-info-container";
+import { EggSummaryPhase } from "#app/phases/egg-summary-phase";
+import { DexAttr } from "#app/system/game-data";
+import { EggHatchData } from "#app/data/egg-hatch-data";
+
+const iconContainerX = 115;
+const iconContainerY = 9;
+const numCols = 11;
+const iconSize = 18;
+
+/**
+ * UI Handler for the egg summary.
+ * Handles navigation and display of each pokemon as a list
+ * Also handles display of the pokemon-hatch-info-container
+ */
+export default class EggSummaryUiHandler extends MessageUiHandler {
+ /** holds all elements in the scene */
+ private eggHatchContainer: Phaser.GameObjects.Container;
+ /** holds the icon containers and info container */
+ private summaryContainer: Phaser.GameObjects.Container;
+ /** container for the mini pokemon sprites */
+ private pokemonIconSpritesContainer: Phaser.GameObjects.Container;
+ /** container for the icons displayed alongside the mini icons (e.g. shiny, HA capsule) */
+ private pokemonIconsContainer: Phaser.GameObjects.Container;
+ /** hatch info container that displays the current pokemon / hatch (main element on left hand side) */
+ private infoContainer: PokemonHatchInfoContainer;
+ /** handles jumping animations for the pokemon sprite icons */
+ private iconAnimHandler: PokemonIconAnimHandler;
+ private eggHatchBg: Phaser.GameObjects.Image;
+ private cursorObj: Phaser.GameObjects.Image;
+ private eggHatchData: EggHatchData[];
+
+
+ /**
+ * Allows subscribers to listen for events
+ *
+ * Current Events:
+ * - {@linkcode EggEventType.EGG_COUNT_CHANGED} {@linkcode EggCountChangedEvent}
+ */
+ public readonly eventTarget: EventTarget = new EventTarget();
+
+ constructor(scene: BattleScene) {
+ super(scene, Mode.EGG_HATCH_SUMMARY);
+ }
+
+
+ setup() {
+ const ui = this.getUi();
+
+ this.summaryContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6);
+ this.summaryContainer.setVisible(false);
+ ui.add(this.summaryContainer);
+
+ this.eggHatchContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6);
+ this.eggHatchContainer.setVisible(false);
+ ui.add(this.eggHatchContainer);
+
+ this.iconAnimHandler = new PokemonIconAnimHandler();
+ this.iconAnimHandler.setup(this.scene);
+
+ this.eggHatchBg = this.scene.add.image(0, 0, "egg_summary_bg");
+ this.eggHatchBg.setOrigin(0, 0);
+ this.eggHatchContainer.add(this.eggHatchBg);
+
+ this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY);
+ this.pokemonIconSpritesContainer = this.scene.add.container(iconContainerX, iconContainerY);
+ this.summaryContainer.add(this.pokemonIconsContainer);
+ this.summaryContainer.add(this.pokemonIconSpritesContainer);
+
+ this.cursorObj = this.scene.add.image(0, 0, "select_cursor");
+ this.cursorObj.setOrigin(0, 0);
+ this.summaryContainer.add(this.cursorObj);
+
+ this.infoContainer = new PokemonHatchInfoContainer(this.scene, this.summaryContainer);
+ this.infoContainer.setup();
+ this.infoContainer.changeToEggSummaryLayout();
+ this.infoContainer.setVisible(true);
+ this.summaryContainer.add(this.infoContainer);
+
+ this.cursor = -1;
+ }
+
+ clear() {
+ super.clear();
+ this.cursor = -1;
+ this.summaryContainer.setVisible(false);
+ this.pokemonIconSpritesContainer.removeAll(true);
+ this.pokemonIconsContainer.removeAll(true);
+ this.eggHatchBg.setVisible(false);
+ this.getUi().hideTooltip();
+ // Note: Questions on garbage collection go to @frutescens
+ const activeKeys = this.scene.getActiveKeys();
+ // Removing unnecessary sprites from animation manager
+ const animKeys = Object.keys(this.scene.anims["anims"]["entries"]);
+ animKeys.forEach(key => {
+ if (key.startsWith("pkmn__") && !activeKeys.includes(key)) {
+ this.scene.anims.remove(key);
+ }
+ });
+ // Removing unnecessary cries from audio cache
+ const audioKeys = Object.keys(this.scene.cache.audio.entries.entries);
+ audioKeys.forEach(key => {
+ if (key.startsWith("cry/") && !activeKeys.includes(key)) {
+ delete this.scene.cache.audio.entries.entries[key];
+ }
+ });
+ // Clears eggHatchData in EggSummaryUiHandler
+ this.eggHatchData.length = 0;
+ // Removes Pokemon icons in EggSummaryUiHandler
+ this.iconAnimHandler.removeAll();
+ console.log("Egg Summary Handler cleared");
+ }
+
+ /**
+ * @param args EggHatchData[][]
+ * args[0]: list of EggHatchData for each egg/pokemon hatched
+ */
+ show(args: EggHatchData[][]): boolean {
+ super.show(args);
+ if (args.length >= 1) {
+ // sort the egg hatch data by egg tier then by species number (then by order hatched)
+ this.eggHatchData = args[0].sort(function sortHatchData(a: EggHatchData, b: EggHatchData) {
+ const speciesA = a.pokemon.species;
+ const speciesB = b.pokemon.species;
+ if (getEggTierForSpecies(speciesA) < getEggTierForSpecies(speciesB)) {
+ return -1;
+ } else if (getEggTierForSpecies(speciesA) > getEggTierForSpecies(speciesB)) {
+ return 1;
+ } else {
+ if (speciesA.speciesId < speciesB.speciesId) {
+ return -1;
+ } else if (speciesA.speciesId > speciesB.speciesId) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ );
+ }
+
+ this.getUi().bringToTop(this.summaryContainer);
+ this.summaryContainer.setVisible(true);
+ this.eggHatchContainer.setVisible(true);
+ this.pokemonIconsContainer.setVisible(true);
+ this.eggHatchBg.setVisible(true);
+ this.infoContainer.hideDisplayPokemon();
+
+ this.eggHatchData.forEach( (value: EggHatchData, i: number) => {
+ const x = (i % numCols) * iconSize;
+ const y = Math.floor(i / numCols) * iconSize;
+
+ const displayPokemon = value.pokemon;
+ const offset = 2;
+ const rightSideX = 12;
+
+ const bg = this.scene.add.image(x+2, y+5, "passive_bg");
+ bg.setOrigin(0, 0);
+ bg.setScale(0.75);
+ bg.setVisible(true);
+ this.pokemonIconsContainer.add(bg);
+
+ // set tint for passive bg
+ switch (getEggTierForSpecies(displayPokemon.species)) {
+ case EggTier.COMMON:
+ bg.setVisible(false);
+ break;
+ case EggTier.GREAT:
+ bg.setTint(0xabafff);
+ break;
+ case EggTier.ULTRA:
+ bg.setTint(0xffffaa);
+ break;
+ case EggTier.MASTER:
+ bg.setTint(0xdfffaf);
+ break;
+ }
+ const species = displayPokemon.species;
+ const female = displayPokemon.gender === Gender.FEMALE;
+ const formIndex = displayPokemon.formIndex;
+ const variant = displayPokemon.variant;
+ const isShiny = displayPokemon.shiny;
+
+ // set pokemon icon (and replace with base sprite if there is a mismatch)
+ const icon = this.scene.add.sprite(x - offset, y + offset, species.getIconAtlasKey(formIndex, isShiny, variant));
+ icon.setScale(0.5);
+ icon.setOrigin(0, 0);
+ icon.setFrame(species.getIconId(female, formIndex, isShiny, variant));
+
+ if (icon.frame.name !== species.getIconId(female, formIndex, isShiny, variant)) {
+ console.log(`${species.name}'s variant icon does not exist. Replacing with default.`);
+ icon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
+ icon.setFrame(species.getIconId(female, formIndex, false, variant));
+ }
+ this.pokemonIconSpritesContainer.add(icon);
+ this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE);
+
+ const shiny = this.scene.add.image(x + rightSideX, y + offset * 2, "shiny_star_small");
+ shiny.setScale(0.5);
+ shiny.setVisible(displayPokemon.shiny);
+ shiny.setTint(getVariantTint(displayPokemon.variant));
+ this.pokemonIconsContainer.add(shiny);
+
+ const ha = this.scene.add.image(x + rightSideX, y + 7, "ha_capsule");
+ ha.setScale(0.5);
+ ha.setVisible((displayPokemon.hasAbility(displayPokemon.species.abilityHidden)));
+ this.pokemonIconsContainer.add(ha);
+
+ const pb = this.scene.add.image(x + rightSideX, y + offset * 7, "icon_owned");
+ pb.setOrigin(0, 0);
+ pb.setScale(0.5);
+
+ // add animation for new unlocks (new catch or new shiny or new form)
+ const dexEntry = value.dexEntryBeforeUpdate;
+ const caughtAttr = dexEntry.caughtAttr;
+ const newShiny = BigInt(1 << (displayPokemon.shiny ? 1 : 0));
+ const newVariant = BigInt(1 << (displayPokemon.variant + 4));
+ const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0));
+ const newForm = (BigInt(1 << displayPokemon.formIndex) * DexAttr.DEFAULT_FORM & caughtAttr) === BigInt(0);
+
+ pb.setVisible(!caughtAttr || newForm);
+ if (!caughtAttr || newShinyOrVariant || newForm) {
+ this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE);
+ }
+ this.pokemonIconsContainer.add(pb);
+
+ const em = this.scene.add.image(x, y + offset, "icon_egg_move");
+ em.setOrigin(0, 0);
+ em.setScale(0.5);
+ em.setVisible(value.eggMoveUnlocked);
+ this.pokemonIconsContainer.add(em);
+ });
+
+ this.setCursor(0);
+ this.scene.playSoundWithoutBgm("evolution_fanfare");
+ return true;
+ }
+
+ processInput(button: Button): boolean {
+ const ui = this.getUi();
+
+ let success = false;
+ const error = false;
+ if (button === Button.CANCEL) {
+ const phase = this.scene.getCurrentPhase();
+ if (phase instanceof EggSummaryPhase) {
+ phase.end();
+ }
+ ui.revertMode();
+ success = true;
+ } else {
+ const count = this.eggHatchData.length;
+ const rows = Math.ceil(count / numCols);
+ const row = Math.floor(this.cursor / numCols);
+ switch (button) {
+ case Button.UP:
+ if (row) {
+ success = this.setCursor(this.cursor - numCols);
+ }
+ break;
+ case Button.DOWN:
+ if (row < rows - 2 || (row < rows - 1 && this.cursor % numCols <= (count - 1) % numCols)) {
+ success = this.setCursor(this.cursor + numCols);
+ }
+ break;
+ case Button.LEFT:
+ if (this.cursor % numCols) {
+ success = this.setCursor(this.cursor - 1);
+ }
+ break;
+ case Button.RIGHT:
+ if (this.cursor % numCols < (row < rows - 1 ? 10 : (count - 1) % numCols)) {
+ success = this.setCursor(this.cursor + 1);
+ }
+ break;
+ }
+ }
+
+ if (success) {
+ ui.playSelect();
+ } else if (error) {
+ ui.playError();
+ }
+
+ return success || error;
+ }
+
+ setCursor(cursor: number): boolean {
+ let changed = false;
+
+ const lastCursor = this.cursor;
+
+ changed = super.setCursor(cursor);
+
+ if (changed) {
+ this.cursorObj.setPosition(iconContainerX - 1 + iconSize * (cursor % numCols), iconContainerY + 1 + iconSize * Math.floor(cursor / numCols));
+
+ if (lastCursor > -1) {
+ this.iconAnimHandler.addOrUpdate(this.pokemonIconSpritesContainer.getAt(lastCursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.NONE);
+ }
+ this.iconAnimHandler.addOrUpdate(this.pokemonIconSpritesContainer.getAt(cursor) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.ACTIVE);
+
+ this.infoContainer.showHatchInfo(this.eggHatchData[cursor]);
+
+ }
+
+ return changed;
+ }
+
+}
diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts
index 0beaddbb517..4bbe88dabd9 100644
--- a/src/ui/fight-ui-handler.ts
+++ b/src/ui/fight-ui-handler.ts
@@ -95,9 +95,13 @@ export default class FightUiHandler extends UiHandler {
messageHandler.bg.setVisible(false);
messageHandler.commandWindow.setVisible(false);
messageHandler.movesWindowContainer.setVisible(true);
- this.setCursor(this.getCursor());
+ const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon();
+ if (pokemon.battleSummonData.turnCount <= 1) {
+ this.setCursor(0);
+ } else {
+ this.setCursor(this.getCursor());
+ }
this.displayMoves();
-
return true;
}
diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts
index 6349d219827..adbb3089e5c 100644
--- a/src/ui/menu-ui-handler.ts
+++ b/src/ui/menu-ui-handler.ts
@@ -31,6 +31,7 @@ let wikiUrl = "https://wiki.pokerogue.net/start";
const discordUrl = "https://discord.gg/uWpTfdKG49";
const githubUrl = "https://github.com/pagefaultgames/pokerogue";
const redditUrl = "https://www.reddit.com/r/pokerogue";
+const donateUrl = "https://github.com/sponsors/patapancakes";
export default class MenuUiHandler extends MessageUiHandler {
private readonly textPadding = 8;
@@ -369,7 +370,16 @@ export default class MenuUiHandler extends MessageUiHandler {
return true;
},
keepOpen: true
- }];
+ },
+ {
+ label: i18next.t("menuUiHandler:donate"),
+ handler: () => {
+ window.open(donateUrl, "_blank")?.focus();
+ return true;
+ },
+ keepOpen: true
+ }
+ ];
if (!bypassLogin && loggedInUser?.hasAdminRole) {
communityOptions.push({
label: "Admin",
diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts
index 6e9a33df5d8..ca5d27f96a4 100644
--- a/src/ui/modifier-select-ui-handler.ts
+++ b/src/ui/modifier-select-ui-handler.ts
@@ -257,6 +257,9 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
if (this.scene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) {
this.setRowCursor(0);
this.setCursor(2);
+ } else if ((this.scene.shopCursorTarget === ShopCursorTarget.SHOP) && this.scene.gameMode.hasNoShop) {
+ this.setRowCursor(ShopCursorTarget.REWARDS);
+ this.setCursor(0);
} else {
this.setRowCursor(this.scene.shopCursorTarget);
this.setCursor(0);
diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts
index e7c4069c16e..98a19402a2b 100644
--- a/src/ui/party-ui-handler.ts
+++ b/src/ui/party-ui-handler.ts
@@ -166,6 +166,8 @@ export default class PartyUiHandler extends MessageUiHandler {
private iconAnimHandler: PokemonIconAnimHandler;
+ private blockInput: boolean;
+
private static FilterAll = (_pokemon: PlayerPokemon) => null;
public static FilterNonFainted = (pokemon: PlayerPokemon) => {
@@ -309,7 +311,7 @@ export default class PartyUiHandler extends MessageUiHandler {
this.partyContainer.setVisible(true);
this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? "_double" : ""}`);
this.populatePartySlots();
- this.setCursor(this.cursor < 6 ? this.cursor : 0);
+ this.setCursor(0);
return true;
}
@@ -317,7 +319,7 @@ export default class PartyUiHandler extends MessageUiHandler {
processInput(button: Button): boolean {
const ui = this.getUi();
- if (this.pendingPrompt) {
+ if (this.pendingPrompt || this.blockInput) {
return false;
}
@@ -485,7 +487,9 @@ export default class PartyUiHandler extends MessageUiHandler {
this.clearOptions();
ui.playSelect();
if (this.cursor >= this.scene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) {
+ this.blockInput = true;
this.showText(i18next.t("partyUiHandler:releaseConfirmation", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => {
+ this.blockInput = false;
ui.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.PARTY);
this.doRelease(this.cursor);
@@ -1317,16 +1321,13 @@ class PartySlot extends Phaser.GameObjects.Container {
this.slotHpOverlay.setVisible(false);
this.slotHpText.setVisible(false);
let slotTmText: string;
- switch (true) {
- case (this.pokemon.compatibleTms.indexOf(tmMoveId) === -1):
- slotTmText = i18next.t("partyUiHandler:notAble");
- break;
- case (this.pokemon.getMoveset().filter(m => m?.moveId === tmMoveId).length > 0):
+
+ if (this.pokemon.getMoveset().filter(m => m?.moveId === tmMoveId).length > 0) {
slotTmText = i18next.t("partyUiHandler:learned");
- break;
- default:
+ } else if (this.pokemon.compatibleTms.indexOf(tmMoveId) === -1) {
+ slotTmText = i18next.t("partyUiHandler:notAble");
+ } else {
slotTmText = i18next.t("partyUiHandler:able");
- break;
}
this.slotDescriptionLabel.setText(slotTmText);
diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts
new file mode 100644
index 00000000000..f8a9adced36
--- /dev/null
+++ b/src/ui/pokemon-hatch-info-container.ts
@@ -0,0 +1,189 @@
+import PokemonInfoContainer from "./pokemon-info-container";
+import BattleScene from "../battle-scene";
+import { Gender } from "../data/gender";
+import { Type } from "../data/type";
+import * as Utils from "../utils";
+import { TextStyle, addTextObject } from "./text";
+import { speciesEggMoves } from "#app/data/egg-moves";
+import { allMoves } from "#app/data/move";
+import { Species } from "#app/enums/species";
+import { getEggTierForSpecies } from "#app/data/egg";
+import { starterColors } from "../battle-scene";
+import { argbFromRgba } from "@material/material-color-utilities";
+import { EggHatchData } from "#app/data/egg-hatch-data";
+import { PlayerPokemon } from "#app/field/pokemon";
+import { getPokemonSpeciesForm } from "#app/data/pokemon-species";
+
+/**
+ * Class for the hatch info summary of each pokemon
+ * Holds an info container as well as an additional egg sprite, name, egg moves and main sprite
+ */
+export default class PokemonHatchInfoContainer extends PokemonInfoContainer {
+ private currentPokemonSprite: Phaser.GameObjects.Sprite;
+ private pokemonNumberText: Phaser.GameObjects.Text;
+ private pokemonNameText: Phaser.GameObjects.Text;
+ private pokemonEggMovesContainer: Phaser.GameObjects.Container;
+ private pokemonEggMoveContainers: Phaser.GameObjects.Container[];
+ private pokemonEggMoveBgs: Phaser.GameObjects.NineSlice[];
+ private pokemonEggMoveLabels: Phaser.GameObjects.Text[];
+ private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
+ private pokemonListContainer: Phaser.GameObjects.Container;
+ private pokemonCandyIcon: Phaser.GameObjects.Sprite;
+ private pokemonCandyOverlayIcon: Phaser.GameObjects.Sprite;
+ private pokemonCandyCountText: Phaser.GameObjects.Text;
+
+ constructor(scene: BattleScene, listContainer : Phaser.GameObjects.Container, x: number = 115, y: number = 9,) {
+ super(scene, x, y);
+ this.pokemonListContainer = listContainer;
+
+ }
+ setup(): void {
+ super.setup();
+ super.changeToEggSummaryLayout();
+
+ this.currentPokemonSprite = this.scene.add.sprite(54, 80, "pkmn__sub");
+ this.currentPokemonSprite.setScale(0.8);
+ this.currentPokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
+ this.pokemonListContainer.add(this.currentPokemonSprite);
+
+ // setup name and number
+ this.pokemonNumberText = addTextObject(this.scene, 80, 107.5, "0000", TextStyle.SUMMARY, {fontSize: 74});
+ this.pokemonNumberText.setOrigin(0, 0);
+ this.pokemonListContainer.add(this.pokemonNumberText);
+
+ this.pokemonNameText = addTextObject(this.scene, 7, 107.5, "", TextStyle.SUMMARY, {fontSize: 74});
+ this.pokemonNameText.setOrigin(0, 0);
+ this.pokemonListContainer.add(this.pokemonNameText);
+
+ // setup egg icon and candy count
+ this.pokemonHatchedIcon = this.scene.add.sprite(-5, 90, "egg_icons");
+ this.pokemonHatchedIcon.setOrigin(0, 0.2);
+ this.pokemonHatchedIcon.setScale(0.8);
+ this.pokemonListContainer.add(this.pokemonHatchedIcon);
+
+ this.pokemonCandyIcon = this.scene.add.sprite(4.5, 40, "candy");
+ this.pokemonCandyIcon.setScale(0.5);
+ this.pokemonCandyIcon.setOrigin(0, 0);
+ this.pokemonListContainer.add(this.pokemonCandyIcon);
+
+ this.pokemonCandyOverlayIcon = this.scene.add.sprite(4.5, 40, "candy_overlay");
+ this.pokemonCandyOverlayIcon.setScale(0.5);
+ this.pokemonCandyOverlayIcon.setOrigin(0, 0);
+ this.pokemonListContainer.add(this.pokemonCandyOverlayIcon);
+
+ this.pokemonCandyCountText = addTextObject(this.scene, 14, 40, "x0", TextStyle.SUMMARY, { fontSize: "56px" });
+ this.pokemonCandyCountText.setOrigin(0, 0);
+ this.pokemonListContainer.add(this.pokemonCandyCountText);
+
+ // setup egg moves
+ this.pokemonEggMoveContainers = [];
+ this.pokemonEggMoveBgs = [];
+ this.pokemonEggMoveLabels = [];
+ this.pokemonEggMovesContainer = this.scene.add.container(0, 200);
+ this.pokemonEggMovesContainer.setVisible(false);
+ this.pokemonEggMovesContainer.setScale(0.5);
+
+ for (let m = 0; m < 4; m++) {
+ const eggMoveContainer = this.scene.add.container(0, 0 + 6 * m);
+
+ const eggMoveBg = this.scene.add.nineslice(70, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2);
+ eggMoveBg.setOrigin(1, 0);
+
+ const eggMoveLabel = addTextObject(this.scene, 70 -eggMoveBg.width / 2, 0, "???", TextStyle.PARTY);
+ eggMoveLabel.setOrigin(0.5, 0);
+
+ this.pokemonEggMoveBgs.push(eggMoveBg);
+ this.pokemonEggMoveLabels.push(eggMoveLabel);
+
+ eggMoveContainer.add(eggMoveBg);
+ eggMoveContainer.add(eggMoveLabel);
+ eggMoveContainer.setScale(0.44);
+
+ this.pokemonEggMoveContainers.push(eggMoveContainer);
+
+ this.pokemonEggMovesContainer.add(eggMoveContainer);
+ }
+
+ super.add(this.pokemonEggMoveContainers);
+
+ }
+
+ /**
+ * Disable the sprite (and replace with substitute)
+ */
+ hideDisplayPokemon() {
+ this.currentPokemonSprite.setVisible(false);
+ }
+
+ /**
+ * Display a given pokemon sprite with animations
+ * assumes the specific pokemon sprite has already been loaded
+ */
+ displayPokemon(pokemon: PlayerPokemon) {
+ const species = pokemon.species;
+ const female = pokemon.gender === Gender.FEMALE;
+ const formIndex = pokemon.formIndex;
+ const shiny = pokemon.shiny;
+ const variant = pokemon.variant;
+ this.currentPokemonSprite.setVisible(false);
+ species.loadAssets(this.scene, female, formIndex, shiny, variant, true).then(() => {
+
+ getPokemonSpeciesForm(species.speciesId, pokemon.formIndex).cry(this.scene);
+ this.currentPokemonSprite.play(species.getSpriteKey(female, formIndex, shiny, variant));
+ this.currentPokemonSprite.setPipelineData("shiny", shiny);
+ this.currentPokemonSprite.setPipelineData("variant", variant);
+ this.currentPokemonSprite.setPipelineData("spriteKey", species.getSpriteKey(female, formIndex, shiny, variant));
+ this.currentPokemonSprite.setVisible(true);
+ });
+ }
+
+ /**
+ * Updates the info container with the appropriate dex data and starter entry from the hatchInfo
+ * Also updates the displayed name, number, egg moves and main animated sprite for the pokemon
+ * @param hatchInfo The EggHatchData of the pokemon / new hatch to show
+ */
+ showHatchInfo(hatchInfo: EggHatchData) {
+ this.pokemonEggMovesContainer.setVisible(true);
+
+ const pokemon = hatchInfo.pokemon;
+ const species = pokemon.species;
+ this.displayPokemon(pokemon);
+
+ super.show(pokemon, false, 1, hatchInfo.getDex(), hatchInfo.getStarterEntry(), true);
+ const colorScheme = starterColors[species.speciesId];
+
+ this.pokemonCandyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0])));
+ this.pokemonCandyIcon.setVisible(true);
+ this.pokemonCandyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1])));
+ this.pokemonCandyOverlayIcon.setVisible(true);
+ this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`);
+ this.pokemonCandyCountText.setVisible(true);
+
+ this.pokemonNumberText.setText(Utils.padInt(species.speciesId, 4));
+ this.pokemonNameText.setText(species.name);
+
+ const hasEggMoves = species && speciesEggMoves.hasOwnProperty(species.speciesId);
+
+ for (let em = 0; em < 4; em++) {
+ const eggMove = hasEggMoves ? allMoves[speciesEggMoves[species.speciesId][em]] : null;
+ const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em);
+ this.pokemonEggMoveBgs[em].setFrame(Type[eggMove ? eggMove.type : Type.UNKNOWN].toString().toLowerCase());
+
+ this.pokemonEggMoveLabels[em].setText(eggMove && eggMoveUnlocked ? eggMove.name : "???");
+ if (!(eggMove && hatchInfo.starterDataEntryBeforeUpdate.eggMoves & Math.pow(2, em)) && eggMoveUnlocked) {
+ this.pokemonEggMoveLabels[em].setText("(+) " + eggMove.name);
+ }
+ }
+
+ // will always have at least one egg move
+ this.pokemonEggMovesContainer.setVisible(true);
+
+ if (species.speciesId === Species.MANAPHY || species.speciesId === Species.PHIONE) {
+ this.pokemonHatchedIcon.setFrame("manaphy");
+ } else {
+ this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species));
+ }
+
+ }
+
+}
diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts
index edb85ecff7a..49bfd4d7293 100644
--- a/src/ui/pokemon-info-container.ts
+++ b/src/ui/pokemon-info-container.ts
@@ -6,7 +6,7 @@ import { getNatureName } from "../data/nature";
import { Type } from "../data/type";
import Pokemon from "../field/pokemon";
import i18next from "i18next";
-import { DexAttr } from "../system/game-data";
+import { DexAttr, DexEntry, StarterDataEntry } from "../system/game-data";
import * as Utils from "../utils";
import ConfirmUiHandler from "./confirm-ui-handler";
import { StatsContainer } from "./stats-container";
@@ -24,7 +24,7 @@ const languageSettings: { [key: string]: LanguageSetting } = {
infoContainerTextSize: "64px"
},
"de": {
- infoContainerTextSize: "64px"
+ infoContainerTextSize: "64px",
},
"es": {
infoContainerTextSize: "64px"
@@ -63,6 +63,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
private pokemonMovesContainers: Phaser.GameObjects.Container[];
private pokemonMoveBgs: Phaser.GameObjects.NineSlice[];
private pokemonMoveLabels: Phaser.GameObjects.Text[];
+ private infoBg;
private numCharsBeforeCutoff = 16;
@@ -83,9 +84,9 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
const currentLanguage = i18next.resolvedLanguage!; // TODO: is this bang correct?
const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage?.includes(lang))!; // TODO: is this bang correct?
const textSettings = languageSettings[langSettingKey];
- const infoBg = addWindow(this.scene, 0, 0, this.infoWindowWidth, 132);
- infoBg.setOrigin(0.5, 0.5);
- infoBg.setName("window-info-bg");
+ this.infoBg = addWindow(this.scene, 0, 0, this.infoWindowWidth, 132);
+ this.infoBg.setOrigin(0.5, 0.5);
+ this.infoBg.setName("window-info-bg");
this.pokemonMovesContainer = this.scene.add.container(6, 14);
this.pokemonMovesContainer.setName("pkmn-moves");
@@ -133,7 +134,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
this.statsContainer = new StatsContainer(this.scene, -48, -64, true);
- this.add(infoBg);
+ this.add(this.infoBg);
this.add(this.statsContainer);
// The position should be set per language
@@ -207,9 +208,16 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
this.setVisible(false);
}
- show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1): Promise {
+ show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1, dexEntry?: DexEntry, starterEntry?: StarterDataEntry, eggInfo = false): Promise {
return new Promise(resolve => {
- const caughtAttr = BigInt(pokemon.scene.gameData.dexData[pokemon.species.speciesId].caughtAttr);
+ if (!dexEntry) {
+ dexEntry = pokemon.scene.gameData.dexData[pokemon.species.speciesId];
+ }
+ if (!starterEntry) {
+ starterEntry = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()];
+ }
+
+ const caughtAttr = BigInt(dexEntry.caughtAttr);
if (pokemon.gender > Gender.GENDERLESS) {
this.pokemonGenderText.setText(getGenderSymbol(pokemon.gender));
this.pokemonGenderText.setColor(getGenderColor(pokemon.gender));
@@ -268,7 +276,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
const opponentPokemonAbilityIndex = (opponentPokemonOneNormalAbility && pokemon.abilityIndex === 1) ? 2 : pokemon.abilityIndex;
const opponentPokemonAbilityAttr = 1 << opponentPokemonAbilityIndex;
- const rootFormHasHiddenAbility = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr & opponentPokemonAbilityAttr;
+ const rootFormHasHiddenAbility = starterEntry.abilityAttr & opponentPokemonAbilityAttr;
if (!rootFormHasHiddenAbility) {
this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme));
@@ -280,7 +288,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
this.pokemonNatureText.setText(getNatureName(pokemon.getNature(), true, false, false, this.scene.uiTheme));
- const dexNatures = pokemon.scene.gameData.dexData[pokemon.species.speciesId].natureAttr;
+ const dexNatures = dexEntry.natureAttr;
const newNature = 1 << (pokemon.nature + 1);
if (!(dexNatures & newNature)) {
@@ -324,31 +332,31 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
}
const starterSpeciesId = pokemon.species.getRootSpeciesId();
- const originalIvs: integer[] | null = this.scene.gameData.dexData[starterSpeciesId].caughtAttr
- ? this.scene.gameData.dexData[starterSpeciesId].ivs
- : null;
+ const originalIvs: integer[] | null = eggInfo ? (dexEntry.caughtAttr ? dexEntry.ivs : null) : (this.scene.gameData.dexData[starterSpeciesId].caughtAttr
+ ? this.scene.gameData.dexData[starterSpeciesId].ivs : null);
this.statsContainer.updateIvs(pokemon.ivs, originalIvs!); // TODO: is this bang correct?
-
- this.scene.tweens.add({
- targets: this,
- duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)),
- ease: "Cubic.easeInOut",
- x: this.initialX - this.infoWindowWidth,
- onComplete: () => {
- resolve();
- }
- });
-
- if (showMoves) {
+ if (!eggInfo) {
this.scene.tweens.add({
- delay: Utils.fixedInt(Math.floor(325 / speedMultiplier)),
- targets: this.pokemonMovesContainer,
- duration: Utils.fixedInt(Math.floor(325 / speedMultiplier)),
+ targets: this,
+ duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)),
ease: "Cubic.easeInOut",
- x: this.movesContainerInitialX - 57,
- onComplete: () => resolve()
+ x: this.initialX - this.infoWindowWidth,
+ onComplete: () => {
+ resolve();
+ }
});
+
+ if (showMoves) {
+ this.scene.tweens.add({
+ delay: Utils.fixedInt(Math.floor(325 / speedMultiplier)),
+ targets: this.pokemonMovesContainer,
+ duration: Utils.fixedInt(Math.floor(325 / speedMultiplier)),
+ ease: "Cubic.easeInOut",
+ x: this.movesContainerInitialX - 57,
+ onComplete: () => resolve()
+ });
+ }
}
for (let m = 0; m < 4; m++) {
@@ -364,6 +372,36 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
});
}
+ changeToEggSummaryLayout() {
+ // The position should be set per language (and shifted for new layout)
+ const currentLanguage = i18next.resolvedLanguage!; // TODO: is this bang correct?
+ const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage?.includes(lang))!; // TODO: is this bang correct?
+ const textSettings = languageSettings[langSettingKey];
+
+ const eggLabelTextOffset = 43;
+ const infoContainerLabelXPos = (textSettings?.infoContainerLabelXPos || -18) + eggLabelTextOffset;
+ const infoContainerTextXPos = (textSettings?.infoContainerTextXPos || -14) + eggLabelTextOffset;
+
+ this.x = this.initialX - this.infoWindowWidth;
+
+ this.pokemonGenderText.setPosition(89, -2);
+ this.pokemonGenderNewText.setPosition(79, -2);
+ this.pokemonShinyIcon.setPosition(82, 87);
+ this.pokemonShinyNewIcon.setPosition(72, 87);
+
+ this.pokemonFormLabelText.setPosition(infoContainerLabelXPos, 152);
+ this.pokemonFormText.setPosition(infoContainerTextXPos, 152);
+ this.pokemonAbilityLabelText.setPosition(infoContainerLabelXPos, 110);
+ this.pokemonAbilityText.setPosition(infoContainerTextXPos, 110);
+ this.pokemonNatureLabelText.setPosition(infoContainerLabelXPos, 125);
+ this.pokemonNatureText.setPosition(infoContainerTextXPos, 125);
+
+ this.statsContainer.setScale(0.7);
+ this.statsContainer.setPosition(30, -3);
+ this.infoBg.setVisible(false);
+ this.pokemonMovesContainer.setVisible(false);
+ }
+
makeRoomForConfirmUi(speedMultiplier: number = 1, fromCatch: boolean = false): Promise {
const xPosition = fromCatch ? this.initialX - this.infoWindowWidth - 65 : this.initialX - this.infoWindowWidth - ConfirmUiHandler.windowWidth;
return new Promise(resolve => {
diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts
index 25210277edc..7a183a11d29 100644
--- a/src/ui/run-info-ui-handler.ts
+++ b/src/ui/run-info-ui-handler.ts
@@ -13,8 +13,9 @@ import { BattleType } from "../battle";
import { TrainerVariant } from "../field/trainer";
import { Challenges } from "#enums/challenges";
import { getLuckString, getLuckTextTint } from "../modifier/modifier-type";
-import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle.js";
+import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle";
import { Type, getTypeRgb } from "../data/type";
+import { TypeColor, TypeShadow } from "#app/enums/color";
import { getNatureStatMultiplier, getNatureName } from "../data/nature";
import { getVariantTint } from "#app/data/variant";
import { PokemonHeldItemModifier, TerastallizeModifier } from "../modifier/modifier";
@@ -373,15 +374,16 @@ export default class RunInfoUiHandler extends UiHandler {
break;
case GameModes.CHALLENGE:
modeText.appendText(`${i18next.t("gameMode:challenge")}`, false);
- modeText.appendText(`\t\t${i18next.t("runHistory:challengeRules")}: `);
+ modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `);
+ modeText.setWrapMode(1); // wrap by word
+ modeText.setWrapWidth(500);
const rules: string[] = this.challengeParser();
if (rules) {
for (let i = 0; i < rules.length; i++) {
- const newline = i > 0 && i%2 === 0;
if (i > 0) {
- modeText.appendText(" + ", newline);
+ modeText.appendText(" + ", false);
}
- modeText.appendText(rules[i], newline);
+ modeText.appendText(rules[i], false);
}
}
break;
@@ -470,14 +472,18 @@ export default class RunInfoUiHandler extends UiHandler {
rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`));
break;
case Challenges.SINGLE_TYPE:
- rules.push(i18next.t(`pokemonInfo:Type.${Type[this.runInfo.challenges[i].value-1]}` as const));
+ const typeRule = Type[this.runInfo.challenges[i].value-1];
+ const typeTextColor = `[color=${TypeColor[typeRule]}]`;
+ const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`;
+ const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)!+"[/color]"+"[/shadow]";
+ rules.push(typeText);
break;
case Challenges.FRESH_START:
rules.push(i18next.t("challenges:freshStart.name"));
break;
case Challenges.INVERSE_BATTLE:
//
- rules.push(i18next.t("challenges:inverseBattle.shortName").split("").reverse().join(""));
+ rules.push(i18next.t("challenges:inverseBattle.shortName"));
break;
}
}
@@ -628,7 +634,7 @@ export default class RunInfoUiHandler extends UiHandler {
// Pokemon Held Items - not displayed by default
// Endless/Endless Spliced have a different scale because Pokemon tend to accumulate more items in these runs.
const heldItemsScale = (this.runInfo.gameMode === GameModes.SPLICED_ENDLESS || this.runInfo.gameMode === GameModes.ENDLESS) ? 0.25 : 0.5;
- const heldItemsContainer = this.scene.add.container(-82, 6);
+ const heldItemsContainer = this.scene.add.container(-82, 2);
const heldItemsList : PokemonHeldItemModifier[] = [];
if (this.runInfo.modifiers.length) {
for (const m of this.runInfo.modifiers) {
@@ -648,6 +654,9 @@ export default class RunInfoUiHandler extends UiHandler {
break;
}
const itemIcon = item?.getIcon(this.scene, true);
+ if (item?.stackCount < item?.getMaxHeldItemCount(pokemon) && itemIcon.list[1] instanceof Phaser.GameObjects.BitmapText) {
+ itemIcon.list[1].clearTint();
+ }
itemIcon.setScale(heldItemsScale);
itemIcon.setPosition((index%19) * 10, row * 10);
heldItemsContainer.add(itemIcon);
diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts
index 267a62104e3..6b75c46bd45 100644
--- a/src/ui/starter-select-ui-handler.ts
+++ b/src/ui/starter-select-ui-handler.ts
@@ -32,19 +32,20 @@ import {SettingKeyboard} from "#app/system/settings/settings-keyboard";
import {Passive as PassiveAttr} from "#enums/passive";
import * as Challenge from "../data/challenge";
import MoveInfoOverlay from "./move-info-overlay";
-import { getEggTierForSpecies } from "#app/data/egg.js";
+import { getEggTierForSpecies } from "#app/data/egg";
import { Device } from "#enums/devices";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import {Button} from "#enums/buttons";
-import { EggSourceType } from "#app/enums/egg-source-types.js";
+import { EggSourceType } from "#app/enums/egg-source-types";
import AwaitableUiHandler from "./awaitable-ui-handler";
-import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType } from "./dropdown";
+import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "./dropdown";
import { StarterContainer } from "./starter-container";
import { DropDownColumn, FilterBar } from "./filter-bar";
import { ScrollBar } from "./scroll-bar";
-import { SelectChallengePhase } from "#app/phases/select-challenge-phase.js";
-import { TitlePhase } from "#app/phases/title-phase.js";
+import { SelectChallengePhase } from "#app/phases/select-challenge-phase";
+import { TitlePhase } from "#app/phases/title-phase";
+import { Abilities } from "#app/enums/abilities";
export type StarterSelectCallback = (starters: Starter[]) => void;
@@ -262,6 +263,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
private pokemonHatchedCountText: Phaser.GameObjects.Text;
private pokemonShinyIcon: Phaser.GameObjects.Sprite;
+ private pokemonPassiveDisabledIcon: Phaser.GameObjects.Sprite;
+ private pokemonPassiveLockedIcon: Phaser.GameObjects.Sprite;
private instructionsContainer: Phaser.GameObjects.Container;
private filterInstructionsContainer: Phaser.GameObjects.Container;
@@ -501,11 +504,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
// sort filter
const sortOptions = [
- new DropDownOption(this.scene, 0, new DropDownLabel(i18next.t("filterBar:sortByNumber"), undefined, DropDownState.ON)),
- new DropDownOption(this.scene, 1, new DropDownLabel(i18next.t("filterBar:sortByCost"))),
- new DropDownOption(this.scene, 2, new DropDownLabel(i18next.t("filterBar:sortByCandies"))),
- new DropDownOption(this.scene, 3, new DropDownLabel(i18next.t("filterBar:sortByIVs"))),
- new DropDownOption(this.scene, 4, new DropDownLabel(i18next.t("filterBar:sortByName")))
+ new DropDownOption(this.scene, SortCriteria.NUMBER, new DropDownLabel(i18next.t("filterBar:sortByNumber"), undefined, DropDownState.ON)),
+ new DropDownOption(this.scene, SortCriteria.COST, new DropDownLabel(i18next.t("filterBar:sortByCost"))),
+ new DropDownOption(this.scene, SortCriteria.CANDY, new DropDownLabel(i18next.t("filterBar:sortByCandies"))),
+ new DropDownOption(this.scene, SortCriteria.IV, new DropDownLabel(i18next.t("filterBar:sortByIVs"))),
+ new DropDownOption(this.scene, SortCriteria.NAME, new DropDownLabel(i18next.t("filterBar:sortByName")))
];
this.filterBar.addFilter(DropDownColumn.SORT, i18next.t("filterBar:sortFilter"), new DropDown(this.scene, 0, 0, sortOptions, this.updateStarters, DropDownType.SINGLE));
this.filterBarContainer.add(this.filterBar);
@@ -573,6 +576,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonPassiveText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonPassiveText);
+ this.pokemonPassiveDisabledIcon = this.scene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_stop");
+ this.pokemonPassiveDisabledIcon.setOrigin(0, 0.5);
+ this.pokemonPassiveDisabledIcon.setScale(0.35);
+ this.pokemonPassiveDisabledIcon.setVisible(false);
+ this.starterSelectContainer.add(this.pokemonPassiveDisabledIcon);
+
+ this.pokemonPassiveLockedIcon = this.scene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_lock");
+ this.pokemonPassiveLockedIcon.setOrigin(0, 0.5);
+ this.pokemonPassiveLockedIcon.setScale(0.42, 0.38);
+ this.pokemonPassiveLockedIcon.setVisible(false);
+ this.starterSelectContainer.add(this.pokemonPassiveLockedIcon);
+
this.pokemonNatureLabelText = addTextObject(this.scene, 6, 145 + starterInfoYOffset, i18next.t("starterSelectUiHandler:nature"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize });
this.pokemonNatureLabelText.setOrigin(0, 0);
this.pokemonNatureLabelText.setVisible(false);
@@ -733,7 +748,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonShinyIcon = this.scene.add.sprite(14, 76, "shiny_icons");
this.pokemonShinyIcon.setOrigin(0.15, 0.2);
this.pokemonShinyIcon.setScale(1);
- this.pokemonCaughtHatchedContainer.add ((this.pokemonShinyIcon));
+ this.pokemonCaughtHatchedContainer.add(this.pokemonShinyIcon);
this.pokemonHatchedCountText = addTextObject(this.scene, 24, 19, "0", TextStyle.SUMMARY_ALT);
this.pokemonHatchedCountText.setOrigin(0, 0);
@@ -1838,10 +1853,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
switch (button) {
case Button.CYCLE_SHINY:
if (this.canCycleShiny) {
- const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : props.variant;
- starterAttributes.shiny = starterAttributes.shiny ? !starterAttributes.shiny : true;
- this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, props.shiny ? 0 : newVariant, undefined, undefined);
+ starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false;
+
if (starterAttributes.shiny) {
+ // Change to shiny, we need to get the proper default variant
+ const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId));
+ const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant;
+ this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined);
+
this.scene.playSound("se/sparkle");
// Set the variant label to the shiny tint
const tint = getVariantTint(newVariant);
@@ -1849,6 +1868,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonShinyIcon.setTint(tint);
this.pokemonShinyIcon.setVisible(true);
} else {
+ this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined);
this.pokemonShinyIcon.setVisible(false);
success = true;
}
@@ -2363,6 +2383,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
// First, ensure you have the caught attributes for the species else default to bigint 0
const caughtAttr = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0);
const starterData = this.scene.gameData.starterData[container.species.speciesId];
+ const isStarterProgressable = speciesEggMoves.hasOwnProperty(container.species.speciesId);
// Gen filter
const fitsGen = this.filterBar.getVals(DropDownColumn.GEN).includes(container.species.generation);
@@ -2398,7 +2419,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.ON) {
return isPassiveUnlocked;
} else if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.EXCLUDE) {
- return !isPassiveUnlocked;
+ return isStarterProgressable && !isPassiveUnlocked;
} else if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.UNLOCKABLE) {
return isPassiveUnlockable;
} else if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.OFF) {
@@ -2413,7 +2434,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.ON) {
return isCostReduced;
} else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.EXCLUDE) {
- return !isCostReduced;
+ return isStarterProgressable && !isCostReduced;
} else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.UNLOCKABLE) {
return isCostReductionUnlockable;
} else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.OFF) {
@@ -2450,12 +2471,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
// HA Filter
+ const speciesHasHiddenAbility = container.species.abilityHidden !== container.species.ability1 && container.species.abilityHidden !== Abilities.NONE;
const hasHA = starterData.abilityAttr & AbilityAttr.ABILITY_HIDDEN;
const fitsHA = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
if (misc.val === "HIDDEN_ABILITY" && misc.state === DropDownState.ON) {
return hasHA;
} else if (misc.val === "HIDDEN_ABILITY" && misc.state === DropDownState.EXCLUDE) {
- return !hasHA;
+ return speciesHasHiddenAbility && !hasHA;
} else if (misc.val === "HIDDEN_ABILITY" && misc.state === DropDownState.OFF) {
return true;
}
@@ -2467,7 +2489,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (misc.val === "EGG" && misc.state === DropDownState.ON) {
return isEggPurchasable;
} else if (misc.val === "EGG" && misc.state === DropDownState.EXCLUDE) {
- return !isEggPurchasable;
+ return isStarterProgressable && !isEggPurchasable;
} else if (misc.val === "EGG" && misc.state === DropDownState.OFF) {
return true;
}
@@ -2498,19 +2520,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
switch (sort.val) {
default:
break;
- case 0:
+ case SortCriteria.NUMBER:
return (a.species.speciesId - b.species.speciesId) * -sort.dir;
- case 1:
+ case SortCriteria.COST:
return (a.cost - b.cost) * -sort.dir;
- case 2:
+ case SortCriteria.CANDY:
const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount;
const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount;
return (candyCountA - candyCountB) * -sort.dir;
- case 3:
+ case SortCriteria.IV:
const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length;
const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length;
return (avgIVsA - avgIVsB) * -sort.dir;
- case 4:
+ case SortCriteria.NAME:
return a.species.name.localeCompare(b.species.name) * -sort.dir;
}
return 0;
@@ -2932,6 +2954,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
this.pokemonSprite.setVisible(false);
+ this.pokemonPassiveLabelText.setVisible(false);
+ this.pokemonPassiveText.setVisible(false);
+ this.pokemonPassiveDisabledIcon.setVisible(false);
+ this.pokemonPassiveLockedIcon.setVisible(false);
if (this.assetLoadCancelled) {
this.assetLoadCancelled.value = true;
@@ -3063,9 +3089,34 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonAbilityText.setShadowColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD, true));
const passiveAttr = this.scene.gameData.starterData[species.speciesId].passiveAttr;
- this.pokemonPassiveText.setText(passiveAttr & PassiveAttr.UNLOCKED ? passiveAttr & PassiveAttr.ENABLED ? allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]].name : i18next.t("starterSelectUiHandler:disabled") : i18next.t("starterSelectUiHandler:locked"));
- this.pokemonPassiveText.setColor(this.getTextColor(passiveAttr === (PassiveAttr.UNLOCKED | PassiveAttr.ENABLED) ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GRAY));
- this.pokemonPassiveText.setShadowColor(this.getTextColor(passiveAttr === (PassiveAttr.UNLOCKED | PassiveAttr.ENABLED) ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GRAY, true));
+ const passiveAbility = allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]];
+
+ if (passiveAbility) {
+ const isUnlocked = !!(passiveAttr & PassiveAttr.UNLOCKED);
+ const isEnabled = !!(passiveAttr & PassiveAttr.ENABLED);
+
+ const textStyle = isUnlocked && isEnabled ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GRAY;
+ const textAlpha = isUnlocked && isEnabled ? 1 : 0.5;
+
+ this.pokemonPassiveLabelText.setVisible(true);
+ this.pokemonPassiveLabelText.setColor(this.getTextColor(TextStyle.SUMMARY_ALT));
+ this.pokemonPassiveLabelText.setShadowColor(this.getTextColor(TextStyle.SUMMARY_ALT, true));
+ this.pokemonPassiveText.setVisible(true);
+ this.pokemonPassiveText.setText(passiveAbility.name);
+ this.pokemonPassiveText.setColor(this.getTextColor(textStyle));
+ this.pokemonPassiveText.setAlpha(textAlpha);
+ this.pokemonPassiveText.setShadowColor(this.getTextColor(textStyle, true));
+
+ const iconPosition = {
+ x: this.pokemonPassiveText.x + this.pokemonPassiveText.displayWidth + 1,
+ y: this.pokemonPassiveText.y + this.pokemonPassiveText.displayHeight / 2
+ };
+ this.pokemonPassiveDisabledIcon.setVisible(isUnlocked && !isEnabled);
+ this.pokemonPassiveDisabledIcon.setPosition(iconPosition.x, iconPosition.y);
+ this.pokemonPassiveLockedIcon.setVisible(!isUnlocked);
+ this.pokemonPassiveLockedIcon.setPosition(iconPosition.x, iconPosition.y);
+
+ }
this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true, false, this.scene.uiTheme));
@@ -3441,23 +3492,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
props += DexAttr.MALE;
}
/* This part is very similar to above, but instead of for gender, it checks for shiny within starter preferences.
- * If they're not there, it checks the caughtAttr for shiny only (i.e. SHINY === true && NON_SHINY === false)
+ * If they're not there, it enables shiny state by default if any shiny was caught
*/
- if (this.starterPreferences[speciesId]?.shiny || ((caughtAttr & DexAttr.SHINY) > 0n && (caughtAttr & DexAttr.NON_SHINY) === 0n)) {
+ if (this.starterPreferences[speciesId]?.shiny || ((caughtAttr & DexAttr.SHINY) > 0n && this.starterPreferences[speciesId]?.shiny !== false)) {
props += DexAttr.SHINY;
- if (this.starterPreferences[speciesId]?.variant) {
+ if (this.starterPreferences[speciesId]?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.variant)) * DexAttr.DEFAULT_VARIANT;
} else {
/* This calculates the correct variant if there's no starter preferences for it.
- * This gets the lowest tier variant that you've caught (in line with other mechanics) and adds it to the temp props
+ * This gets the highest tier variant that you've caught and adds it to the temp props
*/
- if ((caughtAttr & DexAttr.DEFAULT_VARIANT) > 0) {
- props += DexAttr.DEFAULT_VARIANT;
- }
- if ((caughtAttr & DexAttr.VARIANT_2) > 0) {
- props += DexAttr.VARIANT_2;
- } else if ((caughtAttr & DexAttr.VARIANT_3) > 0) {
+ if ((caughtAttr & DexAttr.VARIANT_3) > 0) {
props += DexAttr.VARIANT_3;
+ } else if ((caughtAttr & DexAttr.VARIANT_2) > 0) {
+ props += DexAttr.VARIANT_2;
+ } else {
+ props += DexAttr.DEFAULT_VARIANT;
}
}
} else {
diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts
index 2bd7099a2c5..c6e0ea3a71c 100644
--- a/src/ui/stats-container.ts
+++ b/src/ui/stats-container.ts
@@ -1,7 +1,8 @@
import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
import BattleScene from "../battle-scene";
-import { Stat, getStatName } from "../data/pokemon-stat";
import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text";
+import { PERMANENT_STATS, getStatKey } from "#app/enums/stat";
+import i18next from "i18next";
const ivChartSize = 24;
const ivChartStatCoordMultipliers = [[0, -1], [0.825, -0.5], [0.825, 0.5], [-0.825, -0.5], [-0.825, 0.5], [0, 1]];
@@ -53,16 +54,16 @@ export class StatsContainer extends Phaser.GameObjects.Container {
this.ivStatValueTexts = [];
- new Array(6).fill(null).map((_, i: integer) => {
- const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[i][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[i][1] * 1.325 - 4 + ivLabelOffset[i], getStatName(i as Stat), TextStyle.TOOLTIP_CONTENT);
+ for (const s of PERMANENT_STATS) {
+ const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[s][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[s][1] * 1.325 - 4 + ivLabelOffset[s], i18next.t(getStatKey(s)), TextStyle.TOOLTIP_CONTENT);
statLabel.setOrigin(0.5);
- this.ivStatValueTexts[i] = addBBCodeTextObject(this.scene, statLabel.x, statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT);
- this.ivStatValueTexts[i].setOrigin(0.5);
+ this.ivStatValueTexts[s] = addBBCodeTextObject(this.scene, statLabel.x, statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT);
+ this.ivStatValueTexts[s].setOrigin(0.5);
this.add(statLabel);
- this.add(this.ivStatValueTexts[i]);
- });
+ this.add(this.ivStatValueTexts[s]);
+ }
}
updateIvs(ivs: integer[], originalIvs?: integer[]): void {
diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts
index ea7b798f2bf..8ae72f08edd 100644
--- a/src/ui/summary-ui-handler.ts
+++ b/src/ui/summary-ui-handler.ts
@@ -11,7 +11,6 @@ import Move, { MoveCategory } from "../data/move";
import { getPokeballAtlasKey } from "../data/pokeball";
import { getGenderColor, getGenderSymbol } from "../data/gender";
import { getLevelRelExp, getLevelTotalExp } from "../data/exp";
-import { Stat, getStatName } from "../data/pokemon-stat";
import { PokemonHeldItemModifier } from "../modifier/modifier";
import { StatusEffect } from "../data/status-effect";
import { getBiomeName } from "../data/biomes";
@@ -19,10 +18,11 @@ import { Nature, getNatureName, getNatureStatMultiplier } from "../data/nature";
import { loggedInUser } from "../account";
import { Variant, getVariantTint } from "#app/data/variant";
import {Button} from "#enums/buttons";
-import { Ability } from "../data/ability.js";
+import { Ability } from "../data/ability";
import i18next from "i18next";
import {modifierSortFunc} from "../modifier/modifier";
import { PlayerGender } from "#enums/player-gender";
+import { Stat, PERMANENT_STATS, getStatKey } from "#app/enums/stat";
enum Page {
PROFILE,
@@ -836,10 +836,8 @@ export default class SummaryUiHandler extends UiHandler {
const statsContainer = this.scene.add.container(0, -pageBg.height);
pageContainer.add(statsContainer);
- const stats = Utils.getEnumValues(Stat) as Stat[];
-
- stats.forEach((stat, s) => {
- const statName = getStatName(stat);
+ PERMANENT_STATS.forEach((stat, s) => {
+ const statName = i18next.t(getStatKey(stat));
const rowIndex = s % 3;
const colIndex = Math.floor(s / 3);
@@ -850,7 +848,7 @@ export default class SummaryUiHandler extends UiHandler {
statsContainer.add(statLabel);
const statValueText = stat !== Stat.HP
- ? Utils.formatStat(this.pokemon?.stats[s]!) // TODO: is this bang correct?
+ ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct?
: `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct?
const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT);
diff --git a/src/ui/ui.ts b/src/ui/ui.ts
index 7108a8dd480..6c988b43043 100644
--- a/src/ui/ui.ts
+++ b/src/ui/ui.ts
@@ -49,6 +49,7 @@ import RenameFormUiHandler from "./rename-form-ui-handler";
import AdminUiHandler from "./admin-ui-handler";
import RunHistoryUiHandler from "./run-history-ui-handler";
import RunInfoUiHandler from "./run-info-ui-handler";
+import EggSummaryUiHandler from "./egg-summary-ui-handler";
import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler";
import AutoCompleteUiHandler from "./autocomplete-ui-handler";
@@ -66,6 +67,7 @@ export enum Mode {
STARTER_SELECT,
EVOLUTION_SCENE,
EGG_HATCH_SCENE,
+ EGG_HATCH_SUMMARY,
CONFIRM,
OPTION_SELECT,
MENU,
@@ -171,6 +173,7 @@ export default class UI extends Phaser.GameObjects.Container {
new StarterSelectUiHandler(scene),
new EvolutionSceneHandler(scene),
new EggHatchSceneHandler(scene),
+ new EggSummaryUiHandler(scene),
new ConfirmUiHandler(scene),
new OptionSelectUiHandler(scene),
new MenuUiHandler(scene),
@@ -317,10 +320,11 @@ export default class UI extends Phaser.GameObjects.Container {
if (i18next.exists(keyOrText) ) {
const i18nKey = keyOrText;
hasi18n = true;
+
text = i18next.t(i18nKey, { context: genderStr }); // override text with translation
// Skip dialogue if the player has enabled the option and the dialogue has been already seen
- if (battleScene.skipSeenDialogues && battleScene.gameData.getSeenDialogues()[i18nKey] === true) {
+ if (this.shouldSkipDialogue(i18nKey)) {
console.log(`Dialogue ${i18nKey} skipped`);
callback();
return;