updating branch

This commit is contained in:
PrabbyDD 2024-09-10 20:49:24 -07:00
commit f048cbed4d
527 changed files with 15875 additions and 20011 deletions

View File

@ -11,6 +11,8 @@ on:
branches: branches:
- main # Trigger on pull request events targeting the main branch - main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch - beta # Trigger on pull request events targeting the beta branch
merge_group:
types: [checks_requested]
jobs: jobs:
run-linters: # Define a job named "run-linters" run-linters: # Define a job named "run-linters"

View File

@ -8,6 +8,8 @@ on:
branches: branches:
- main - main
- beta - beta
merge_group:
types: [checks_requested]
jobs: jobs:
pages: pages:

View File

@ -0,0 +1,30 @@
name: Test Template
on:
workflow_call:
inputs:
project:
required: true
type: string
shard:
required: true
type: number
totalShards:
required: true
type: number
jobs:
test:
name: Shard ${{ inputs.shard }} of ${{ inputs.totalShards }}
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Node.js dependencies
run: npm ci
- name: Run tests
run: npx vitest --project ${{ inputs.project }} --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }}

View File

@ -11,23 +11,37 @@ on:
branches: branches:
- main # Trigger on pull request events targeting the main branch - main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch - beta # Trigger on pull request events targeting the beta branch
merge_group:
types: [checks_requested]
jobs: jobs:
run-tests: # Define a job named "run-tests" pre-test:
name: Run tests # Human-readable name for the job name: Run Pre-test
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job runs-on: ubuntu-latest
steps: steps:
- name: Check out Git repository # Step to check out the repository - name: Check out Git repository
uses: actions/checkout@v4 # Use the checkout action version 4 uses: actions/checkout@v4
- name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4
with: with:
node-version: 20 # Specify Node.js version 20 path: tests-action
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Node.js dependencies
working-directory: tests-action
run: npm ci
- name: Run Pre-test
working-directory: tests-action
run: npx vitest run --project pre ${{ !runner.debug && '--silent' || '' }}
- name: Install Node.js dependencies # Step to install Node.js dependencies run-tests:
run: npm ci # Use 'npm ci' to install dependencies name: Run Tests
needs: [pre-test]
- name: tests # Step to run tests strategy:
run: npm run test:silent matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
uses: ./.github/workflows/test-shard-template.yml
with:
project: main
shard: ${{ matrix.shard }}
totalShards: 10

View File

@ -4,7 +4,8 @@ import { fileURLToPath } from 'url';
/** /**
* This script creates a test boilerplate file for a move or ability. * 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} type - The type of test to create. Either "move", "ability",
* or "item".
* @param {string} fileName - The name of the file to create. * @param {string} fileName - The name of the file to create.
* @example npm run create-test move tackle * @example npm run create-test move tackle
*/ */
@ -19,12 +20,15 @@ const type = args[0]; // "move" or "ability"
let fileName = args[1]; // The file name let fileName = args[1]; // The file name
if (!type || !fileName) { if (!type || !fileName) {
console.error('Please provide both a type ("move" or "ability") and a file name.'); console.error('Please provide a type ("move", "ability", or "item") and a file name.');
process.exit(1); process.exit(1);
} }
// Convert fileName from to snake_case if camelCase is given // Convert fileName from kebab-case or camelCase to snake_case
fileName = fileName.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); fileName = fileName
.replace(/-+/g, '_') // Convert kebab-case (dashes) to underscores
.replace(/([a-z])([A-Z])/g, '$1_$2') // Convert camelCase to snake_case
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case // Format the description for the test case
const formattedName = fileName const formattedName = fileName
@ -40,8 +44,11 @@ if (type === 'move') {
} else if (type === 'ability') { } else if (type === 'ability') {
dir = path.join(__dirname, 'src', 'test', 'abilities'); dir = path.join(__dirname, 'src', 'test', 'abilities');
description = `Abilities - ${formattedName}`; description = `Abilities - ${formattedName}`;
} else if (type === "item") {
dir = path.join(__dirname, 'src', 'test', 'items');
description = `Items - ${formattedName}`;
} else { } else {
console.error('Invalid type. Please use "move" or "ability".'); console.error('Invalid type. Please use "move", "ability", or "item".');
process.exit(1); process.exit(1);
} }
@ -60,10 +67,11 @@ if (fs.existsSync(filePath)) {
// Define the content template // Define the content template
const content = `import { Abilities } from "#enums/abilities"; const content = `import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
describe("${description}", () => { describe("${description}", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -83,14 +91,15 @@ describe("${description}", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override game.override
.moveset([Moves.SPLASH])
.battleType("single") .battleType("single")
.enemyAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(SPLASH_ONLY); .enemyMoveset(Moves.SPLASH);
}); });
it("test case", async () => { it("test case", async () => {
// await game.classicMode.startBattle(); // await game.classicMode.startBattle([Species.MAGIKARP]);
// game.move.select(); // game.move.select(Moves.SPLASH);
}, TIMEOUT); }, TIMEOUT);
}); });
`; `;

View File

@ -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**. 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)})$ $\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). 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$ $\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$

View File

@ -1,7 +1,7 @@
import tseslint from '@typescript-eslint/eslint-plugin'; import tseslint from '@typescript-eslint/eslint-plugin';
import stylisticTs from '@stylistic/eslint-plugin-ts' import stylisticTs from '@stylistic/eslint-plugin-ts'
import parser from '@typescript-eslint/parser'; import parser from '@typescript-eslint/parser';
// import imports from 'eslint-plugin-import'; // Disabled due to not being compatible with eslint v9 import importX from 'eslint-plugin-import-x';
export default [ export default [
{ {
@ -11,7 +11,7 @@ export default [
parser: parser parser: parser
}, },
plugins: { plugins: {
// imports: imports.configs.recommended // Disabled due to not being compatible with eslint v9 "import-x": importX,
'@stylistic/ts': stylisticTs, '@stylistic/ts': stylisticTs,
'@typescript-eslint': tseslint '@typescript-eslint': tseslint
}, },
@ -39,7 +39,8 @@ export default [
}], }],
"space-before-blocks": ["error", "always"], // Enforces a space before blocks "space-before-blocks": ["error", "always"], // Enforces a space before blocks
"keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords "keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords
"comma-spacing": ["error", { "before": false, "after": true }] // Enforces spacing after comma "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma
"import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json
} }
} }
] ]

View File

@ -2,6 +2,15 @@ pre-commit:
parallel: true parallel: true
commands: commands:
eslint: eslint:
glob: '*.{js,jsx,ts,tsx}' glob: "*.{js,jsx,ts,tsx}"
run: npx eslint --fix {staged_files} run: npx eslint --fix {staged_files}
stage_fixed: true stage_fixed: true
skip:
- merge
- rebase
pre-push:
commands:
eslint:
glob: "*.{js,ts,jsx,tsx}"
run: npx eslint --fix {push_files}

200
package-lock.json generated
View File

@ -28,6 +28,7 @@
"@vitest/coverage-istanbul": "^2.0.4", "@vitest/coverage-istanbul": "^2.0.4",
"dependency-cruiser": "^16.3.10", "dependency-cruiser": "^16.3.10",
"eslint": "^9.7.0", "eslint": "^9.7.0",
"eslint-plugin-import-x": "^4.2.1",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"lefthook": "^1.6.12", "lefthook": "^1.6.12",
"phaser3spectorjs": "^0.0.8", "phaser3spectorjs": "^0.0.8",
@ -2505,6 +2506,19 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"esutils": "^2.0.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2687,6 +2701,155 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
"integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^3.2.7",
"is-core-module": "^2.13.0",
"resolve": "^1.22.4"
}
},
"node_modules/eslint-import-resolver-node/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/eslint-plugin-import-x": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.2.1.tgz",
"integrity": "sha512-WWi2GedccIJa0zXxx3WDnTgouGQTtdYK1nhXMwywbqqAgB0Ov+p1pYBsWh3VaB0bvBOwLse6OfVII7jZD9xo5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/utils": "^8.1.0",
"debug": "^4.3.4",
"doctrine": "^3.0.0",
"eslint-import-resolver-node": "^0.3.9",
"get-tsconfig": "^4.7.3",
"is-glob": "^4.0.3",
"minimatch": "^9.0.3",
"semver": "^7.6.3",
"stable-hash": "^0.0.4",
"tslib": "^2.6.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/scope-manager": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz",
"integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/visitor-keys": "8.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/types": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz",
"integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz",
"integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/visitor-keys": "8.5.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/utils": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz",
"integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/types": "8.5.0",
"@typescript-eslint/typescript-estree": "8.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-import-x/node_modules/@typescript-eslint/visitor-keys": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz",
"integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.5.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
@ -3143,6 +3306,19 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/get-tsconfig": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz",
"integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -4854,6 +5030,16 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/reusify": { "node_modules/reusify": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@ -5069,6 +5255,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/stable-hash": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
"integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==",
"dev": true,
"license": "MIT"
},
"node_modules/stackback": { "node_modules/stackback": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@ -5460,6 +5653,13 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true,
"license": "0BSD"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -32,6 +32,7 @@
"@vitest/coverage-istanbul": "^2.0.4", "@vitest/coverage-istanbul": "^2.0.4",
"dependency-cruiser": "^16.3.10", "dependency-cruiser": "^16.3.10",
"eslint": "^9.7.0", "eslint": "^9.7.0",
"eslint-plugin-import-x": "^4.2.1",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",
"lefthook": "^1.6.12", "lefthook": "^1.6.12",
"phaser3spectorjs": "^0.0.8", "phaser3spectorjs": "^0.0.8",

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,604 +1,529 @@
{"frames": [ { "frames": {
"0.png": {
{ "frame": { "x": 12, "y": 44, "w": 12, "h": 11 },
"filename": "0.png",
"frame": {"x":0,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "1.png": {
"filename": "1.png", "frame": { "x": 36, "y": 44, "w": 12, "h": 11 },
"frame": {"x":12,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "2.png": {
"filename": "2.png", "frame": { "x": 0, "y": 55, "w": 12, "h": 11 },
"frame": {"x":24,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "3.png": {
"filename": "3.png", "frame": { "x": 12, "y": 55, "w": 12, "h": 11 },
"frame": {"x":36,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "4.png": {
"filename": "4.png", "frame": { "x": 24, "y": 55, "w": 12, "h": 11 },
"frame": {"x":48,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "5.png": {
"filename": "5.png", "frame": { "x": 84, "y": 55, "w": 12, "h": 11 },
"frame": {"x":60,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "6.png": {
"filename": "6.png", "frame": { "x": 96, "y": 55, "w": 12, "h": 11 },
"frame": {"x":72,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "7.png": {
"filename": "7.png", "frame": { "x": 120, "y": 55, "w": 12, "h": 11 },
"frame": {"x":84,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "8.png": {
"filename": "8.png", "frame": { "x": 132, "y": 55, "w": 12, "h": 11 },
"frame": {"x":96,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "9.png": {
"filename": "9.png", "frame": { "x": 52, "y": 33, "w": 12, "h": 11 },
"frame": {"x":108,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "A.png": {
"filename": "A.png", "frame": { "x": 64, "y": 33, "w": 12, "h": 11 },
"frame": {"x":120,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "ALT.png": {
"filename": "ALT.png", "frame": { "x": 0, "y": 22, "w": 16, "h": 11 },
"frame": {"x":132,"y":0,"w":16,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
"sourceSize": {"w":16,"h":12} "sourceSize": { "w": 16, "h": 11 }
}, },
{ "B.png": {
"filename": "B.png", "frame": { "x": 76, "y": 33, "w": 12, "h": 11 },
"frame": {"x":148,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "BACK.png": {
"filename": "BACK.png", "frame": { "x": 80, "y": 0, "w": 24, "h": 11 },
"frame": {"x":160,"y":0,"w":24,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":24,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 24, "h": 11 },
"sourceSize": {"w":24,"h":12} "sourceSize": { "w": 24, "h": 11 }
}, },
{ "C.png": {
"filename": "C.png", "frame": { "x": 88, "y": 33, "w": 12, "h": 11 },
"frame": {"x":184,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "CTRL.png": {
"filename": "CTRL.png", "frame": { "x": 0, "y": 11, "w": 22, "h": 11 },
"frame": {"x":196,"y":0,"w":22,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":22,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 22, "h": 11 },
"sourceSize": {"w":22,"h":12} "sourceSize": { "w": 22, "h": 11 }
}, },
{ "D.png": {
"filename": "D.png", "frame": { "x": 100, "y": 33, "w": 12, "h": 11 },
"frame": {"x":218,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "DEL.png": {
"filename": "DEL.png", "frame": { "x": 116, "y": 11, "w": 17, "h": 11 },
"frame": {"x":230,"y":0,"w":17,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"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 }, "spriteSourceSize": { "x": 0, "y": 0, "w": 17, "h": 11 },
"sourceSize": { "w": 17, "h": 11 } "sourceSize": { "w": 17, "h": 11 }
}, },
{ "E.png": {
"filename": "F.png", "frame": { "x": 112, "y": 33, "w": 12, "h": 11 },
"frame": {"x":321,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "END.png": {
"filename": "F1.png", "frame": { "x": 81, "y": 11, "w": 18, "h": 11 },
"frame": {"x":333,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 18, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 18, "h": 11 }
}, },
{ "ENTER.png": {
"filename": "F2.png", "frame": { "x": 28, "y": 0, "w": 27, "h": 11 },
"frame": {"x":346,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 27, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 27, "h": 11 }
}, },
{ "ESC.png": {
"filename": "F3.png", "frame": { "x": 99, "y": 11, "w": 17, "h": 11 },
"frame": {"x":359,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 17, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 17, "h": 11 }
}, },
{ "F.png": {
"filename": "F4.png", "frame": { "x": 124, "y": 33, "w": 12, "h": 11 },
"frame": {"x":372,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "F1.png": {
"filename": "F5.png", "frame": { "x": 78, "y": 22, "w": 13, "h": 11 },
"frame": {"x":385,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F2.png": {
"filename": "F6.png", "frame": { "x": 91, "y": 22, "w": 13, "h": 11 },
"frame": {"x":398,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F3.png": {
"filename": "F7.png", "frame": { "x": 104, "y": 22, "w": 13, "h": 11 },
"frame": {"x":411,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F4.png": {
"filename": "F8.png", "frame": { "x": 117, "y": 22, "w": 13, "h": 11 },
"frame": {"x":424,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F5.png": {
"filename": "F9.png", "frame": { "x": 130, "y": 22, "w": 13, "h": 11 },
"frame": {"x":437,"y":0,"w":13,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":13,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":13,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F6.png": {
"filename": "F10.png", "frame": { "x": 0, "y": 33, "w": 13, "h": 11 },
"frame": {"x":450,"y":0,"w":16,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":16,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F7.png": {
"filename": "F11.png", "frame": { "x": 13, "y": 33, "w": 13, "h": 11 },
"frame": {"x":466,"y":0,"w":15,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":15,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":15,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F8.png": {
"filename": "F12.png", "frame": { "x": 26, "y": 33, "w": 13, "h": 11 },
"frame": {"x":481,"y":0,"w":16,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":16,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F9.png": {
"filename": "G.png", "frame": { "x": 39, "y": 33, "w": 13, "h": 11 },
"frame": {"x":497,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 13, "h": 11 }
}, },
{ "F10.png": {
"filename": "H.png", "frame": { "x": 16, "y": 22, "w": 16, "h": 11 },
"frame": {"x":509,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 16, "h": 11 }
}, },
{ "F11.png": {
"filename": "HOME.png", "frame": { "x": 48, "y": 22, "w": 15, "h": 11 },
"frame": {"x":521,"y":0,"w":23,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":23,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 15, "h": 11 },
"sourceSize": {"w":23,"h":12} "sourceSize": { "w": 15, "h": 11 }
}, },
{ "F12.png": {
"filename": "I.png", "frame": { "x": 133, "y": 11, "w": 16, "h": 11 },
"frame": {"x":544,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 16, "h": 11 }
}, },
{ "G.png": {
"filename": "INS.png", "frame": { "x": 136, "y": 33, "w": 12, "h": 11 },
"frame": {"x":556,"y":0,"w":16,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":16,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":16,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "H.png": {
"filename": "J.png", "frame": { "x": 0, "y": 44, "w": 12, "h": 11 },
"frame": {"x":572,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "HOME.png": {
"filename": "K.png", "frame": { "x": 104, "y": 0, "w": 23, "h": 11 },
"frame": {"x":584,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 23, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 23, "h": 11 }
}, },
{ "I.png": {
"filename": "KEY_ARROW_DOWN.png", "frame": { "x": 24, "y": 44, "w": 12, "h": 11 },
"frame": {"x":596,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "INS.png": {
"filename": "KEY_ARROW_LEFT.png", "frame": { "x": 32, "y": 22, "w": 16, "h": 11 },
"frame": {"x":608,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 16, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 16, "h": 11 }
}, },
{ "J.png": {
"filename": "KEY_ARROW_RIGHT.png", "frame": { "x": 48, "y": 44, "w": 12, "h": 11 },
"frame": {"x":620,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "K.png": {
"filename": "KEY_ARROW_UP.png", "frame": { "x": 60, "y": 44, "w": 12, "h": 11 },
"frame": {"x":632,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "KEY_ARROW_DOWN.png": {
"filename": "L.png", "frame": { "x": 72, "y": 66, "w": 11, "h": 11 },
"frame": {"x":644,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 11, "h": 11 }
}, },
{ "KEY_ARROW_LEFT.png": {
"filename": "LEFT_BRACKET.png", "frame": { "x": 72, "y": 44, "w": 12, "h": 11 },
"frame": {"x":656,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "KEY_ARROW_RIGHT.png": {
"filename": "M.png", "frame": { "x": 84, "y": 44, "w": 12, "h": 11 },
"frame": {"x":668,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "KEY_ARROW_UP.png": {
"filename": "MINUS.png", "frame": { "x": 94, "y": 66, "w": 11, "h": 11 },
"frame": {"x":680,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 11, "h": 11 }
}, },
{ "L.png": {
"filename": "N.png", "frame": { "x": 96, "y": 44, "w": 12, "h": 11 },
"frame": {"x":692,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "LEFT_BRACKET.png": {
"filename": "O.png", "frame": { "x": 127, "y": 66, "w": 10, "h": 11 },
"frame": {"x":704,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 10, "h": 11 }
}, },
{ "M.png": {
"filename": "P.png", "frame": { "x": 108, "y": 44, "w": 12, "h": 11 },
"frame": {"x":716,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "MINUS.png": {
"filename": "PAGE_DOWN.png", "frame": { "x": 105, "y": 66, "w": 11, "h": 11 },
"frame": {"x":728,"y":0,"w":20,"h":11},
"rotated": false, "rotated": false,
"trimmed": 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 }, "spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 11 },
"sourceSize": { "w": 20, "h": 11 } "sourceSize": { "w": 20, "h": 11 }
}, },
{ "PAGE_UP.png": {
"filename": "PAGE_UP.png", "frame": { "x": 42, "y": 11, "w": 20, "h": 11 },
"frame": {"x":748,"y":0,"w":20,"h":11},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 11 }, "spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 11 },
"sourceSize": { "w": 20, "h": 11 } "sourceSize": { "w": 20, "h": 11 }
}, },
{ "PLUS.png": {
"filename": "PLUS.png", "frame": { "x": 116, "y": 66, "w": 11, "h": 11 },
"frame": {"x":768,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 11, "h": 11 }
}, },
{ "Q.png": {
"filename": "Q.png", "frame": { "x": 36, "y": 55, "w": 12, "h": 11 },
"frame": {"x":780,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "QUOTE.png": {
"filename": "QUOTE.png", "frame": { "x": 83, "y": 66, "w": 11, "h": 11 },
"frame": {"x":792,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 11, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 11, "h": 11 }
}, },
{ "R.png": {
"filename": "R.png", "frame": { "x": 48, "y": 55, "w": 12, "h": 11 },
"frame": {"x":804,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "RIGHT_BRACKET.png": {
"filename": "RIGHT_BRACKET.png", "frame": { "x": 137, "y": 66, "w": 10, "h": 11 },
"frame": {"x":816,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 10, "h": 11 }
}, },
{ "S.png": {
"filename": "S.png", "frame": { "x": 60, "y": 55, "w": 12, "h": 11 },
"frame": {"x":828,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "SEMICOLON.png": {
"filename": "SEMICOLON.png", "frame": { "x": 72, "y": 55, "w": 12, "h": 11 },
"frame": {"x":840,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "SHIFT.png": {
"filename": "SHIFT.png", "frame": { "x": 127, "y": 0, "w": 23, "h": 11 },
"frame": {"x":852,"y":0,"w":23,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":23,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 23, "h": 11 },
"sourceSize": {"w":23,"h":12} "sourceSize": { "w": 23, "h": 11 }
}, },
{ "SPACE.png": {
"filename": "SPACE.png", "frame": { "x": 55, "y": 0, "w": 25, "h": 11 },
"frame": {"x":875,"y":0,"w":25,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":25,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 25, "h": 11 },
"sourceSize": {"w":25,"h":12} "sourceSize": { "w": 25, "h": 11 }
}, },
{ "T.png": {
"filename": "T.png", "frame": { "x": 108, "y": 55, "w": 12, "h": 11 },
"frame": {"x":900,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "TAB.png": {
"filename": "TAB.png", "frame": { "x": 62, "y": 11, "w": 19, "h": 11 },
"frame": {"x":912,"y":0,"w":19,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":19,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 11 },
"sourceSize": {"w":19,"h":12} "sourceSize": { "w": 19, "h": 11 }
}, },
{ "TILDE.png": {
"filename": "TILDE.png", "frame": { "x": 63, "y": 22, "w": 15, "h": 11 },
"frame": {"x":931,"y":0,"w":15,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":15,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 15, "h": 11 },
"sourceSize": {"w":15,"h":12} "sourceSize": { "w": 15, "h": 11 }
}, },
{ "U.png": {
"filename": "U.png", "frame": { "x": 0, "y": 66, "w": 12, "h": 11 },
"frame": {"x":946,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "V.png": {
"filename": "V.png", "frame": { "x": 12, "y": 66, "w": 12, "h": 11 },
"frame": {"x":958,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "W.png": {
"filename": "W.png", "frame": { "x": 24, "y": 66, "w": 12, "h": 11 },
"frame": {"x":970,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "X.png": {
"filename": "X.png", "frame": { "x": 36, "y": 66, "w": 12, "h": 11 },
"frame": {"x":982,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "Y.png": {
"filename": "Y.png", "frame": { "x": 48, "y": 66, "w": 12, "h": 11 },
"frame": {"x":994,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "Z.png": {
"filename": "Z.png", "frame": { "x": 60, "y": 66, "w": 12, "h": 11 },
"frame": {"x":1006,"y":0,"w":12,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":12,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 12, "h": 11 },
"sourceSize": {"w":12,"h":12} "sourceSize": { "w": 12, "h": 11 }
}, },
{ "ACTION.png": {
"filename": "ACTION.png", "frame": { "x": 0, "y": 0, "w": 28, "h": 11 },
"frame": {"x":1018,"y":0,"w":28,"h":12},
"rotated": false, "rotated": false,
"trimmed": false, "trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":28,"h":12}, "spriteSourceSize": { "x": 0, "y": 0, "w": 28, "h": 11 },
"sourceSize": {"w":28,"h":12} "sourceSize": { "w": 28, "h": 11 }
}], }
},
"meta": { "meta": {
"app": "https://www.codeandweb.com/texturepacker", "app": "https://www.aseprite.org/",
"version": "1.0", "version": "1.3.7-dev",
"image": "keyboard.png", "image": "keyboard.png",
"format": "RGBA8888", "format": "RGBA8888",
"size": {"w":1018,"h":12}, "size": { "w": 150, "h": 77 },
"scale": "1", "scale": "1"
"smartupdate": "$TexturePacker:SmartUpdate:085d4353a5c4d18c90f82f8926710d72:45908b22b446cf7f4904d4e0b658b16a:bad03abb89ad027d879c383c13fd51bc$"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,22 @@
{
"1": {
"529cc5": "8153c7",
"d65a94": "5ad662",
"3a73ad": "6b3aad",
"bd216b": "21bd69",
"5a193a": "195a2a",
"193a63": "391963",
"295a84": "472984"
},
"2": {
"529cc5": "ffedb6",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"bd216b": "b35131",
"31313a": "3d1519",
"5a193a": "752e2e",
"193a63": "705040",
"295a84": "ad875a",
"4a4a52": "57211a"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1691,8 +1691,8 @@
], ],
"465": [ "465": [
0, 0,
2, 1,
2 1
], ],
"466": [ "466": [
1, 1,
@ -3980,6 +3980,11 @@
1, 1,
1 1
], ],
"465": [
0,
1,
1
],
"592": [ "592": [
1, 1,
1, 1,
@ -5690,7 +5695,7 @@
"465": [ "465": [
0, 0,
1, 1,
2 1
], ],
"466": [ "466": [
2, 2,
@ -8008,6 +8013,11 @@
1, 1,
1 1
], ],
"465": [
0,
1,
1
],
"592": [ "592": [
1, 1,
1, 1,

View File

@ -8,5 +8,14 @@
"bd216b": "21bd69", "bd216b": "21bd69",
"31313a": "31313a", "31313a": "31313a",
"d65a94": "5ad662" "d65a94": "5ad662"
},
"2": {
"5a193a": "752e2e",
"31313a": "3d1519",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"295a84": "ad875a",
"bd216b": "b35131",
"193a63": "705040"
} }
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,21 @@
{
"1": {
"193a63": "391963",
"295a84": "472984",
"3a73ad": "6b3aad",
"000000": "000000",
"5a193a": "195a2a",
"bd216b": "21bd69",
"31313a": "31313a",
"d65a94": "5ad662"
},
"2": {
"5a193a": "752e2e",
"31313a": "3d1519",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"295a84": "ad875a",
"bd216b": "b35131",
"193a63": "705040"
}
}

View File

@ -0,0 +1,22 @@
{
"1": {
"529cc5": "8153c7",
"d65a94": "5ad662",
"3a73ad": "6b3aad",
"bd216b": "21bd69",
"5a193a": "195a2a",
"193a63": "391963",
"295a84": "472984"
},
"2": {
"529cc5": "ffedb6",
"d65a94": "e67d2f",
"3a73ad": "ebc582",
"bd216b": "b35131",
"31313a": "3d1519",
"5a193a": "752e2e",
"193a63": "705040",
"295a84": "ad875a",
"4a4a52": "57211a"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

View File

@ -1,3 +1,3 @@
import BattleScene from "#app/battle-scene.js"; import BattleScene from "#app/battle-scene";
export type ConditionFn = (scene: BattleScene, args?: any[]) => boolean; export type ConditionFn = (scene: BattleScene, args?: any[]) => boolean;

View File

@ -1,4 +1,4 @@
import { type enConfig } from "#app/locales/en/config.js"; import { type enConfig } from "#app/locales/en/config";
import { TOptions } from "i18next"; import { TOptions } from "i18next";
//TODO: this needs to be type properly in the future //TODO: this needs to be type properly in the future

View File

@ -63,7 +63,7 @@ import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { UiTheme } from "#enums/ui-theme"; import { UiTheme } from "#enums/ui-theme";
import { TimedEventManager } from "#app/timed-event-manager.js"; import { TimedEventManager } from "#app/timed-event-manager";
import i18next from "i18next"; import i18next from "i18next";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { battleSpecDialogue } from "./data/dialogue"; import { battleSpecDialogue } from "./data/dialogue";
@ -130,7 +130,7 @@ export default class BattleScene extends SceneBase {
public gameSpeed: integer = 1; public gameSpeed: integer = 1;
public damageNumbersMode: integer = 0; public damageNumbersMode: integer = 0;
public reroll: boolean = false; public reroll: boolean = false;
public shopCursorTarget: number = ShopCursorTarget.CHECK_TEAM; public shopCursorTarget: number = ShopCursorTarget.REWARDS;
public showMovesetFlyout: boolean = true; public showMovesetFlyout: boolean = true;
public showArenaFlyout: boolean = true; public showArenaFlyout: boolean = true;
public showTimeOfDayWidget: boolean = true; public showTimeOfDayWidget: boolean = true;
@ -855,7 +855,7 @@ export default class BattleScene extends SceneBase {
overrideModifiers(this, false); overrideModifiers(this, false);
overrideHeldItems(this, pokemon, false); overrideHeldItems(this, pokemon, false);
if (boss && !dataSource) { if (boss && !dataSource) {
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295)); const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
for (let s = 0; s < pokemon.ivs.length; s++) { for (let s = 0; s < pokemon.ivs.length; s++) {
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75)); pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
@ -961,6 +961,16 @@ export default class BattleScene extends SceneBase {
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym(); this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
} }
/**
* Generates a random number using the current battle's seed
*
* This calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`
*
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randBattleSeedInt(range: integer, min: integer = 0): integer { randBattleSeedInt(range: integer, min: integer = 0): integer {
return this.currentBattle?.randSeedInt(this, range, min); return this.currentBattle?.randSeedInt(this, range, min);
} }
@ -974,6 +984,7 @@ export default class BattleScene extends SceneBase {
this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24)); this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24));
console.log("Seed:", this.seed); console.log("Seed:", this.seed);
this.resetSeed(); // Properly resets RNG after saving and quitting a session
this.disableMenu = false; this.disableMenu = false;
@ -1111,7 +1122,8 @@ export default class BattleScene extends SceneBase {
doubleTrainer = false; doubleTrainer = false;
} }
} }
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant);
this.field.add(newTrainer); this.field.add(newTrainer);
} }
} }
@ -2181,8 +2193,14 @@ export default class BattleScene extends SceneBase {
return true; return true;
} }
findPhase(phaseFilter: (phase: Phase) => boolean): Phase | undefined { /**
return this.phaseQueue.find(phaseFilter); * Find a specific {@linkcode Phase} in the phase queue.
*
* @param phaseFilter filter function to use to find the wanted phase
* @returns the found phase or undefined if none found
*/
findPhase<P extends Phase = Phase>(phaseFilter: (phase: P) => boolean): P | undefined {
return this.phaseQueue.find(phaseFilter) as P;
} }
tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean { tryReplacePhase(phaseFilter: (phase: Phase) => boolean, phase: Phase): boolean {
@ -2619,7 +2637,7 @@ export default class BattleScene extends SceneBase {
if (mods.length < 1) { if (mods.length < 1) {
return mods; return mods;
} }
const rand = Math.floor(Utils.randSeedInt(mods.length)); const rand = Utils.randSeedInt(mods.length);
return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))]; return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))];
}; };
modifiers = shuffleModifiers(modifiers); modifiers = shuffleModifiers(modifiers);
@ -2741,6 +2759,35 @@ export default class BattleScene extends SceneBase {
(window as any).gameInfo = gameInfo; (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) * Initialized the 2nd phase of the final boss (e.g. form-change for Eternatus)
* @param pokemon The (enemy) pokemon * @param pokemon The (enemy) pokemon

View File

@ -31,7 +31,7 @@ export enum BattlerIndex {
export interface TurnCommand { export interface TurnCommand {
command: Command; command: Command;
cursor?: integer; cursor?: number;
move?: QueuedMove; move?: QueuedMove;
targets?: BattlerIndex[]; targets?: BattlerIndex[];
skip?: boolean; skip?: boolean;
@ -39,38 +39,40 @@ export interface TurnCommand {
} }
interface TurnCommands { interface TurnCommands {
[key: integer]: TurnCommand | null [key: number]: TurnCommand | null
} }
export default class Battle { export default class Battle {
protected gameMode: GameMode; protected gameMode: GameMode;
public waveIndex: integer; public waveIndex: number;
public battleType: BattleType; public battleType: BattleType;
public battleSpec: BattleSpec; public battleSpec: BattleSpec;
public trainer: Trainer | null; public trainer: Trainer | null;
public enemyLevels: integer[] | undefined; public enemyLevels: number[] | undefined;
public enemyParty: EnemyPokemon[]; public enemyParty: EnemyPokemon[] = [];
public seenEnemyPartyMemberIds: Set<integer>; public seenEnemyPartyMemberIds: Set<number> = new Set<number>();
public double: boolean; public double: boolean;
public started: boolean; public started: boolean = false;
public enemySwitchCounter: integer; public enemySwitchCounter: number = 0;
public turn: integer; public turn: number = 0;
public turnCommands: TurnCommands; public turnCommands: TurnCommands;
public playerParticipantIds: Set<integer>; public playerParticipantIds: Set<number> = new Set<number>();
public battleScore: integer; public battleScore: number = 0;
public postBattleLoot: PokemonHeldItemModifier[]; public postBattleLoot: PokemonHeldItemModifier[] = [];
public escapeAttempts: integer; public escapeAttempts: number = 0;
public lastMove: Moves; public lastMove: Moves;
public battleSeed: string; public battleSeed: string = Utils.randomString(16, true);
private battleSeedState: string | null; private battleSeedState: string | null = null;
public moneyScattered: number; public moneyScattered: number = 0;
public lastUsedPokeball: PokeballType | null; public lastUsedPokeball: PokeballType | null = null;
public playerFaints: number; // The amount of times pokemon on the players side have fainted /** The number of times a Pokemon on the player's side has fainted this battle */
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted public playerFaints: number = 0;
/** The number of times a Pokemon on the enemy's side has fainted this battle */
public enemyFaints: number = 0;
private rngCounter: integer = 0; private rngCounter: number = 0;
constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer?: Trainer, double?: boolean) { constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double?: boolean) {
this.gameMode = gameMode; this.gameMode = gameMode;
this.waveIndex = waveIndex; this.waveIndex = waveIndex;
this.battleType = battleType; this.battleType = battleType;
@ -79,22 +81,7 @@ export default class Battle {
this.enemyLevels = battleType !== BattleType.TRAINER this.enemyLevels = battleType !== BattleType.TRAINER
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave()) ? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
: trainer?.getPartyLevels(this.waveIndex); : trainer?.getPartyLevels(this.waveIndex);
this.enemyParty = []; this.double = double ?? false;
this.seenEnemyPartyMemberIds = new Set<integer>();
this.double = !!double;
this.enemySwitchCounter = 0;
this.turn = 0;
this.playerParticipantIds = new Set<integer>();
this.battleScore = 0;
this.postBattleLoot = [];
this.escapeAttempts = 0;
this.started = false;
this.battleSeed = Utils.randomString(16, true);
this.battleSeedState = null;
this.moneyScattered = 0;
this.lastUsedPokeball = null;
this.playerFaints = 0;
this.enemyFaints = 0;
} }
private initBattleSpec(): void { private initBattleSpec(): void {
@ -105,7 +92,7 @@ export default class Battle {
this.battleSpec = spec; this.battleSpec = spec;
} }
private getLevelForWave(): integer { private getLevelForWave(): number {
const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex); const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex);
const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2); const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
const bossMultiplier = 1.2; const bossMultiplier = 1.2;
@ -138,7 +125,7 @@ export default class Battle {
return rand / value; return rand / value;
} }
getBattlerCount(): integer { getBattlerCount(): number {
return this.double ? 2 : 1; return this.double ? 2 : 1;
} }
@ -367,7 +354,13 @@ export default class Battle {
return null; return null;
} }
randSeedInt(scene: BattleScene, range: integer, min: integer = 0): integer { /**
* Generates a random number using the current battle's seed. Calls {@linkcode Utils.randSeedInt}
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randSeedInt(scene: BattleScene, range: number, min: number = 0): number {
if (range <= 1) { if (range <= 1) {
return min; return min;
} }
@ -392,7 +385,7 @@ export default class Battle {
} }
export class FixedBattle extends Battle { export class FixedBattle extends Battle {
constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) { constructor(scene: BattleScene, waveIndex: number, config: FixedBattleConfig) {
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double); super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double);
if (config.getEnemyParty) { if (config.getEnemyParty) {
this.enemyParty = config.getEnemyParty(scene); this.enemyParty = config.getEnemyParty(scene);
@ -408,7 +401,7 @@ export class FixedBattleConfig {
public double: boolean; public double: boolean;
public getTrainer: GetTrainerFunc; public getTrainer: GetTrainerFunc;
public getEnemyParty: GetEnemyPartyFunc; public getEnemyParty: GetEnemyPartyFunc;
public seedOffsetWaveIndex: integer; public seedOffsetWaveIndex: number;
setBattleType(battleType: BattleType): FixedBattleConfig { setBattleType(battleType: BattleType): FixedBattleConfig {
this.battleType = battleType; this.battleType = battleType;
@ -430,7 +423,7 @@ export class FixedBattleConfig {
return this; return this;
} }
setSeedOffsetWave(seedOffsetWaveIndex: integer): FixedBattleConfig { setSeedOffsetWave(seedOffsetWaveIndex: number): FixedBattleConfig {
this.seedOffsetWaveIndex = seedOffsetWaveIndex; this.seedOffsetWaveIndex = seedOffsetWaveIndex;
return this; return this;
} }
@ -476,7 +469,7 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], rand
} }
export interface FixedBattleConfigs { export interface FixedBattleConfigs {
[key: integer]: FixedBattleConfig [key: number]: FixedBattleConfig
} }
/** /**
* Youngster/Lass on 5 * Youngster/Lass on 5

View File

@ -1,4 +1,4 @@
import {SettingGamepad} from "#app/system/settings/settings-gamepad.js"; import {SettingGamepad} from "#app/system/settings/settings-gamepad";
import {Button} from "#enums/buttons"; import {Button} from "#enums/buttons";
/** /**

1
src/constants.ts Normal file
View File

@ -0,0 +1 @@
export const PLAYER_PARTY_MAX_SIZE = 6;

672
src/data/ability.ts Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -7,17 +7,17 @@ import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
import { BattleStat } from "./battle-stat"; import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims"; import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import i18next from "i18next"; import i18next from "i18next";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { StatChangePhase } from "#app/phases/stat-change-phase.js"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
export enum ArenaTagSide { export enum ArenaTagSide {
BOTH, BOTH,
@ -786,8 +786,8 @@ class StickyWebTag extends ArenaTrapTag {
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const statLevels = new Utils.NumberHolder(-1); const stages = new Utils.NumberHolder(-1);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [BattleStat.SPD], statLevels.value)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
} }
} }
@ -875,7 +875,7 @@ class TailwindTag extends ArenaTag {
// Raise attack by one stage if party member has WIND_RIDER ability // Raise attack by one stage if party member has WIND_RIDER ability
if (pokemon.hasAbility(Abilities.WIND_RIDER)) { if (pokemon.hasAbility(Abilities.WIND_RIDER)) {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex())); 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));
} }
} }
} }

View File

@ -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 });
}

View File

@ -1,7 +1,6 @@
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { Stat, getStatName } from "./pokemon-stat";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves } from "./move"; import { ChargeAttr, MoveFlags, allMoves } from "./move";
@ -9,20 +8,20 @@ import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability"; import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { BattleStat } from "./battle-stat";
import { allAbilities } from "./ability"; import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import i18next from "#app/plugins/i18n.js"; import i18next from "#app/plugins/i18n";
import { CommonAnimPhase } from "#app/phases/common-anim-phase.js"; import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { MovePhase } from "#app/phases/move-phase.js"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; import { MovePhase } from "#app/phases/move-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatChangePhase, StatChangeCallback } from "#app/phases/stat-change-phase.js"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
export enum BattlerTagLapseType { export enum BattlerTagLapseType {
FAINT, FAINT,
@ -40,13 +39,15 @@ export class BattlerTag {
public turnCount: number; public turnCount: number;
public sourceMove: Moves; public sourceMove: Moves;
public sourceId?: number; 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.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ]; this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct? this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId; this.sourceId = sourceId;
this.isBatonPassable = isBatonPassable;
} }
canAdd(pokemon: Pokemon): boolean { canAdd(pokemon: Pokemon): boolean {
@ -97,6 +98,245 @@ export interface TerrainBattlerTag {
terrainTypes: TerrainType[]; terrainTypes: TerrainType[];
} }
/**
* Base class for tags that restrict the usage of moves. This effect is generally referred to as "disabling" a move
* in-game. This is not to be confused with {@linkcode Moves.DISABLE}.
*
* Descendants can override {@linkcode isMoveRestricted} to restrict moves that
* match a condition. A restricted move gets cancelled before it is used. Players and enemies should not be allowed
* to select restricted moves.
*/
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: integer, sourceMove?: Moves, sourceId?: integer) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
}
/** @override */
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
// Cancel the affected pokemon's selected move
const phase = pokemon.scene.getCurrentPhase() as MovePhase;
const move = phase.move;
if (this.isMoveRestricted(move.moveId)) {
if (this.interruptedText(pokemon, move.moveId)) {
pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId));
}
phase.cancel();
}
return true;
}
return super.lapse(pokemon, lapseType);
}
/**
* Gets whether this tag is restricting a move.
*
* @param {Moves} move {@linkcode Moves} ID to check restriction for.
* @returns {boolean} `true` if the move is restricted by this tag, otherwise `false`.
*/
abstract isMoveRestricted(move: Moves): boolean;
/**
* Gets the text to display when the player attempts to select a move that is restricted by this tag.
*
* @param {Pokemon} pokemon {@linkcode Pokemon} for which the player is attempting to select the restricted move
* @param {Moves} move {@linkcode Moves} ID of the move that is having its selection denied
* @returns {string} text to display when the player attempts to select the restricted move
*/
abstract selectionDeniedText(pokemon: Pokemon, move: Moves): string;
/**
* Gets the text to display when a move's execution is prevented as a result of the restriction.
* Because restriction effects also prevent selection of the move, this situation can only arise if a
* pokemon first selects a move, then gets outsped by a pokemon using a move that restricts the selected move.
*
* @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
* @returns {string} text to display when the move is interrupted
*/
interruptedText(pokemon: Pokemon, move: Moves): string {
return "";
}
}
/**
* Tag representing the "Throat Chop" effect. Pokemon with this tag cannot use sound-based moves.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Throat_Chop_(move) | Throat Chop}
* @extends MoveRestrictionBattlerTag
*/
export class ThroatChoppedTag extends MoveRestrictionBattlerTag {
constructor() {
super(BattlerTagType.THROAT_CHOPPED, [ BattlerTagLapseType.TURN_END, BattlerTagLapseType.PRE_MOVE ], 2, Moves.THROAT_CHOP);
}
/**
* Checks if a {@linkcode Moves | move} is restricted by Throat Chop.
* @override
* @param {Moves} move the {@linkcode Moves | move} to check for sound-based restriction
* @returns true if the move is sound-based
*/
override isMoveRestricted(move: Moves): boolean {
return allMoves[move].hasFlag(MoveFlags.SOUND_BASED);
}
/**
* Shows a message when the player attempts to select a move that is restricted by Throat Chop.
* @override
* @param {Pokemon} pokemon the {@linkcode Pokemon} that is attempting to select the restricted move
* @param {Moves} move the {@linkcode Moves | move} that is being restricted
* @returns the message to display when the player attempts to select the restricted move
*/
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name });
}
/**
* Shows a message when a move is interrupted by Throat Chop.
* @override
* @param {Pokemon} pokemon the interrupted {@linkcode Pokemon}
* @param {Moves} move the {@linkcode Moves | move} that was interrupted
* @returns the message to display when the move is interrupted
*/
override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:throatChopInterruptedMove", { pokemonName: getPokemonNameWithAffix(pokemon) });
}
}
/**
* Tag representing the "disabling" effect performed by {@linkcode Moves.DISABLE} and {@linkcode Abilities.CURSED_BODY}.
* When the tag is added, the last-used move of the tag holder is set as the disabled move.
*/
export class DisabledTag extends MoveRestrictionBattlerTag {
/** The move being disabled. Gets set when {@linkcode onAdd} is called for this tag. */
private moveId: Moves = Moves.NONE;
constructor(sourceId: number) {
super(BattlerTagType.DISABLED, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 4, Moves.DISABLE, sourceId);
}
/** @override */
override isMoveRestricted(move: Moves): boolean {
return move === this.moveId;
}
/**
* @override
*
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@linkcode moveId} and shows a message.
* Otherwise the move ID will not get assigned and this tag will get removed next turn.
*/
override onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
const move = pokemon.getLastXMoves()
.find(m => m.move !== Moves.NONE && m.move !== Moves.STRUGGLE && !m.virtual);
if (move === undefined) {
return;
}
this.moveId = move.move;
pokemon.scene.queueMessage(i18next.t("battlerTags:disabledOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name }));
}
/** @override */
override onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:disabledLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name }));
}
/** @override */
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name });
}
/**
* @override
* @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
* @returns {string} text to display when the move is interrupted
*/
override interruptedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
}
/** @override */
override loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.moveId = source.moveId;
}
}
/**
* Tag used by Gorilla Tactics to restrict the user to using only one move.
* @extends MoveRestrictionBattlerTag
*/
export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
private moveId = Moves.NONE;
constructor() {
super(BattlerTagType.GORILLA_TACTICS, BattlerTagLapseType.CUSTOM, 0);
}
/** @override */
override isMoveRestricted(move: Moves): boolean {
return move !== this.moveId;
}
/**
* @override
* @param {Pokemon} pokemon the {@linkcode Pokemon} to check if the tag can be added
* @returns `true` if the pokemon has a valid move and no existing {@linkcode GorillaTacticsTag}; `false` otherwise
*/
override canAdd(pokemon: Pokemon): boolean {
return (this.getLastValidMove(pokemon) !== undefined) && !pokemon.getTag(GorillaTacticsTag);
}
/**
* Ensures that move history exists on {@linkcode Pokemon} and has a valid move.
* If so, sets the {@linkcode moveId} and increases the user's Attack by 50%.
* @override
* @param {Pokemon} pokemon the {@linkcode Pokemon} to add the tag to
*/
override onAdd(pokemon: Pokemon): void {
const lastValidMove = this.getLastValidMove(pokemon);
if (!lastValidMove) {
return;
}
this.moveId = lastValidMove;
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
}
/**
*
* @override
* @param {Pokemon} pokemon n/a
* @param {Moves} move {@linkcode Moves} ID of the move being denied
* @returns {string} text to display when the move is denied
*/
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
return i18next.t("battle:canOnlyUseMove", { moveName: allMoves[this.moveId].name, pokemonName: getPokemonNameWithAffix(pokemon) });
}
/**
* Gets the last valid move from the pokemon's move history.
* @param {Pokemon} pokemon {@linkcode Pokemon} to get the last valid move from
* @returns {Moves | undefined} the last valid move from the pokemon's move history
*/
getLastValidMove(pokemon: Pokemon): Moves | undefined {
const move = pokemon.getLastXMoves()
.find(m => m.move !== Moves.NONE && m.move !== Moves.STRUGGLE && !m.virtual);
return move?.move;
}
}
/** /**
* BattlerTag that represents the "recharge" effects of moves like Hyper Beam. * BattlerTag that represents the "recharge" effects of moves like Hyper Beam.
*/ */
@ -207,7 +447,7 @@ export class ShellTrapTag extends BattlerTag {
export class TrappedTag extends BattlerTag { export class TrappedTag extends BattlerTag {
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) { 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 { canAdd(pokemon: Pokemon): boolean {
@ -327,7 +567,7 @@ export class InterruptedTag extends BattlerTag {
*/ */
export class ConfusedTag extends BattlerTag { export class ConfusedTag extends BattlerTag {
constructor(turnCount: number, sourceMove: Moves) { 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 { canAdd(pokemon: Pokemon): boolean {
@ -362,9 +602,9 @@ export class ConfusedTag extends BattlerTag {
// 1/3 chance of hitting self with a 40 base power move // 1/3 chance of hitting self with a 40 base power move
if (pokemon.randSeedInt(3) === 0) { if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getBattleStat(Stat.ATK); const atk = pokemon.getEffectiveStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF); 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)); const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
pokemon.damageAndUpdate(damage); pokemon.damageAndUpdate(damage);
pokemon.battleData.hitCount++; pokemon.battleData.hitCount++;
@ -387,7 +627,7 @@ export class ConfusedTag extends BattlerTag {
*/ */
export class DestinyBondTag extends BattlerTag { export class DestinyBondTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: number) { 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);
} }
/** /**
@ -506,7 +746,7 @@ export class SeedTag extends BattlerTag {
private sourceIndex: number; private sourceIndex: number;
constructor(sourceId: 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);
} }
/** /**
@ -767,7 +1007,7 @@ export class OctolockTag extends TrappedTag {
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (shouldLapse) { if (shouldLapse) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [BattleStat.DEF, BattleStat.SPDEF], -1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.DEF, Stat.SPDEF ], -1));
return true; return true;
} }
@ -777,7 +1017,7 @@ export class OctolockTag extends TrappedTag {
export class AquaRingTag extends BattlerTag { export class AquaRingTag extends BattlerTag {
constructor() { 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 { onAdd(pokemon: Pokemon): void {
@ -809,7 +1049,7 @@ export class AquaRingTag extends BattlerTag {
/** Tag used to allow moves that interact with {@link Moves.MINIMIZE} to function */ /** Tag used to allow moves that interact with {@link Moves.MINIMIZE} to function */
export class MinimizeTag extends BattlerTag { export class MinimizeTag extends BattlerTag {
constructor() { constructor() {
super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE, undefined); super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE);
} }
canAdd(pokemon: Pokemon): boolean { canAdd(pokemon: Pokemon): boolean {
@ -1093,7 +1333,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
} }
} }
export class ContactStatChangeProtectedTag extends ProtectedTag { export class ContactStatStageChangeProtectedTag extends ProtectedTag {
private stat: BattleStat; private stat: BattleStat;
private levels: number; private levels: number;
@ -1110,7 +1350,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
*/ */
loadTag(source: BattlerTag | any): void { loadTag(source: BattlerTag | any): void {
super.loadTag(source); super.loadTag(source);
this.stat = source.stat as BattleStat; this.stat = source.stat;
this.levels = source.levels; this.levels = source.levels;
} }
@ -1121,7 +1361,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
const effectPhase = pokemon.scene.getCurrentPhase(); const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon(); 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));
} }
} }
@ -1207,7 +1447,7 @@ export class SturdyTag extends BattlerTag {
export class PerishSongTag extends BattlerTag { export class PerishSongTag extends BattlerTag {
constructor(turnCount: number) { 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 { canAdd(pokemon: Pokemon): boolean {
@ -1263,7 +1503,7 @@ export class AbilityBattlerTag extends BattlerTag {
public ability: Abilities; public ability: Abilities;
constructor(tagType: BattlerTagType, ability: Abilities, lapseType: BattlerTagLapseType, turnCount: number) { constructor(tagType: BattlerTagType, ability: Abilities, lapseType: BattlerTagLapseType, turnCount: number) {
super(tagType, lapseType, turnCount, undefined); super(tagType, lapseType, turnCount);
this.ability = ability; this.ability = ability;
} }
@ -1348,11 +1588,10 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
const stats = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; let highestStat: EffectiveStat;
let highestStat: Stat; EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s)).reduce((highestValue: number, value: number, i: number) => {
stats.map(s => pokemon.getBattleStat(s)).reduce((highestValue: number, value: number, i: number) => {
if (value > highestValue) { if (value > highestValue) {
highestStat = stats[i]; highestStat = EFFECTIVE_STATS[i];
return value; return value;
} }
return highestValue; return highestValue;
@ -1370,7 +1609,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break; 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 { onRemove(pokemon: Pokemon): void {
@ -1440,7 +1679,7 @@ export class TypeImmuneTag extends BattlerTag {
public immuneType: Type; public immuneType: Type;
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) { 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; this.immuneType = immuneType;
} }
@ -1504,7 +1743,7 @@ export class TypeBoostTag extends BattlerTag {
export class CritBoostTag extends BattlerTag { export class CritBoostTag extends BattlerTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) { 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 { onAdd(pokemon: Pokemon): void {
@ -1596,7 +1835,7 @@ export class CursedTag extends BattlerTag {
private sourceIndex: number; private sourceIndex: number;
constructor(sourceId: 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);
} }
/** /**
@ -1788,25 +2027,25 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag {
*/ */
export class StockpilingTag extends BattlerTag { export class StockpilingTag extends BattlerTag {
public stockpiledCount: number = 0; public stockpiledCount: number = 0;
public statChangeCounts: { [BattleStat.DEF]: number; [BattleStat.SPDEF]: number } = { public statChangeCounts: { [Stat.DEF]: number; [Stat.SPDEF]: number } = {
[BattleStat.DEF]: 0, [Stat.DEF]: 0,
[BattleStat.SPDEF]: 0 [Stat.SPDEF]: 0
}; };
constructor(sourceMove: Moves = Moves.NONE) { constructor(sourceMove: Moves = Moves.NONE) {
super(BattlerTagType.STOCKPILING, BattlerTagLapseType.CUSTOM, 1, sourceMove); super(BattlerTagType.STOCKPILING, BattlerTagLapseType.CUSTOM, 1, sourceMove);
} }
private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => { private onStatStagesChanged: StatStageChangeCallback = (_, statsChanged, statChanges) => {
const defChange = statChanges[statsChanged.indexOf(BattleStat.DEF)] ?? 0; const defChange = statChanges[statsChanged.indexOf(Stat.DEF)] ?? 0;
const spDefChange = statChanges[statsChanged.indexOf(BattleStat.SPDEF)] ?? 0; const spDefChange = statChanges[statsChanged.indexOf(Stat.SPDEF)] ?? 0;
if (defChange) { if (defChange) {
this.statChangeCounts[BattleStat.DEF]++; this.statChangeCounts[Stat.DEF]++;
} }
if (spDefChange) { if (spDefChange) {
this.statChangeCounts[BattleStat.SPDEF]++; this.statChangeCounts[Stat.SPDEF]++;
} }
}; };
@ -1814,8 +2053,8 @@ export class StockpilingTag extends BattlerTag {
super.loadTag(source); super.loadTag(source);
this.stockpiledCount = source.stockpiledCount || 0; this.stockpiledCount = source.stockpiledCount || 0;
this.statChangeCounts = { this.statChangeCounts = {
[ BattleStat.DEF ]: source.statChangeCounts?.[ BattleStat.DEF ] ?? 0, [ Stat.DEF ]: source.statChangeCounts?.[ Stat.DEF ] ?? 0,
[ BattleStat.SPDEF ]: source.statChangeCounts?.[ BattleStat.SPDEF ] ?? 0, [ Stat.SPDEF ]: source.statChangeCounts?.[ Stat.SPDEF ] ?? 0,
}; };
} }
@ -1835,9 +2074,9 @@ export class StockpilingTag extends BattlerTag {
})); }));
// Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes. // 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, 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
)); ));
} }
} }
@ -1851,15 +2090,15 @@ export class StockpilingTag extends BattlerTag {
* one stage for each stack which had successfully changed that particular stat during onAdd. * one stage for each stack which had successfully changed that particular stat during onAdd.
*/ */
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
const defChange = this.statChangeCounts[BattleStat.DEF]; const defChange = this.statChangeCounts[Stat.DEF];
const spDefChange = this.statChangeCounts[BattleStat.SPDEF]; const spDefChange = this.statChangeCounts[Stat.SPDEF];
if (defChange) { 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) { 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));
} }
} }
} }
@ -1937,7 +2176,38 @@ export class ExposedTag extends BattlerTag {
} }
} }
/**
* Tag that doubles the type effectiveness of Fire-type moves.
* @extends BattlerTag
*/
export class TarShotTag extends BattlerTag {
constructor() {
super(BattlerTagType.TAR_SHOT, BattlerTagLapseType.CUSTOM, 0);
}
/**
* If the Pokemon is terastallized, the tag cannot be added.
* @param {Pokemon} pokemon the {@linkcode Pokemon} to which the tag is added
* @returns whether the tag is applied
*/
override canAdd(pokemon: Pokemon): boolean {
return !pokemon.isTerastallized();
}
override onAdd(pokemon: Pokemon): void {
pokemon.scene.queueMessage(i18next.t("battlerTags:tarShotOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
/**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
*
* @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}.
* @param turnCount the turn count.
* @param {Moves} sourceMove the source {@linkcode Moves}.
* @param sourceId the source ID.
* @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object.
*/
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) { switch (tagType) {
case BattlerTagType.RECHARGING: case BattlerTagType.RECHARGING:
@ -2001,11 +2271,11 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.SPIKY_SHIELD: case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8); return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD: case BattlerTagType.KINGS_SHIELD:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.ATK, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT: case BattlerTagType.OBSTRUCT:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.DEF, -2); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP: case BattlerTagType.SILK_TRAP:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.SPD, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER: case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove); return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK: case BattlerTagType.BURNING_BULWARK:
@ -2071,6 +2341,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new StockpilingTag(sourceMove); return new StockpilingTag(sourceMove);
case BattlerTagType.OCTOLOCK: case BattlerTagType.OCTOLOCK:
return new OctolockTag(sourceId); return new OctolockTag(sourceId);
case BattlerTagType.DISABLED:
return new DisabledTag(sourceId);
case BattlerTagType.IGNORE_GHOST: case BattlerTagType.IGNORE_GHOST:
return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]); return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]);
case BattlerTagType.IGNORE_DARK: case BattlerTagType.IGNORE_DARK:
@ -2078,6 +2350,12 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.GULP_MISSILE_ARROKUDA: case BattlerTagType.GULP_MISSILE_ARROKUDA:
case BattlerTagType.GULP_MISSILE_PIKACHU: case BattlerTagType.GULP_MISSILE_PIKACHU:
return new GulpMissileTag(tagType, sourceMove); return new GulpMissileTag(tagType, sourceMove);
case BattlerTagType.TAR_SHOT:
return new TarShotTag();
case BattlerTagType.THROAT_CHOPPED:
return new ThroatChoppedTag();
case BattlerTagType.GORILLA_TACTICS:
return new GorillaTacticsTag();
case BattlerTagType.NONE: case BattlerTagType.NONE:
default: default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -1,14 +1,14 @@
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { HitResult } from "../field/pokemon"; import Pokemon, { HitResult } from "../field/pokemon";
import { BattleStat } from "./battle-stat";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability"; import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
import i18next from "i18next"; import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js"; import { Stat, type BattleStat } from "#app/enums/stat";
import { StatChangePhase } from "#app/phases/stat-change-phase.js"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
export function getBerryName(berryType: BerryType): string { export function getBerryName(berryType: BerryType): string {
return i18next.t(`berry:${BerryType[berryType]}.name`); return i18next.t(`berry:${BerryType[berryType]}.name`);
@ -35,9 +35,10 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.SALAC: case BerryType.SALAC:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); 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); 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: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
@ -95,10 +96,11 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
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 statLevels = new Utils.NumberHolder(1); const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels); const statStages = new Utils.NumberHolder(1);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value)); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
@ -112,9 +114,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
const statLevels = new Utils.NumberHolder(2); const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels); const stages = new Utils.NumberHolder(2);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], statLevels.value)); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
}; };
case BerryType.LEPPA: case BerryType.LEPPA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {

View File

@ -46,7 +46,7 @@ export const biomeLinks: BiomeLinks = {
[Biome.SEABED]: [ Biome.CAVE, [ Biome.VOLCANO, 3 ] ], [Biome.SEABED]: [ Biome.CAVE, [ Biome.VOLCANO, 3 ] ],
[Biome.MOUNTAIN]: [ Biome.VOLCANO, [ Biome.WASTELAND, 2 ], [ Biome.SPACE, 3 ] ], [Biome.MOUNTAIN]: [ Biome.VOLCANO, [ Biome.WASTELAND, 2 ], [ Biome.SPACE, 3 ] ],
[Biome.BADLANDS]: [ Biome.DESERT, Biome.MOUNTAIN ], [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.DESERT]: [ Biome.RUINS, [ Biome.CONSTRUCTION_SITE, 2 ] ],
[Biome.ICE_CAVE]: Biome.SNOWY_FOREST, [Biome.ICE_CAVE]: Biome.SNOWY_FOREST,
[Biome.MEADOW]: [ Biome.PLAINS, Biome.FAIRY_CAVE ], [Biome.MEADOW]: [ Biome.PLAINS, Biome.FAIRY_CAVE ],

View File

@ -1,18 +1,18 @@
import * as Utils from "../utils"; import * as Utils from "../utils";
import i18next from "i18next"; import i18next from "i18next";
import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data.js"; import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species"; import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species";
import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; import Pokemon, { PokemonMove } from "#app/field/pokemon";
import { BattleType, FixedBattleConfig } from "#app/battle.js"; import { BattleType, FixedBattleConfig } from "#app/battle";
import Trainer, { TrainerVariant } from "#app/field/trainer.js"; import Trainer, { TrainerVariant } from "#app/field/trainer";
import { GameMode } from "#app/game-mode.js"; import { GameMode } from "#app/game-mode";
import { Type } from "./type"; import { Type } from "./type";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { Nature } from "./nature"; import { Nature } from "./nature";
import { Moves } from "#app/enums/moves.js"; import { Moves } from "#app/enums/moves";
import { TypeColor, TypeShadow } from "#app/enums/color.js"; import { TypeColor, TypeShadow } from "#app/enums/color";
import { pokemonEvolutions } from "./pokemon-evolutions"; import { pokemonEvolutions } from "./pokemon-evolutions";
import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonFormChanges } from "./pokemon-forms";

View File

@ -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<void>(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();
});
});
});
}
}

View File

@ -8,14 +8,14 @@ import { PlayerPokemon } from "#app/field/pokemon";
import i18next from "i18next"; import i18next from "i18next";
import { EggTier } from "#enums/egg-type"; import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { EggSourceType } from "#app/enums/egg-source-types.js"; import { EggSourceType } from "#app/enums/egg-source-types";
export const EGG_SEED = 1073741824; export const EGG_SEED = 1073741824;
// Rates for specific random properties in 1/x // Rates for specific random properties in 1/x
const DEFAULT_SHINY_RATE = 128; const DEFAULT_SHINY_RATE = 128;
const GACHA_SHINY_UP_SHINY_RATE = 64; const GACHA_SHINY_UP_SHINY_RATE = 64;
const SAME_SPECIES_EGG_SHINY_RATE = 24; const SAME_SPECIES_EGG_SHINY_RATE = 12;
const SAME_SPECIES_EGG_HA_RATE = 8; const SAME_SPECIES_EGG_HA_RATE = 8;
const MANAPHY_EGG_MANAPHY_RATE = 8; const MANAPHY_EGG_MANAPHY_RATE = 8;
const GACHA_EGG_HA_RATE = 192; const GACHA_EGG_HA_RATE = 192;
@ -139,6 +139,7 @@ export class Egg {
//// ////
constructor(eggOptions?: IEggOptions) { constructor(eggOptions?: IEggOptions) {
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.") //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? this._sourceType = eggOptions?.sourceType!; // TODO: is this bang correct?
@ -180,6 +181,16 @@ export class Egg {
this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct? this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct?
this.addEggToGameData(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,6 +211,9 @@ export class Egg {
// Generates a PlayerPokemon from an egg // Generates a PlayerPokemon from an egg
public generatePlayerPokemon(scene: BattleScene): PlayerPokemon { public generatePlayerPokemon(scene: BattleScene): PlayerPokemon {
let ret: PlayerPokemon;
const generatePlayerPokemonHelper = (scene: BattleScene) => {
// Legacy egg wants to hatch. Generate missing properties // Legacy egg wants to hatch. Generate missing properties
if (!this._species) { if (!this._species) {
this._isShiny = this.rollShiny(); this._isShiny = this.rollShiny();
@ -208,7 +222,7 @@ export class Egg {
let pokemonSpecies = getPokemonSpecies(this._species); let pokemonSpecies = getPokemonSpecies(this._species);
// Special condition to have Phione eggs also have a chance of generating Manaphy // Special condition to have Phione eggs also have a chance of generating Manaphy
if (this._species === Species.PHIONE) { if (this._species === Species.PHIONE && this._sourceType === EggSourceType.SAME_SPECIES_EGG) {
pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY); pokemonSpecies = getPokemonSpecies(Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) ? Species.PHIONE : Species.MANAPHY);
} }
@ -222,7 +236,7 @@ export class Egg {
} }
// This function has way to many optional parameters // This function has way to many optional parameters
const ret: PlayerPokemon = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false); ret = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false);
ret.shiny = this._isShiny; ret.shiny = this._isShiny;
ret.variant = this._variantTier; ret.variant = this._variantTier;
@ -231,6 +245,12 @@ export class Egg {
for (let s = 0; s < ret.ivs.length; s++) { for (let s = 0; s < ret.ivs.length; s++) {
ret.ivs[s] = Math.max(ret.ivs[s], secondaryIvs[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; return ret;
} }
@ -306,7 +326,8 @@ export class Egg {
break; break;
} }
return Utils.randSeedInt(baseChance * Math.pow(2, 3 - this.tier)) ? Utils.randSeedInt(3) : 3; const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier);
return Utils.randSeedInt(baseChance * tierMultiplier) ? Utils.randSeedInt(3) : 3;
} }
private getEggTierDefaultHatchWaves(eggTier?: EggTier): number { private getEggTierDefaultHatchWaves(eggTier?: EggTier): number {
@ -341,7 +362,12 @@ export class Egg {
* the species that was the legendary focus at the time * the species that was the legendary focus at the time
*/ */
if (this.isManaphyEgg()) { if (this.isManaphyEgg()) {
const rand = Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE); /**
* Adding a technicality to make unit tests easier: By making this check pass
* when Utils.randSeedInt(8) = 1, and by making the generatePlayerPokemon() species
* check pass when Utils.randSeedInt(8) = 0, we can tell them apart during tests.
*/
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
return rand ? Species.PHIONE : Species.MANAPHY; return rand ? Species.PHIONE : Species.MANAPHY;
} else if (this.tier === EggTier.MASTER } else if (this.tier === EggTier.MASTER
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) { && this._sourceType === EggSourceType.GACHA_LEGENDARY) {

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
import { Stat, getStatName } from "./pokemon-stat";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { TextStyle, getBBCodeFrag } from "../ui/text"; import { TextStyle, getBBCodeFrag } from "../ui/text";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { UiTheme } from "#enums/ui-theme"; import { UiTheme } from "#enums/ui-theme";
import i18next from "i18next"; import i18next from "i18next";
import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#app/enums/stat";
export { Nature }; export { Nature };
@ -14,10 +14,9 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
ret = i18next.t("nature:" + ret as any); ret = i18next.t("nature:" + ret as any);
} }
if (includeStatEffects) { if (includeStatEffects) {
const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat | null = null; let increasedStat: Stat | null = null;
let decreasedStat: Stat | null = null; let decreasedStat: Stat | null = null;
for (const stat of stats) { for (const stat of EFFECTIVE_STATS) {
const multiplier = getNatureStatMultiplier(nature, stat); const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) { if (multiplier > 1) {
increasedStat = stat; increasedStat = stat;
@ -28,7 +27,7 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW; const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW;
const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text; const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text;
if (increasedStat && decreasedStat) { 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 { } else {
ret = getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(-)`, textStyle); ret = getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(-)`, textStyle);
} }

View File

@ -1,7 +1,7 @@
import { Gender } from "./gender"; import { Gender } from "./gender";
import { PokeballType } from "./pokeball"; import { PokeballType } from "./pokeball";
import Pokemon from "../field/pokemon"; import Pokemon from "../field/pokemon";
import { Stat } from "./pokemon-stat"; import { Stat } from "#enums/stat";
import { Type } from "./type"; import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { SpeciesFormKey } from "./pokemon-species"; import { SpeciesFormKey } from "./pokemon-species";

View File

@ -8,7 +8,7 @@ import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { getPokemonNameWithAffix } from "#app/messages.js"; import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next"; import i18next from "i18next";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";

View File

@ -14,7 +14,7 @@ import { GrowthRate } from "./exp";
import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions"; import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions";
import { Type } from "./type"; import { Type } from "./type";
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "./pokemon-level-moves"; 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"; import { Variant, VariantSet, variantColorCache, variantData } from "./variant";
export enum Region { 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.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 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("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.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), 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),

View File

@ -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;
}

View File

@ -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";
}
}

View File

@ -4,7 +4,7 @@ import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { ChangeMovePriorityAbAttr, applyAbAttrs } from "./ability"; import { ChangeMovePriorityAbAttr, applyAbAttrs } from "./ability";
import { ProtectAttr } from "./move"; import { ProtectAttr } from "./move";
import { BattlerIndex } from "#app/battle.js"; import { BattlerIndex } from "#app/battle";
import i18next from "i18next"; import i18next from "i18next";
export enum TerrainType { export enum TerrainType {

View File

@ -1,4 +1,4 @@
import { VariantTier } from "#app/enums/variant-tier.js"; import { VariantTier } from "#app/enums/variant-tier";
export type Variant = 0 | 1 | 2; export type Variant = 0 | 1 | 2;

View File

@ -64,6 +64,7 @@ export enum BattlerTagType {
STOCKPILING = "STOCKPILING", STOCKPILING = "STOCKPILING",
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE", RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
ALWAYS_GET_HIT = "ALWAYS_GET_HIT", ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
DISABLED = "DISABLED",
IGNORE_GHOST = "IGNORE_GHOST", IGNORE_GHOST = "IGNORE_GHOST",
IGNORE_DARK = "IGNORE_DARK", IGNORE_DARK = "IGNORE_DARK",
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA", GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
@ -72,5 +73,8 @@ export enum BattlerTagType {
SHELL_TRAP = "SHELL_TRAP", SHELL_TRAP = "SHELL_TRAP",
DRAGON_CHEER = "DRAGON_CHEER", DRAGON_CHEER = "DRAGON_CHEER",
NO_RETREAT = "NO_RETREAT", NO_RETREAT = "NO_RETREAT",
GORILLA_TACTICS = "GORILLA_TACTICS",
THROAT_CHOPPED = "THROAT_CHOPPED",
TAR_SHOT = "TAR_SHOT",
BURNED_UP = "BURNED_UP", BURNED_UP = "BURNED_UP",
} }

View File

@ -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 { export enum ShopCursorTarget {
/** Cursor points to Reroll */ /** Cursor points to Reroll row */
REROLL, REROLL,
/** Cursor points to Items */ /** Cursor points to Rewards row */
ITEMS, REWARDS,
/** Cursor points to Shop */ /** Cursor points to Shop row */
SHOP, SHOP,
/** Cursor points to Check Team */ /** Cursor points to Check Team row */
CHECK_TEAM CHECK_TEAM
} }

View File

@ -1,8 +1,75 @@
/** Enum that comprises all possible stat-related attributes, in-battle and permanent, of a Pokemon. */
export enum Stat { export enum Stat {
/** Hit Points */
HP = 0, HP = 0,
/** Attack */
ATK, ATK,
/** Defense */
DEF, DEF,
/** Special Attack */
SPATK, SPATK,
/** Special Defense */
SPDEF, SPDEF,
/** Speed */
SPD, 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`;
} }

View File

@ -1,7 +1,7 @@
import { ArenaTagSide } from "#app/data/arena-tag.js"; import { ArenaTagSide } from "#app/data/arena-tag";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { TerrainType } from "#app/data/terrain.js"; import { TerrainType } from "#app/data/terrain";
import { WeatherType } from "#app/data/weather.js"; import { WeatherType } from "#app/data/weather";
/** Alias for all {@linkcode ArenaEvent} type strings */ /** Alias for all {@linkcode ArenaEvent} type strings */
export enum ArenaEventType { export enum ArenaEventType {

View File

@ -60,7 +60,7 @@ export class Arena {
this.scene.arenaBg.setTexture(`${biomeKey}_bg`); this.scene.arenaBg.setTexture(`${biomeKey}_bg`);
this.scene.arenaBgTransition.setTexture(`${biomeKey}_bg`); this.scene.arenaBgTransition.setTexture(`${biomeKey}_bg`);
// Redo this on initialise because during save/load the current wave isn't always // Redo this on initialize because during save/load the current wave isn't always
// set correctly during construction // set correctly during construction
this.updatePoolsForTimeOfDay(); this.updatePoolsForTimeOfDay();
} }
@ -289,7 +289,7 @@ export class Arena {
/** /**
* Sets weather to the override specified in overrides.ts * Sets weather to the override specified in overrides.ts
* @param weather new weather to set of type WeatherType * @param weather new {@linkcode WeatherType} to set
* @returns true to force trySetWeather to return true * @returns true to force trySetWeather to return true
*/ */
trySetWeatherOverride(weather: WeatherType): boolean { trySetWeatherOverride(weather: WeatherType): boolean {
@ -301,8 +301,8 @@ export class Arena {
/** /**
* Attempts to set a new weather to the battle * Attempts to set a new weather to the battle
* @param weather new weather to set of type WeatherType * @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set
* @param hasPokemonSource is the new weather from a pokemon * @param hasPokemonSource boolean if the new weather is from a pokemon
* @returns true if new weather set, false if no weather provided or attempting to set the same weather as currently in use * @returns true if new weather set, false if no weather provided or attempting to set the same weather as currently in use
*/ */
trySetWeather(weather: WeatherType, hasPokemonSource: boolean): boolean { trySetWeather(weather: WeatherType, hasPokemonSource: boolean): boolean {
@ -573,6 +573,12 @@ export class Arena {
this.ignoreAbilities = ignoreAbilities; this.ignoreAbilities = ignoreAbilities;
} }
/**
* Applies each `ArenaTag` in this Arena, based on which side (self, enemy, or both) is passed in as a parameter
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param side {@linkcode ArenaTagSide} which side's arena tags to apply
* @param args array of parameters that the called upon tags may need
*/
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, ...args: unknown[]): void { applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, ...args: unknown[]): void {
let tags = typeof tagType === "string" let tags = typeof tagType === "string"
? this.tags.filter(t => t.tagType === tagType) ? this.tags.filter(t => t.tagType === tagType)
@ -583,11 +589,28 @@ export class Arena {
tags.forEach(t => t.apply(this, args)); tags.forEach(t => t.apply(this, args));
} }
/**
* Applies the specified tag to both sides (ie: both user and trainer's tag that match the Tag specified)
* by calling {@linkcode applyTagsForSide()}
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param args array of parameters that the called upon tags may need
*/
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, ...args: unknown[]): void { applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, ...args: unknown[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
} }
addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean { /**
* Adds a new tag to the arena
* @param tagType {@linkcode ArenaTagType} the tag being added
* @param turnCount How many turns the tag lasts
* @param sourceMove {@linkcode Moves} the move the tag came from, or `undefined` if not from a move
* @param sourceId The ID of the pokemon in play the tag came from (see {@linkcode BattleScene.getPokemonById})
* @param side {@linkcode ArenaTagSide} which side(s) the tag applies to
* @param quiet If a message should be queued on screen to announce the tag being added
* @param targetIndex The {@linkcode BattlerIndex} of the target pokemon
* @returns `false` if there already exists a tag of this type in the Arena
*/
addTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, side: ArenaTagSide = ArenaTagSide.BOTH, quiet: boolean = false, targetIndex?: BattlerIndex): boolean {
const existingTag = this.getTagOnSide(tagType, side); const existingTag = this.getTagOnSide(tagType, side);
if (existingTag) { if (existingTag) {
existingTag.onOverlap(this); existingTag.onOverlap(this);
@ -600,6 +623,7 @@ export class Arena {
return false; return false;
} }
// creates a new tag object
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
if (newTag) { if (newTag) {
this.tags.push(newTag); this.tags.push(newTag);
@ -613,6 +637,11 @@ export class Arena {
return true; return true;
} }
/**
* Attempts to get a tag from the Arena via {@linkcode getTagOnSide} that applies to both sides
* @param tagType The {@linkcode ArenaTagType} or {@linkcode ArenaTag} to get
* @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there
*/
getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag | undefined { getTag(tagType: ArenaTagType | Constructor<ArenaTag>): ArenaTag | undefined {
return this.getTagOnSide(tagType, ArenaTagSide.BOTH); return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
} }
@ -621,16 +650,35 @@ export class Arena {
return !!this.getTag(tagType); return !!this.getTag(tagType);
} }
/**
* Attempts to get a tag from the Arena from a specific side (the tag passed in has to either apply to both sides, or the specific side only)
*
* eg: `MIST` only applies to the user's side, while `MUD_SPORT` applies to both user and enemy side
* @param tagType The {@linkcode ArenaTagType} or {@linkcode ArenaTag} to get
* @param side The {@linkcode ArenaTagSide} to look at
* @returns either the {@linkcode ArenaTag}, or `undefined` if it isn't there
*/
getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined { getTagOnSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide): ArenaTag | undefined {
return typeof(tagType) === "string" return typeof(tagType) === "string"
? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)) ? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side))
: this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); : this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side));
} }
/**
* Uses {@linkcode findTagsOnSide} to filter (using the parameter function) for specific tags that apply to both sides
* @param tagPredicate a function mapping {@linkcode ArenaTag}s to `boolean`s
* @returns array of {@linkcode ArenaTag}s from which the Arena's tags return true and apply to both sides
*/
findTags(tagPredicate: (t: ArenaTag) => boolean): ArenaTag[] { findTags(tagPredicate: (t: ArenaTag) => boolean): ArenaTag[] {
return this.findTagsOnSide(tagPredicate, ArenaTagSide.BOTH); return this.findTagsOnSide(tagPredicate, ArenaTagSide.BOTH);
} }
/**
* Returns specific tags from the arena that pass the `tagPredicate` function passed in as a parameter, and apply to the given side
* @param tagPredicate a function mapping {@linkcode ArenaTag}s to `boolean`s
* @param side The {@linkcode ArenaTagSide} to look at
* @returns array of {@linkcode ArenaTag}s from which the Arena's tags return `true` and apply to the given side
*/
findTagsOnSide(tagPredicate: (t: ArenaTag) => boolean, side: ArenaTagSide): ArenaTag[] { findTagsOnSide(tagPredicate: (t: ArenaTag) => boolean, side: ArenaTagSide): ArenaTag[] {
return this.tags.filter(t => tagPredicate(t) && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side)); return this.tags.filter(t => tagPredicate(t) && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side));
} }

View File

@ -3,26 +3,24 @@ import BattleScene, { AnySound } from "../battle-scene";
import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../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 { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import { Constructor } from "#app/utils"; import { Constructor } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp"; import { getLevelTotalExp } from "../data/exp";
import { Stat } from "../data/pokemon-stat"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier } from "../modifier/modifier"; 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 { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender"; import { Gender } from "../data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect"; import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { BattleStat } from "../data/battle-stat"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag } 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 { WeatherType } from "../data/weather";
import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; 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, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } 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 PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui"; import { Mode } from "../ui/ui";
@ -40,7 +38,7 @@ import Overrides from "#app/overrides";
import i18next from "i18next"; import i18next from "i18next";
import { speciesEggMoves } from "../data/egg-moves"; import { speciesEggMoves } from "../data/egg-moves";
import { ModifierTier } from "../modifier/modifier-tier"; 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 { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
@ -49,17 +47,18 @@ import { BerryType } from "#enums/berry-type";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { 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 { Challenges } from "#enums/challenges";
import { getPokemonNameWithAffix } from "#app/messages.js"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
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 { export enum FieldPosition {
CENTER, CENTER,
@ -676,49 +675,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]; return this.stats[stat];
} }
getBattleStat(stat: Stat, opponent?: Pokemon, move?: Move, isCritical: boolean = false): integer { /**
if (stat === Stat.HP) { * Writes the value to the corrseponding {@linkcode PermanentStat} of the {@linkcode Pokemon}.
return this.getStat(Stat.HP); *
} * Note that this does nothing if {@linkcode value} is less than 0.
const battleStat = (stat - 1) as BattleStat; * @param stat the desired {@linkcode PermanentStat} to be overwritten
const statLevel = new Utils.IntegerHolder(this.summonData.battleStats[battleStat]); * @param value the desired numeric value
if (opponent) { * @param bypassSummonData write to actual stats (`true` by default) or in-battle overridden stats (`false`)
if (isCritical) { */
switch (stat) { setStat(stat: PermanentStat, value: number, bypassSummonData: boolean = true): void {
case Stat.ATK: if (value >= 0) {
case Stat.SPATK: if (!bypassSummonData && this.summonData) {
statLevel.value = Math.max(statLevel.value, 0); this.summonData.stats[stat] = value;
break; } else {
case Stat.DEF: this.stats[stat] = value;
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); /**
* 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);
} }
} }
if (this.isPlayer()) {
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel);
} }
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); this.scene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
const fieldApplied = new Utils.BooleanHolder(false); const fieldApplied = new Utils.BooleanHolder(false);
for (const pokemon of this.scene.getField(true)) { 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) { if (fieldApplied.value) {
break; break;
} }
} }
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, battleStat, statValue); applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue);
let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value)); let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, isCritical);
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
if (this.getTag(BattlerTagType.SLOW_START)) { if (this.getTag(BattlerTagType.SLOW_START)) {
@ -765,24 +854,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!this.stats) { if (!this.stats) {
this.stats = [ 0, 0, 0, 0, 0, 0 ]; this.stats = [ 0, 0, 0, 0, 0, 0 ];
} }
const baseStats = this.getSpeciesForm().baseStats.slice(0);
if (this.fusionSpecies) { // Get and manipulate base stats
const fusionBaseStats = this.getFusionSpeciesForm().baseStats; const baseStats = this.getSpeciesForm(true).baseStats.slice();
for (let s = 0; s < this.stats.length; s++) { if (this.isFusion()) {
const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats;
for (const s of PERMANENT_STATS) {
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2); baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
} }
} else if (this.scene.gameMode.isSplicedOnly) { } 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); baseStats[s] = Math.ceil(baseStats[s] / 2);
} }
} }
this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats); this.scene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats);
const stats = Utils.getEnumValues(Stat);
for (const s of stats) { // Using base stats, calculate and store stats one by one
const isHp = s === Stat.HP; for (const s of PERMANENT_STATS) {
const baseStat = baseStats[s]; let value = Math.floor(((2 * baseStats[s] + this.ivs[s]) * this.level) * 0.01);
let value = Math.floor(((2 * baseStat + this.ivs[s]) * this.level) * 0.01); if (s === Stat.HP) {
if (isHp) {
value = value + this.level + 10; value = value + this.level + 10;
if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) { if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) {
value = 1; value = 1;
@ -803,7 +893,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
value = Math.max(Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](value * natureStatMultiplier.value), 1); value = Math.max(Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](value * natureStatMultiplier.value), 1);
} }
} }
this.stats[s] = value;
this.setStat(s, value);
} }
} }
@ -893,10 +984,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: this.moveset; : this.moveset;
// Overrides moveset based on arrays specified in overrides.ts // Overrides moveset based on arrays specified in overrides.ts
const overrideArray: Array<Moves> = this.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE; let overrideArray: Moves | Array<Moves> = this.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE;
if (!Array.isArray(overrideArray)) {
overrideArray = [overrideArray];
}
if (overrideArray.length > 0) { if (overrideArray.length > 0) {
if (!this.isPlayer()) {
this.moveset = [];
}
overrideArray.forEach((move: Moves, index: number) => { overrideArray.forEach((move: Moves, index: number) => {
const ppUsed = this.moveset[index]?.ppUsed || 0; const ppUsed = this.moveset[index]?.ppUsed ?? 0;
this.moveset[index] = new PokemonMove(move, Math.min(ppUsed, allMoves[move].pp)); this.moveset[index] = new PokemonMove(move, Math.min(ppUsed, allMoves[move].pp));
}); });
} }
@ -959,6 +1056,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const teraType = this.getTeraType(); const teraType = this.getTeraType();
if (teraType !== Type.UNKNOWN) { if (teraType !== Type.UNKNOWN) {
types.push(teraType); types.push(teraType);
if (forDefend) {
return types;
}
} }
} }
@ -1230,9 +1330,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const trappedByAbility = new Utils.BooleanHolder(false); const trappedByAbility = new Utils.BooleanHolder(false);
const opposingField = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
this.scene.getEnemyField()!.forEach(enemyPokemon => opposingField.forEach(opponent =>
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, enemyPokemon, trappedByAbility, this, trappedAbMessages, simulated) applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated)
); );
return (trappedByAbility.value || !!this.getTag(TrappedTag)); return (trappedByAbility.value || !!this.getTag(TrappedTag));
@ -1258,7 +1359,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Calculates the effectiveness of a move against the Pokémon. * Calculates the effectiveness of a move against the Pokémon.
* * This includes modifiers from move and ability attributes.
* @param source {@linkcode Pokemon} The attacking Pokémon. * @param source {@linkcode Pokemon} The attacking Pokémon.
* @param move {@linkcode Move} The move being used by the attacking Pokémon. * @param move {@linkcode Move} The move being used by the attacking Pokémon.
* @param ignoreAbility Whether to ignore abilities that might affect type effectiveness or immunity (defaults to `false`). * @param ignoreAbility Whether to ignore abilities that might affect type effectiveness or immunity (defaults to `false`).
@ -1278,10 +1379,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: 1); : 1);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (this.getTypes().find(t => move.isTypeImmune(source, this, t))) { if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) {
typeMultiplier.value = 0; typeMultiplier.value = 0;
} }
if (this.getTag(TarShotTag) && (this.getMoveType(move) === Type.FIRE)) {
typeMultiplier.value *= 2;
}
const cancelledHolder = cancelled ?? new Utils.BooleanHolder(false); const cancelledHolder = cancelled ?? new Utils.BooleanHolder(false);
if (!ignoreAbility) { if (!ignoreAbility) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier);
@ -1313,7 +1418,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
/** /**
* Calculates the type effectiveness multiplier for an attack type * Calculates the move's type effectiveness multiplier based on the target's type/s.
* @param moveType {@linkcode Type} the type of the move being used * @param moveType {@linkcode Type} the type of the move being used
* @param source {@linkcode Pokemon} the Pokemon using the move * @param source {@linkcode Pokemon} the Pokemon using the move
* @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) * @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks)
@ -1337,22 +1442,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
let multiplier = types.map(defType => { let multiplier = types.map(defType => {
const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType));
applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (source) { if (source) {
const ignoreImmunity = new Utils.BooleanHolder(false); const ignoreImmunity = new Utils.BooleanHolder(false);
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType); applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType);
} }
if (ignoreImmunity.value) { if (ignoreImmunity.value) {
if (multiplier.value === 0) {
return 1; return 1;
} }
}
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[]; const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) { if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) {
if (multiplier.value === 0) {
return 1; return 1;
} }
} }
const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType)); }
applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier);
return multiplier.value; return multiplier.value;
}).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier;
@ -1378,7 +1487,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true); const types = this.getTypes(true);
const enemyTypes = opponent.getTypes(true, true); const enemyTypes = opponent.getTypes(true, true);
/** Is this Pokemon faster than the opponent? */ /** 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. * 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. * This score is increased by 25 percent if this Pokemon is faster than the opponent.
@ -1630,7 +1739,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}; };
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true); this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? this.fusionSpecies.ability2 ? 2 : 1 : this.fusionSpecies.ability2 ? randAbilityIndex : 0); this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 : this.fusionSpecies.ability2 !== this.fusionSpecies.ability1 ? randAbilityIndex : 0);
this.fusionShiny = this.shiny; this.fusionShiny = this.shiny;
this.fusionVariant = this.variant; this.fusionVariant = this.variant;
@ -1757,7 +1866,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(SacrificialAttr) ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 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 // 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 // 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)]); movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1)]);
} }
@ -1769,8 +1878,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))]); 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 // Weight damaging moves against the lower stat
const worseCategory: MoveCategory = this.stats[Stat.ATK] > this.stats[Stat.SPATK] ? MoveCategory.SPECIAL : MoveCategory.PHYSICAL; const atk = this.getStat(Stat.ATK);
const statRatio = worseCategory === MoveCategory.PHYSICAL ? this.stats[Stat.ATK]/this.stats[Stat.SPATK] : this.stats[Stat.SPATK]/this.stats[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)]); 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. let weightMultiplier = 0.9; // The higher this is the more the game weights towards higher level moves. At 0 all moves are equal weight.
@ -1956,6 +2067,48 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField(); 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. * Calculates the accuracy multiplier of the user against a target.
* *
@ -1972,34 +2125,38 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return 1; return 1;
} }
const userAccuracyLevel = new Utils.IntegerHolder(this.summonData.battleStats[BattleStat.ACC]); const userAccStage = new Utils.IntegerHolder(this.getStatStage(Stat.ACC));
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]); const targetEvaStage = new Utils.IntegerHolder(target.getStatStage(Stat.EVA));
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, false, userAccuracyLevel); const ignoreAccStatStage = new Utils.BooleanHolder(false);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, false, targetEvasionLevel); const ignoreEvaStatStage = new Utils.BooleanHolder(false);
applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, false, targetEvasionLevel);
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvasionLevel); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage);
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), TempBattleStat.ACC, userAccuracyLevel); 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)) { 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); const accuracyMultiplier = new Utils.NumberHolder(1);
if (userAccuracyLevel.value !== targetEvasionLevel.value) { if (userAccStage.value !== targetEvaStage.value) {
accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value accuracyMultiplier.value = userAccStage.value > targetEvaStage.value
? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3 ? (3 + Math.min(userAccStage.value - targetEvaStage.value, 6)) / 3
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6)); : 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); 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 / evasionMultiplier.value;
return accuracyMultiplier.value;
} }
/** /**
@ -2086,29 +2243,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (critOnly.value || critAlways) { if (critOnly.value || critAlways) {
isCritical = true; isCritical = true;
} else { } else {
const critLevel = new Utils.IntegerHolder(0); const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
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;
}
}
const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) {
if (critBoostTag instanceof DragonCheerTag) {
critLevel.value += critBoostTag.typesOnAdd.includes(Type.DRAGON) ? 2 : 1;
} else {
critLevel.value += 2;
}
}
console.log(`crit stage: +${critLevel.value}`);
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance); isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
if (Overrides.NEVER_CRIT_OVERRIDE) { if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false; isCritical = false;
@ -2122,8 +2257,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false; isCritical = false;
} }
} }
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical)); const sourceAtk = new Utils.IntegerHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, 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); const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, false, criticalMultiplier); applyAbAttrs(MultCritAbAttr, source, null, false, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1); const screenMultiplier = new Utils.NumberHolder(1);
@ -2162,7 +2297,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (!isTypeImmune) { if (!isTypeImmune) {
const levelMultiplier = (2 * source.level / 5 + 2); const levelMultiplier = (2 * source.level / 5 + 2);
const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100); const randomMultiplier = (this.randSeedIntRange(85, 100) / 100);
damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2) damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
* stabMultiplier.value * stabMultiplier.value
* typeMultiplier * typeMultiplier
@ -2534,27 +2669,53 @@ 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 * @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 { transferSummon(source: Pokemon): void {
const battleStats = Utils.getEnumValues(BattleStat); // Copy all stat stages
for (const stat of battleStats) { for (const s of BATTLE_STATS) {
this.summonData.battleStats[stat] = source.summonData.battleStats[stat]; 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) { for (const tag of source.summonData.tags) {
if (!tag.isBatonPassable) {
// 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)) {
continue; continue;
} }
this.summonData.tags.push(tag); 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(); this.updateInfo();
} }
/**
* Gets whether the given move is currently disabled for this Pokemon.
*
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
* @returns {boolean} `true` if the move is disabled for this Pokemon, otherwise `false`
*
* @see {@linkcode MoveRestrictionBattlerTag}
*/
isMoveRestricted(moveId: Moves): boolean {
return this.getRestrictingTag(moveId) !== null;
}
/**
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
*
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
* @returns {MoveRestrictionBattlerTag | null} the first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
*/
getRestrictingTag(moveId: Moves): MoveRestrictionBattlerTag | null {
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
return tag as MoveRestrictionBattlerTag;
}
}
return null;
}
getMoveHistory(): TurnMove[] { getMoveHistory(): TurnMove[] {
return this.battleSummonData.moveHistory; return this.battleSummonData.moveHistory;
} }
@ -3306,12 +3467,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
fusionCanvas.remove(); fusionCanvas.remove();
} }
/**
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
* <!-- @import "../battle".Battle -->
* This calls either {@linkcode BattleScene.randBattleSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle-scene.ts`
* which calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`,
* or it directly calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle
*
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
* @param min The minimum integer to pick, default `0`
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
*/
randSeedInt(range: integer, min: integer = 0): integer { randSeedInt(range: integer, min: integer = 0): integer {
return this.scene.currentBattle return this.scene.currentBattle
? this.scene.randBattleSeedInt(range, min) ? this.scene.randBattleSeedInt(range, min)
: Utils.randSeedInt(range, min); : Utils.randSeedInt(range, min);
} }
/**
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
* @param min The minimum integer to generate
* @param max The maximum integer to generate
* @returns a random integer between {@linkcode min} and {@linkcode max} inclusive
*/
randSeedIntRange(min: integer, max: integer): integer { randSeedIntRange(min: integer, max: integer): integer {
return this.randSeedInt((max - min) + 1, min); return this.randSeedInt((max - min) + 1, min);
} }
@ -3729,16 +3908,17 @@ export class PlayerPokemon extends Pokemon {
this.scene.gameData.gameStats.pokemonFused++; this.scene.gameData.gameStats.pokemonFused++;
// Store the average HP% that each Pokemon has // 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.generateName();
this.calculateStats(); this.calculateStats();
// Set this Pokemon's HP to the average % of both fusion components // 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.isFainted()) {
// If this Pokemon hasn't fainted, make sure the HP wasn't set over the new maximum // 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 this.status = getRandomStatus(this.status, pokemon.status); // Get a random valid status between the two
} else if (!pokemon.isFainted()) { } else if (!pokemon.isFainted()) {
// If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero // If this Pokemon fainted but the other hasn't, make sure the HP wasn't set to zero
@ -4231,39 +4411,40 @@ export class EnemyPokemon extends Pokemon {
handleBossSegmentCleared(segmentIndex: integer): void { handleBossSegmentCleared(segmentIndex: integer): void {
while (segmentIndex - 1 < this.bossSegmentIndex) { 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); let boostedStat: EffectiveStat;
const statWeights = new Array().fill(battleStats.length).filter((bs: BattleStat) => this.summonData.battleStats[bs] < 6).map((bs: BattleStat) => this.getStat(bs + 1)); const statThresholds: number[] = [];
const statThresholds: integer[] = [];
let totalWeight = 0; let totalWeight = 0;
for (const bs of battleStats) {
totalWeight += statWeights[bs]; for (const i in statWeights) {
totalWeight += statWeights[i];
statThresholds.push(totalWeight); statThresholds.push(totalWeight);
} }
// Pick a random stat from the leftover stats to increase its stages
const randInt = Utils.randSeedInt(totalWeight); const randInt = Utils.randSeedInt(totalWeight);
for (const i in statThresholds) {
for (const bs of battleStats) { if (randInt < statThresholds[i]) {
if (randInt < statThresholds[bs]) { boostedStat = leftoverStats[i];
boostedStat = bs;
break; break;
} }
} }
let statLevels = 1; let stages = 1;
// increase the boost if the boss has at least 3 segments and we passed last shield // increase the boost if the boss has at least 3 segments and we passed last shield
if (this.bossSegments >= 3 && this.bossSegmentIndex === 1) { if (this.bossSegments >= 3 && this.bossSegmentIndex === 1) {
statLevels++; stages++;
} }
// increase the boost if the boss has at least 5 segments and we passed the second to last shield // 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) { if (this.bossSegments >= 5 && this.bossSegmentIndex === 2) {
statLevels++; 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--; this.bossSegmentIndex--;
} }
} }
@ -4296,17 +4477,29 @@ export class EnemyPokemon extends Pokemon {
return BattlerIndex.ENEMY + this.getFieldIndex(); return BattlerIndex.ENEMY + this.getFieldIndex();
} }
addToParty(pokeballType: PokeballType) { /**
* Add a new pokemon to the player's party (at `slotIndex` if set).
* @param pokeballType the type of pokeball the pokemon was caught with
* @param slotIndex an optional index to place the pokemon in the party
* @returns the pokemon that was added or null if the pokemon could not be added
*/
addToParty(pokeballType: PokeballType, slotIndex: number = -1) {
const party = this.scene.getParty(); const party = this.scene.getParty();
let ret: PlayerPokemon | null = null; let ret: PlayerPokemon | null = null;
if (party.length < 6) { if (party.length < PLAYER_PARTY_MAX_SIZE) {
this.pokeball = pokeballType; this.pokeball = pokeballType;
this.metLevel = this.level; this.metLevel = this.level;
this.metBiome = this.scene.arena.biomeType; this.metBiome = this.scene.arena.biomeType;
this.metSpecies = this.species.speciesId; this.metSpecies = this.species.speciesId;
const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this);
if (Utils.isBetween(slotIndex, 0, PLAYER_PARTY_MAX_SIZE - 1)) {
party.splice(slotIndex, 0, newPokemon);
} else {
party.push(newPokemon); party.push(newPokemon);
}
ret = newPokemon; ret = newPokemon;
this.scene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true); this.scene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true);
} }
@ -4339,10 +4532,9 @@ export interface AttackMoveResult {
} }
export class PokemonSummonData { export class PokemonSummonData {
public battleStats: number[] = [ 0, 0, 0, 0, 0, 0, 0 ]; /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */
public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
public moveQueue: QueuedMove[] = []; public moveQueue: QueuedMove[] = [];
public disabledMove: Moves = Moves.NONE;
public disabledTurns: number = 0;
public tags: BattlerTag[] = []; public tags: BattlerTag[] = [];
public abilitySuppressed: boolean = false; public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = []; public abilitiesApplied: Abilities[] = [];
@ -4352,7 +4544,7 @@ export class PokemonSummonData {
public ability: Abilities = Abilities.NONE; public ability: Abilities = Abilities.NONE;
public gender: Gender; public gender: Gender;
public fusionGender: Gender; public fusionGender: Gender;
public stats: number[]; public stats: number[] = [ 0, 0, 0, 0, 0, 0 ];
public moveset: (PokemonMove | null)[]; public moveset: (PokemonMove | null)[];
// If not initialized this value will not be populated from save data. // If not initialized this value will not be populated from save data.
public types: Type[] = []; public types: Type[] = [];
@ -4383,8 +4575,8 @@ export class PokemonTurnData {
public damageTaken: number = 0; public damageTaken: number = 0;
public attacksReceived: AttackMoveResult[] = []; public attacksReceived: AttackMoveResult[] = [];
public order: number; public order: number;
public battleStatsIncreased: boolean = false; public statStagesIncreased: boolean = false;
public battleStatsDecreased: boolean = false; public statStagesDecreased: boolean = false;
} }
export enum AiType { export enum AiType {
@ -4423,7 +4615,7 @@ export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | Hit
* It links to {@linkcode Move} class via the move ID. * It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks if a move has received. * Compared to {@linkcode Move}, this class also tracks if a move has received.
* PP Ups, amount of PP used, and things like that. * PP Ups, amount of PP used, and things like that.
* @see {@linkcode isUsable} - checks if move is disabled, out of PP, or not implemented. * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move. * @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has. * @see {@linkcode getMovePp} - returns amount of PP a move currently has.
@ -4443,11 +4635,25 @@ export class PokemonMove {
this.virtual = !!virtual; this.virtual = !!virtual;
} }
isUsable(pokemon: Pokemon, ignorePp?: boolean): boolean { /**
if (this.moveId && pokemon.summonData?.disabledMove === this.moveId) { * Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
*
* @param {Pokemon} pokemon {@linkcode Pokemon} that would be using this move
* @param {boolean} ignorePp If `true`, skips the PP check
* @param {boolean} ignoreRestrictionTags If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
*/
isUsable(pokemon: Pokemon, ignorePp?: boolean, ignoreRestrictionTags?: boolean): boolean {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId)) {
return false; return false;
} }
return (ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1) && !this.getMove().name.endsWith(" (N)");
if (this.getMove().name.endsWith(" (N)")) {
return false;
}
return (ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1);
} }
getMove(): Move { getMove(): Move {

View File

@ -16,7 +16,7 @@ import {
getIconForLatestInput, swap, getIconForLatestInput, swap,
} from "#app/configs/inputs/configHandler"; } from "#app/configs/inputs/configHandler";
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import {SettingGamepad} from "#app/system/settings/settings-gamepad.js"; import {SettingGamepad} from "#app/system/settings/settings-gamepad";
import {SettingKeyboard} from "#app/system/settings/settings-keyboard"; import {SettingKeyboard} from "#app/system/settings/settings-keyboard";
import TouchControl from "#app/touch-controls"; import TouchControl from "#app/touch-controls";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";

View File

@ -37,8 +37,7 @@ export interface ModifierTypeTranslationEntries {
ModifierType: { [key: string]: ModifierTypeTranslationEntry }, ModifierType: { [key: string]: ModifierTypeTranslationEntry },
SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry }, SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry },
AttackTypeBoosterItem: SimpleTranslationEntries, AttackTypeBoosterItem: SimpleTranslationEntries,
TempBattleStatBoosterItem: SimpleTranslationEntries, TempStatStageBoosterItem: SimpleTranslationEntries,
TempBattleStatBoosterStatName: SimpleTranslationEntries,
BaseStatBoosterItem: SimpleTranslationEntries, BaseStatBoosterItem: SimpleTranslationEntries,
EvolutionItem: SimpleTranslationEntries, EvolutionItem: SimpleTranslationEntries,
FormChangeItem: SimpleTranslationEntries, FormChangeItem: SimpleTranslationEntries,

View File

@ -78,6 +78,7 @@ export class LoadingScene extends SceneBase {
this.loadAtlas("overlay_hp_boss", "ui"); this.loadAtlas("overlay_hp_boss", "ui");
this.loadImage("overlay_exp", "ui"); this.loadImage("overlay_exp", "ui");
this.loadImage("icon_owned", "ui"); this.loadImage("icon_owned", "ui");
this.loadImage("icon_egg_move", "ui");
this.loadImage("ability_bar_left", "ui"); this.loadImage("ability_bar_left", "ui");
this.loadImage("bgm_bar", "ui"); this.loadImage("bgm_bar", "ui");
this.loadImage("party_exp_bar", "ui"); this.loadImage("party_exp_bar", "ui");
@ -164,6 +165,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("saving_icon", "ui"); this.loadImage("saving_icon", "ui");
this.loadImage("discord", "ui"); this.loadImage("discord", "ui");
this.loadImage("google", "ui"); this.loadImage("google", "ui");
this.loadImage("settings_icon", "ui");
this.loadImage("default_bg", "arenas"); this.loadImage("default_bg", "arenas");
// Load arena images // Load arena images
@ -246,7 +248,7 @@ export class LoadingScene extends SceneBase {
} else { } else {
this.loadAtlas("types", ""); this.loadAtlas("types", "");
} }
const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt_BR", "zh_CN"]; const availableLangs = ["en", "de", "it", "fr", "ja", "ko", "es", "pt-BR", "zh-CN"];
if (lang && availableLangs.includes(lang)) { if (lang && availableLangs.includes(lang)) {
this.loadImage("september-update-"+lang, "events"); this.loadImage("september-update-"+lang, "events");
} else { } else {
@ -272,6 +274,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("gacha_knob", "egg"); this.loadImage("gacha_knob", "egg");
this.loadImage("egg_list_bg", "ui"); this.loadImage("egg_list_bg", "ui");
this.loadImage("egg_summary_bg", "ui");
this.loadImage("end_m", "cg"); this.loadImage("end_m", "cg");
this.loadImage("end_f", "cg"); this.loadImage("end_f", "cg");

View File

@ -12,6 +12,7 @@
"typeImmunityHeal": "{{abilityName}} von {{pokemonNameWithAffix}} füllte einige KP auf!", "typeImmunityHeal": "{{abilityName}} von {{pokemonNameWithAffix}} füllte einige KP auf!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} vermeidet Schaden mit {{abilityName}}!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} vermeidet Schaden mit {{abilityName}}!",
"disguiseAvoidedDamage": "Die Tarnung von {{pokemonNameWithAffix}} ist aufgeflogen!!", "disguiseAvoidedDamage": "Die Tarnung von {{pokemonNameWithAffix}} ist aufgeflogen!!",
"fullHpResistType": "Der Panzer von {{pokemonNameWithAffix}} funkelt und verzerrt die Wechselwirkungen zwischen den Typen!",
"moveImmunity": "Es hat keine Wirkung auf {{pokemonNameWithAffix}}...", "moveImmunity": "Es hat keine Wirkung auf {{pokemonNameWithAffix}}...",
"reverseDrain": "{{pokemonNameWithAffix}} saugt Kloakensoße auf!", "reverseDrain": "{{pokemonNameWithAffix}} saugt Kloakensoße auf!",
"postDefendTypeChange": "{{abilityName}} von {{pokemonNameWithAffix}} macht es zu einem {{typeName}}-Typ!", "postDefendTypeChange": "{{abilityName}} von {{pokemonNameWithAffix}} macht es zu einem {{typeName}}-Typ!",
@ -51,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}} strahlt eine knisternde Aura aus!", "postSummonTeravolt": "{{pokemonNameWithAffix}} strahlt eine knisternde Aura aus!",
"postSummonDarkAura": "{{pokemonNameWithAffix}} strahlt eine dunkle Aura aus!", "postSummonDarkAura": "{{pokemonNameWithAffix}} strahlt eine dunkle Aura aus!",
"postSummonFairyAura": "{{pokemonNameWithAffix}} strahlt eine Feenaura aus!", "postSummonFairyAura": "{{pokemonNameWithAffix}} strahlt eine Feenaura aus!",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} kehrt die Wirkung aller Aura-Fähigkeiten um!",
"postSummonNeutralizingGas": "Reaktionsgas von {{pokemonNameWithAffix}} hat sich in der Umgebung ausgebreitet!", "postSummonNeutralizingGas": "Reaktionsgas von {{pokemonNameWithAffix}} hat sich in der Umgebung ausgebreitet!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} verfügt über zwei Fähigkeiten!",

View File

@ -89,7 +89,7 @@
"name": "Bänder-Meister", "name": "Bänder-Meister",
"name_female": "Bänder-Meisterin" "name_female": "Bänder-Meisterin"
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
"name": "Teamwork", "name": "Teamwork",
"description": "Nutze Staffette, während der Anwender mindestens eines Statuswertes maximiert hat." "description": "Nutze Staffette, während der Anwender mindestens eines Statuswertes maximiert hat."
}, },

View File

@ -36,5 +36,6 @@
"matBlock": "Tatami-Schild", "matBlock": "Tatami-Schild",
"craftyShield": "Trickschutz", "craftyShield": "Trickschutz",
"tailwind": "Rückenwind", "tailwind": "Rückenwind",
"happyHour": "Goldene Zeiten" "happyHour": "Goldene Zeiten",
"safeguard": "Bodyguard"
} }

View File

@ -44,6 +44,7 @@
"moveNotImplemented": "{{moveName}} ist noch nicht implementiert und kann nicht ausgewählt werden.", "moveNotImplemented": "{{moveName}} ist noch nicht implementiert und kann nicht ausgewählt werden.",
"moveNoPP": "Es sind keine AP für diese Attacke mehr übrig!", "moveNoPP": "Es sind keine AP für diese Attacke mehr übrig!",
"moveDisabled": "{{moveName}} ist deaktiviert!", "moveDisabled": "{{moveName}} ist deaktiviert!",
"disableInterruptedMove": "{{moveName}} von {{pokemonNameWithAffix}} ist blockiert!",
"noPokeballForce": "Eine unsichtbare Kraft verhindert die Nutzung von Pokébällen.", "noPokeballForce": "Eine unsichtbare Kraft verhindert die Nutzung von Pokébällen.",
"noPokeballTrainer": "Du kannst das Pokémon eines anderen Trainers nicht fangen!", "noPokeballTrainer": "Du kannst das Pokémon eines anderen Trainers nicht fangen!",
"noPokeballMulti": "Du kannst erst einen Pokéball werfen, wenn nur noch ein Pokémon übrig ist!", "noPokeballMulti": "Du kannst erst einen Pokéball werfen, wenn nur noch ein Pokémon übrig ist!",
@ -94,5 +95,6 @@
"retryBattle": "Möchtest du vom Beginn des Kampfes neustarten?", "retryBattle": "Möchtest du vom Beginn des Kampfes neustarten?",
"unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.", "unlockedSomething": "{{unlockedThing}} wurde freigeschaltet.",
"congratulations": "Glückwunsch!", "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?"
} }

View File

@ -67,5 +67,7 @@
"saltCuredLapse": "{{pokemonNameWithAffix}} wurde durch {{moveName}} verletzt!", "saltCuredLapse": "{{pokemonNameWithAffix}} wurde durch {{moveName}} verletzt!",
"cursedOnAdd": "{{pokemonNameWithAffix}} nimmt einen Teil seiner KP und legt einen Fluch auf {{pokemonName}}!", "cursedOnAdd": "{{pokemonNameWithAffix}} nimmt einen Teil seiner KP und legt einen Fluch auf {{pokemonName}}!",
"cursedLapse": "{{pokemonNameWithAffix}} wurde durch den Fluch verletzt!", "cursedLapse": "{{pokemonNameWithAffix}} wurde durch den Fluch verletzt!",
"stockpilingOnAdd": "{{pokemonNameWithAffix}} hortet {{stockpiledCount}}!" "stockpilingOnAdd": "{{pokemonNameWithAffix}} hortet {{stockpiledCount}}!",
"disabledOnAdd": " {{moveName}} von {{pokemonNameWithAffix}} wurde blockiert!",
"disabledLapse": "{{moveName}} von {{pokemonNameWithAffix}} ist nicht länger blockiert!"
} }

View File

@ -1,10 +1,11 @@
{ {
"noneSelected": "Keine ausgewählt",
"title": "Herausforderungsmodifikatoren", "title": "Herausforderungsmodifikatoren",
"illegalEvolution": "{{pokemon}} hat sich in ein Pokémon verwandelt, dass für diese Herausforderung nicht zulässig ist!", "illegalEvolution": "{{pokemon}} hat sich in ein Pokémon verwandelt, dass für diese Herausforderung nicht zulässig ist!",
"singleGeneration": { "singleGeneration": {
"name": "Mono-Generation", "name": "Mono-Generation",
"desc": "Du kannst nur Pokémon aus der {{gen}} Generation verwenden.", "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_1": "ersten",
"gen_2": "zweiten", "gen_2": "zweiten",
"gen_3": "dritten", "gen_3": "dritten",

View File

@ -3,5 +3,6 @@
"power": "Stärke", "power": "Stärke",
"accuracy": "Genauigkeit", "accuracy": "Genauigkeit",
"abilityFlyInText": "{{passive}}{{abilityName}} von {{pokemonName}} wirkt!", "abilityFlyInText": "{{passive}}{{abilityName}} von {{pokemonName}} wirkt!",
"passive": "Passive Fähigkeit " "passive": "Passive Fähigkeit ",
"teraHover": "Tera-Typ {{type}}"
} }

View File

@ -25,5 +25,6 @@
"unlinkGoogle": "Google trennen", "unlinkGoogle": "Google trennen",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"losingProgressionWarning": "Du wirst jeglichen Fortschritt seit Anfang dieses Kampfes verlieren. Fortfahren?", "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"
} }

View File

@ -51,5 +51,7 @@
"renamePokemon": "Pokémon umbennenen", "renamePokemon": "Pokémon umbennenen",
"rename": "Umbenennen", "rename": "Umbenennen",
"nickname": "Spitzname", "nickname": "Spitzname",
"errorServerDown": "Ups! Es gab einen Fehler beim Versuch\nden Server zu kontaktieren\nLasse dieses Fenster offen\nDu wirst automatisch neu verbunden." "errorServerDown": "Ups! Es gab einen Fehler beim Versuch\nden Server zu kontaktieren\nLasse dieses Fenster offen\nDu wirst automatisch neu verbunden.",
"noSaves": "Du hast keine gespeicherten Dateien!",
"tooManySaves": "Du hast zu viele gespeicherte Dateien!"
} }

View File

@ -47,10 +47,14 @@
"description": "Ändert das Wesen zu {{natureName}}. Schaltet dieses Wesen permanent für diesen Starter frei." "description": "Ändert das Wesen zu {{natureName}}. Schaltet dieses Wesen permanent für diesen Starter frei."
}, },
"DoubleBattleChanceBoosterModifierType": { "DoubleBattleChanceBoosterModifierType": {
"description": "Verdoppelt die Wahrscheinlichkeit, dass die nächsten {{battleCount}} Begegnungen mit wilden Pokémon ein Doppelkampf sind." "description": "Vervierfacht die Chance, dass ein Kampf ein Doppelkampf wird, für bis zu {{battleCount}} Kämpfe."
}, },
"TempBattleStatBoosterModifierType": { "TempStatStageBoosterModifierType": {
"description": "Erhöht die {{tempBattleStatName}} aller Teammitglieder für 5 Kämpfe um eine Stufe." "description": "Erhöht {{stat}} aller Teammitglieder um {{amount}} für bis zu 5 Kämpfe.",
"extra": {
"stage": "eine Stufe",
"percentage": "30%"
}
}, },
"AttackTypeBoosterModifierType": { "AttackTypeBoosterModifierType": {
"description": "Erhöht die Stärke aller {{moveType}}-Attacken eines Pokémon um 20%." "description": "Erhöht die Stärke aller {{moveType}}-Attacken eines Pokémon um 20%."
@ -61,8 +65,8 @@
"AllPokemonLevelIncrementModifierType": { "AllPokemonLevelIncrementModifierType": {
"description": "Erhöht das Level aller Teammitglieder um {{levels}}." "description": "Erhöht das Level aller Teammitglieder um {{levels}}."
}, },
"PokemonBaseStatBoosterModifierType": { "BaseStatBoosterModifierType": {
"description": "Erhöht den {{statName}} Basiswert des Trägers um 10%. Das Stapellimit erhöht sich, je höher dein IS-Wert ist." "description": "Erhöht den {{stat}} Basiswert des Trägers um 10%. Das Stapellimit erhöht sich, je höher dein IS-Wert ist."
}, },
"AllPokemonFullHpRestoreModifierType": { "AllPokemonFullHpRestoreModifierType": {
"description": "Stellt 100% der KP aller Pokémon her." "description": "Stellt 100% der KP aller Pokémon her."
@ -248,6 +252,12 @@
"name": "Scope-Linse", "name": "Scope-Linse",
"description": "Ein Item zum Tragen. Es erhöht die Volltrefferquote." "description": "Ein Item zum Tragen. Es erhöht die Volltrefferquote."
}, },
"DIRE_HIT": {
"name": "X-Volltreffer",
"extra": {
"raises": "Volltrefferquote"
}
},
"LEEK": { "LEEK": {
"name": "Lauchstange", "name": "Lauchstange",
"description": "Ein Item, das von Porenta getragen werden kann. Diese lange Lauchstange erhöht die Volltrefferquote stark." "description": "Ein Item, das von Porenta getragen werden kann. Diese lange Lauchstange erhöht die Volltrefferquote stark."
@ -411,25 +421,13 @@
"description": "Ein Item, das Ditto zum Tragen gegeben werden kann. Fein und doch hart, erhöht dieses sonderbare Pulver die Initiative." "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_attack": "X-Angriff",
"x_defense": "X-Verteidigung", "x_defense": "X-Verteidigung",
"x_sp_atk": "X-Sp.-Ang.", "x_sp_atk": "X-Sp.-Ang.",
"x_sp_def": "X-Sp.-Vert.", "x_sp_def": "X-Sp.-Vert.",
"x_speed": "X-Tempo", "x_speed": "X-Tempo",
"x_accuracy": "X-Treffer", "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": "???"
}, },
"AttackTypeBoosterItem": { "AttackTypeBoosterItem": {
"silk_scarf": "Seidenschal", "silk_scarf": "Seidenschal",

View File

@ -3,7 +3,7 @@
"turnHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!", "turnHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!",
"hitHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!", "hitHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} wurde durch {{typeName}} wiederbelebt!", "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}}!", "moneyInterestApply": "Du erhählst {{moneyAmount}} ₽ durch das Item {{typeName}}!",
"turnHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} absorbiert!", "turnHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} absorbiert!",
"contactHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} geklaut!", "contactHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} geklaut!",

View File

@ -3,6 +3,10 @@
"cutHpPowerUpMove": "{{pokemonName}} nutzt seine KP um seine Attacke zu verstärken!", "cutHpPowerUpMove": "{{pokemonName}} nutzt seine KP um seine Attacke zu verstärken!",
"absorbedElectricity": "{{pokemonName}} absorbiert elektrische Energie!", "absorbedElectricity": "{{pokemonName}} absorbiert elektrische Energie!",
"switchedStatChanges": "{{pokemonName}} tauschte die Statuswerteveränderungen mit dem Ziel!", "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!", "goingAllOutForAttack": "{{pokemonName}} legt sich ins Zeug!",
"regainedHealth": "{{pokemonName}} erholt sich!", "regainedHealth": "{{pokemonName}} erholt sich!",
"keptGoingAndCrashed": "{{pokemonName}} springt daneben und verletzt sich!", "keptGoingAndCrashed": "{{pokemonName}} springt daneben und verletzt sich!",
@ -62,5 +66,6 @@
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!", "revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!", "swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
"exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!", "exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!" "safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!"
} }

View File

@ -1,7 +1,6 @@
{ {
"Stat": { "Stat": {
"HP": "KP", "HP": "KP",
"HPStat": "KP",
"HPshortened": "KP", "HPshortened": "KP",
"ATK": "Angriff", "ATK": "Angriff",
"ATKshortened": "Ang", "ATKshortened": "Ang",

View File

@ -100,7 +100,7 @@
"moveTouchControls": "Bewegung Touch Steuerung", "moveTouchControls": "Bewegung Touch Steuerung",
"shopOverlayOpacity": "Shop Overlay Deckkraft", "shopOverlayOpacity": "Shop Overlay Deckkraft",
"shopCursorTarget": "Shop-Cursor Ziel", "shopCursorTarget": "Shop-Cursor Ziel",
"items": "Items", "rewards": "Items",
"reroll": "Neu rollen", "reroll": "Neu rollen",
"shop": "Shop", "shop": "Shop",
"checkTeam": "Team überprüfen" "checkTeam": "Team überprüfen"

View File

@ -3,7 +3,7 @@
"badDreams": "{{pokemonName}} is tormented!", "badDreams": "{{pokemonName}} is tormented!",
"costar": "{{pokemonName}} copied {{allyName}}'s stat changes!", "costar": "{{pokemonName}} copied {{allyName}}'s stat changes!",
"iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} avoided\ndamage with {{abilityName}}!", "iceFaceAvoidedDamage": "{{pokemonNameWithAffix}} avoided\ndamage with {{abilityName}}!",
"perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both pokemon in 3 turns!", "perishBody": "{{pokemonName}}'s {{abilityName}}\nwill faint both Pokémon in 3 turns!",
"poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!", "poisonHeal": "{{pokemonName}}'s {{abilityName}}\nrestored its HP a little!",
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!", "trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!",
@ -52,6 +52,7 @@
"postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!", "postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!",
"postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!", "postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!",
"postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!", "postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!",
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
"postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!", "postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!",
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!", "postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!",
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!", "postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!",

View File

@ -97,9 +97,9 @@
"name": "Master League Champion", "name": "Master League Champion",
"name_female": "Master League Champion" "name_female": "Master League Champion"
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
"name": "Teamwork", "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": { "MAX_FRIENDSHIP": {
"name": "Friendmaxxing", "name": "Friendmaxxing",

View File

@ -39,5 +39,6 @@
"matBlock": "Mat Block", "matBlock": "Mat Block",
"craftyShield": "Crafty Shield", "craftyShield": "Crafty Shield",
"tailwind": "Tailwind", "tailwind": "Tailwind",
"happyHour": "Happy Hour" "happyHour": "Happy Hour",
"safeguard": "Safeguard"
} }

View File

@ -47,5 +47,11 @@
"tailwindOnRemovePlayer": "Your team's Tailwind petered out!", "tailwindOnRemovePlayer": "Your team's Tailwind petered out!",
"tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!", "tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!",
"happyHourOnAdd": "Everyone is caught up in the happy atmosphere!", "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!"
} }

View File

@ -44,6 +44,10 @@
"moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.", "moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.",
"moveNoPP": "There's no PP left for\nthis move!", "moveNoPP": "There's no PP left for\nthis move!",
"moveDisabled": "{{moveName}} is disabled!", "moveDisabled": "{{moveName}} is disabled!",
"canOnlyUseMove": "{{pokemonName}} can only use {{moveName}}!",
"moveCannotBeSelected": "{{moveName}} cannot be selected!",
"disableInterruptedMove": "{{pokemonNameWithAffix}}'s {{moveName}}\nis disabled!",
"throatChopInterruptedMove": "The effects of Throat Chop prevent\n{{pokemonName}} from using certain moves!",
"noPokeballForce": "An unseen force\nprevents using Poké Balls.", "noPokeballForce": "An unseen force\nprevents using Poké Balls.",
"noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!", "noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!",
"noPokeballMulti": "You can only throw a Poké Ball\nwhen there is one Pokémon remaining!", "noPokeballMulti": "You can only throw a Poké Ball\nwhen there is one Pokémon remaining!",
@ -61,6 +65,7 @@
"skipItemQuestion": "Are you sure you want to skip taking an item?", "skipItemQuestion": "Are you sure you want to skip taking an item?",
"itemStackFull": "The stack for {{fullItemName}} is full.\nYou will receive {{itemName}} instead.", "itemStackFull": "The stack for {{fullItemName}} is full.\nYou will receive {{itemName}} instead.",
"eggHatching": "Oh?", "eggHatching": "Oh?",
"eggSkipPrompt": "Skip to egg summary?",
"ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?", "ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?",
"wildPokemonWithAffix": "Wild {{pokemonName}}", "wildPokemonWithAffix": "Wild {{pokemonName}}",
"foePokemonWithAffix": "Foe {{pokemonName}}", "foePokemonWithAffix": "Foe {{pokemonName}}",

View File

@ -67,5 +67,8 @@
"saltCuredLapse": "{{pokemonNameWithAffix}} is hurt by {{moveName}}!", "saltCuredLapse": "{{pokemonNameWithAffix}} is hurt by {{moveName}}!",
"cursedOnAdd": "{{pokemonNameWithAffix}} cut its own HP and put a curse on the {{pokemonName}}!", "cursedOnAdd": "{{pokemonNameWithAffix}} cut its own HP and put a curse on the {{pokemonName}}!",
"cursedLapse": "{{pokemonNameWithAffix}} is afflicted by the Curse!", "cursedLapse": "{{pokemonNameWithAffix}} is afflicted by the Curse!",
"stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!" "stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!",
"disabledOnAdd": "{{pokemonNameWithAffix}}'s {{moveName}}\nwas disabled!",
"disabledLapse": "{{pokemonNameWithAffix}}'s {{moveName}}\nis no longer disabled.",
"tarShotOnAdd": "{{pokemonNameWithAffix}} became weaker to fire!"
} }

View File

@ -1,6 +1,6 @@
{ {
"title": "Challenge Modifiers", "title": "Challenge Modifiers",
"illegalEvolution": "{{pokemon}} changed into an ineligble pokémon\nfor this challenge!", "illegalEvolution": "{{pokemon}} changed into an ineligible Pokémon\nfor this challenge!",
"noneSelected": "None Selected", "noneSelected": "None Selected",
"singleGeneration": { "singleGeneration": {
"name": "Mono Gen", "name": "Mono Gen",

View File

@ -413,7 +413,7 @@
}, },
"ariana": { "ariana": {
"encounter": { "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", "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!" "3": "Your trip ends here. I'm going to take you down!"
}, },

View File

@ -3,5 +3,6 @@
"power": "Power", "power": "Power",
"accuracy": "Accuracy", "accuracy": "Accuracy",
"abilityFlyInText": " {{pokemonName}}'s {{passive}}{{abilityName}}", "abilityFlyInText": " {{pokemonName}}'s {{passive}}{{abilityName}}",
"passive": "Passive " "passive": "Passive ",
"teraHover": "{{type}} Terastallized"
} }

View File

@ -51,5 +51,7 @@
"renamePokemon": "Rename Pokémon", "renamePokemon": "Rename Pokémon",
"rename": "Rename", "rename": "Rename",
"nickname": "Nickname", "nickname": "Nickname",
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect." "errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect.",
"noSaves": "You don't have any save files on record!",
"tooManySaves": "You have too many save files on record!"
} }

View File

@ -47,10 +47,14 @@
"description": "Changes a Pokémon's nature to {{natureName}} and permanently unlocks the nature for the starter." "description": "Changes a Pokémon's nature to {{natureName}} and permanently unlocks the nature for the starter."
}, },
"DoubleBattleChanceBoosterModifierType": { "DoubleBattleChanceBoosterModifierType": {
"description": "Doubles the chance of an encounter being a double battle for {{battleCount}} battles." "description": "Quadruples the chance of an encounter being a double battle for up to {{battleCount}} battles."
}, },
"TempBattleStatBoosterModifierType": { "TempStatStageBoosterModifierType": {
"description": "Increases the {{tempBattleStatName}} of all party members by 1 stage for 5 battles." "description": "Increases the {{stat}} of all party members by {{amount}} for up to 5 battles.",
"extra": {
"stage": "1 stage",
"percentage": "30%"
}
}, },
"AttackTypeBoosterModifierType": { "AttackTypeBoosterModifierType": {
"description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%." "description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%."
@ -61,8 +65,8 @@
"AllPokemonLevelIncrementModifierType": { "AllPokemonLevelIncrementModifierType": {
"description": "Increases all party members' level by {{levels}}." "description": "Increases all party members' level by {{levels}}."
}, },
"PokemonBaseStatBoosterModifierType": { "BaseStatBoosterModifierType": {
"description": "Increases the holder's base {{statName}} by 10%. The higher your IVs, the higher the stack limit." "description": "Increases the holder's base {{stat}} by 10%. The higher your IVs, the higher the stack limit."
}, },
"AllPokemonFullHpRestoreModifierType": { "AllPokemonFullHpRestoreModifierType": {
"description": "Restores 100% HP for all Pokémon." "description": "Restores 100% HP for all Pokémon."
@ -183,6 +187,7 @@
"SOOTHE_BELL": { "name": "Soothe Bell" }, "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."}, "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."}, "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." }, "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 +255,14 @@
"METAL_POWDER": { "name": "Metal Powder", "description": "Extremely fine yet hard, this odd powder boosts Ditto's Defense stat." }, "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." } "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_attack": "X Attack",
"x_defense": "X Defense", "x_defense": "X Defense",
"x_sp_atk": "X Sp. Atk", "x_sp_atk": "X Sp. Atk",
"x_sp_def": "X Sp. Def", "x_sp_def": "X Sp. Def",
"x_speed": "X Speed", "x_speed": "X Speed",
"x_accuracy": "X Accuracy", "x_accuracy": "X Accuracy"
"dire_hit": "Dire Hit"
}, },
"TempBattleStatBoosterStatName": {
"ATK": "Attack",
"DEF": "Defense",
"SPATK": "Sp. Atk",
"SPDEF": "Sp. Def",
"SPD": "Speed",
"ACC": "Accuracy",
"CRIT": "Critical Hit Ratio",
"EVA": "Evasiveness",
"DEFAULT": "???"
},
"AttackTypeBoosterItem": { "AttackTypeBoosterItem": {
"silk_scarf": "Silk Scarf", "silk_scarf": "Silk Scarf",
"black_belt": "Black Belt", "black_belt": "Black Belt",

View File

@ -3,7 +3,7 @@
"turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!",
"hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!",
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{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}}!", "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!",
"turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!",
"contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!",

View File

@ -3,6 +3,11 @@
"cutHpPowerUpMove": "{{pokemonName}} cut its own HP to power up its move!", "cutHpPowerUpMove": "{{pokemonName}} cut its own HP to power up its move!",
"absorbedElectricity": "{{pokemonName}} absorbed electricity!", "absorbedElectricity": "{{pokemonName}} absorbed electricity!",
"switchedStatChanges": "{{pokemonName}} switched stat changes with the target!", "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!",
"shiftedStats": "{{pokemonName}} switched its {{statToSwitch}} and {{statToSwitchWith}}!",
"goingAllOutForAttack": "{{pokemonName}} is going all out for this attack!", "goingAllOutForAttack": "{{pokemonName}} is going all out for this attack!",
"regainedHealth": "{{pokemonName}} regained\nhealth!", "regainedHealth": "{{pokemonName}} regained\nhealth!",
"keptGoingAndCrashed": "{{pokemonName}} kept going\nand crashed!", "keptGoingAndCrashed": "{{pokemonName}} kept going\nand crashed!",
@ -61,5 +66,7 @@
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!", "suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
"revivalBlessing": "{{pokemonName}} was revived!", "revivalBlessing": "{{pokemonName}} was revived!",
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!", "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!",
"afterYou": "{{pokemonName}} took the kind offer!"
} }

View File

@ -1,7 +1,7 @@
{ {
"Stat": { "Stat": {
"HP": "Max. HP", "HP": "Max. HP",
"HPshortened": "MaxHP", "HPshortened": "HP",
"ATK": "Attack", "ATK": "Attack",
"ATKshortened": "Atk", "ATKshortened": "Atk",
"DEF": "Defense", "DEF": "Defense",
@ -13,8 +13,7 @@
"SPD": "Speed", "SPD": "Speed",
"SPDshortened": "Spd", "SPDshortened": "Spd",
"ACC": "Accuracy", "ACC": "Accuracy",
"EVA": "Evasiveness", "EVA": "Evasiveness"
"HPStat": "HP"
}, },
"Type": { "Type": {
"UNKNOWN": "Unknown", "UNKNOWN": "Unknown",

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