Merge branch 'beta' into daily-standardization
|
@ -1,7 +1,7 @@
|
|||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
labels: ["Bug"]
|
||||
labels: ["Bug", "Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
@ -19,21 +19,12 @@ body:
|
|||
value: |
|
||||
---
|
||||
- type: textarea
|
||||
id: session-file
|
||||
id: repro
|
||||
attributes:
|
||||
label: Session export file
|
||||
description: Open Menu → ManageData → Export Session → Select slot. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||
label: Reproduction
|
||||
description: Describe the steps to reproduce this bug. If applicable attach user/session data at the bottom
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: data-file
|
||||
attributes:
|
||||
label: User data export file
|
||||
description: Open Menu → ManageData → Export Data. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
|
@ -60,48 +51,20 @@ body:
|
|||
attributes:
|
||||
value: |
|
||||
---
|
||||
- type: dropdown
|
||||
id: os
|
||||
- type: textarea
|
||||
id: session-file
|
||||
attributes:
|
||||
label: What OS did you observe the bug on?
|
||||
multiple: true
|
||||
options:
|
||||
- PC/Windows
|
||||
- Mac/OSX
|
||||
- Linux
|
||||
- iOS
|
||||
- Android
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os-other
|
||||
attributes:
|
||||
label: If other please specify
|
||||
label: Session export file
|
||||
description: Open Menu → ManageData → Export Session → Select slot. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
- type: textarea
|
||||
id: data-file
|
||||
attributes:
|
||||
value: |
|
||||
---
|
||||
- type: dropdown
|
||||
id: browser
|
||||
attributes:
|
||||
label: Which browser do you use?
|
||||
multiple: true
|
||||
options:
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari
|
||||
- Edge
|
||||
- Opera
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-other
|
||||
attributes:
|
||||
label: If other please specify
|
||||
label: User data export file
|
||||
description: Open Menu → ManageData → Export Data. The file should now be in your `/Downloads` directory. Change the file extension type from `.prsv` to `.txt` (How to [Windows](https://www.guidingtech.com/how-to-change-file-type-on-windows/) | [Mac](https://support.apple.com/guide/mac-help/show-or-hide-filename-extensions-on-mac-mchlp2304/mac) | [iOS](https://www.guidingtech.com/change-file-type-extension-on-iphone/)).
|
||||
placeholder: Focus me and then drop your file here (or use the upload button at the bottom)
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
labels: ["Enhancement"]
|
||||
labels: ["Enhancement", "Triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
||||
- [ ] Have I provided a clear explanation of the changes?
|
||||
- [ ] Have I considered writing automated tests for the issue?
|
||||
- [ ] If I have text, did I add make it translatable and added a key in the English language?
|
||||
- [ ] If I have text, did I make it translatable and add a key in the English locale file(s)?
|
||||
- [ ] Have I tested the changes (manually)?
|
||||
- [ ] Are all unit tests still passing? (`npm run test`)
|
||||
- [ ] Are the changes visual?
|
||||
|
|
|
@ -11,6 +11,8 @@ on:
|
|||
branches:
|
||||
- main # Trigger on pull request events targeting the main branch
|
||||
- beta # Trigger on pull request events targeting the beta branch
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
jobs:
|
||||
run-linters: # Define a job named "run-linters"
|
||||
|
|
|
@ -8,6 +8,8 @@ on:
|
|||
branches:
|
||||
- main
|
||||
- beta
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
jobs:
|
||||
pages:
|
||||
|
|
|
@ -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' || '' }}
|
|
@ -11,23 +11,37 @@ on:
|
|||
branches:
|
||||
- main # Trigger on pull request events targeting the main branch
|
||||
- beta # Trigger on pull request events targeting the beta branch
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
jobs:
|
||||
run-tests: # Define a job named "run-tests"
|
||||
name: Run tests # Human-readable name for the job
|
||||
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
|
||||
|
||||
pre-test:
|
||||
name: Run Pre-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository # Step to check out the repository
|
||||
uses: actions/checkout@v4 # Use the checkout action version 4
|
||||
|
||||
- name: Set up Node.js # Step to set up Node.js environment
|
||||
uses: actions/setup-node@v4 # Use the setup-node action version 4
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
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: npm ci # Use 'npm ci' to install dependencies
|
||||
|
||||
- name: tests # Step to run tests
|
||||
run: npm run test:silent
|
||||
run-tests:
|
||||
name: Run Tests
|
||||
needs: [pre-test]
|
||||
strategy:
|
||||
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
|
|
@ -0,0 +1,110 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/**
|
||||
* This script creates a test boilerplate file for a move or ability.
|
||||
* @param {string} type - The type of test to create. Either "move", "ability",
|
||||
* or "item".
|
||||
* @param {string} fileName - The name of the file to create.
|
||||
* @example npm run create-test move tackle
|
||||
*/
|
||||
|
||||
// Get the directory name of the current module file
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Get the arguments from the command line
|
||||
const args = process.argv.slice(2);
|
||||
const type = args[0]; // "move" or "ability"
|
||||
let fileName = args[1]; // The file name
|
||||
|
||||
if (!type || !fileName) {
|
||||
console.error('Please provide a type ("move", "ability", or "item") and a file name.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Convert fileName from kebab-case or camelCase to snake_case
|
||||
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
|
||||
const formattedName = fileName
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, char => char.toUpperCase());
|
||||
|
||||
// Determine the directory based on the type
|
||||
let dir;
|
||||
let description;
|
||||
if (type === 'move') {
|
||||
dir = path.join(__dirname, 'src', 'test', 'moves');
|
||||
description = `Moves - ${formattedName}`;
|
||||
} else if (type === 'ability') {
|
||||
dir = path.join(__dirname, 'src', 'test', 'abilities');
|
||||
description = `Abilities - ${formattedName}`;
|
||||
} else if (type === "item") {
|
||||
dir = path.join(__dirname, 'src', 'test', 'items');
|
||||
description = `Items - ${formattedName}`;
|
||||
} else {
|
||||
console.error('Invalid type. Please use "move", "ability", or "item".');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure the directory exists
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create the file with the given name
|
||||
const filePath = path.join(dir, `${fileName}.test.ts`);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(`File "${fileName}.test.ts" already exists.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Define the content template
|
||||
const content = `import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||
|
||||
describe("${description}", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
const TIMEOUT = 20 * 1000;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.SPLASH])
|
||||
.battleType("single")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("test case", async () => {
|
||||
// await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
// game.move.select(Moves.SPLASH);
|
||||
}, TIMEOUT);
|
||||
});
|
||||
`;
|
||||
|
||||
// Write the template content to the file
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
|
||||
console.log(`File created at: ${filePath}`);
|
|
@ -191,15 +191,15 @@ Now that the enemy Pokémon with the best matchup score is on the field (assumin
|
|||
|
||||
We then need to apply a 2x multiplier for the move's type effectiveness and a 1.5x multiplier since STAB applies. After applying these multipliers, the final score for this move is **75**.
|
||||
|
||||
- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to
|
||||
- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatStageChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to
|
||||
|
||||
$\text{TBS}=4\times \text{levels} + (-2\times \text{sign(levels)})$
|
||||
|
||||
where `levels` is the number of stat stages added by the attribute (in this case, +2). The final score for this move is **6** (Note: because this move is self-targeted, we don't flip the sign of TBS when computing the target score).
|
||||
|
||||
- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score.
|
||||
- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatStageChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score.
|
||||
|
||||
$\text{TBS}=\text{getTargetBenefitScore(StatChangeAttr)}-\text{attackScore}$
|
||||
$\text{TBS}=\text{getTargetBenefitScore(StatStageChangeAttr)}-\text{attackScore}$
|
||||
|
||||
$\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import stylisticTs from '@stylistic/eslint-plugin-ts'
|
||||
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 [
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ export default [
|
|||
parser: parser
|
||||
},
|
||||
plugins: {
|
||||
// imports: imports.configs.recommended // Disabled due to not being compatible with eslint v9
|
||||
"import-x": importX,
|
||||
'@stylistic/ts': stylisticTs,
|
||||
'@typescript-eslint': tseslint
|
||||
},
|
||||
|
@ -39,7 +39,8 @@ export default [
|
|||
}],
|
||||
"space-before-blocks": ["error", "always"], // Enforces a space before blocks
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -23,15 +23,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
#links {
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="./index.css" />
|
||||
<link rel="manifest" href="./manifest.webmanifest">
|
||||
<script type="text/javascript" src="https://app.termly.io/resource-blocker/c5dbfa2f-9723-4c0f-a84b-2895124e851f?autoBlock=on"></script>
|
||||
<script>
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", function () {
|
||||
|
@ -144,13 +143,6 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<div id="tnc-links">
|
||||
<a href="#" class="termly-display-preferences" style="display: none;" target="_blank" rel="noreferrer noopener">Consent Preferences</a>
|
||||
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=bc96778b-3f04-4d25-bafc-0deba53e8bec" target="_blank" rel="noreferrer noopener">Privacy Policy</a>
|
||||
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=8b523c05-7ec2-4646-9534-5bd61b386e2a" target="_blank" rel="noreferrer noopener">Cookie Disclaimer</a>
|
||||
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=b01e092a-9721-477f-8356-45576702ff9e" target="_blank" rel="noreferrer noopener">Terms & Conditions</a>
|
||||
<a href="https://app.termly.io/policy-viewer/policy.html?policyUUID=3b5d1928-3f5b-4ee1-b8df-2d6c276b0bcc" target="_blank" rel="noreferrer noopener">Acceptable Use Policy</a>
|
||||
</div>
|
||||
<script type="module" src="./src/main.ts"></script>
|
||||
<script src="./src/touch-controls.ts" type="module"></script>
|
||||
<script src="./src/debug.js" type="module"></script>
|
||||
|
|
11
lefthook.yml
|
@ -2,6 +2,15 @@ pre-commit:
|
|||
parallel: true
|
||||
commands:
|
||||
eslint:
|
||||
glob: '*.{js,jsx,ts,tsx}'
|
||||
glob: "*.{js,jsx,ts,tsx}"
|
||||
run: npx eslint --fix {staged_files}
|
||||
stage_fixed: true
|
||||
skip:
|
||||
- merge
|
||||
- rebase
|
||||
|
||||
pre-push:
|
||||
commands:
|
||||
eslint:
|
||||
glob: "*.{js,ts,jsx,tsx}"
|
||||
run: npx eslint --fix {push_files}
|
|
@ -28,6 +28,7 @@
|
|||
"@vitest/coverage-istanbul": "^2.0.4",
|
||||
"dependency-cruiser": "^16.3.10",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-import-x": "^4.2.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"lefthook": "^1.6.12",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
|
@ -2505,6 +2506,19 @@
|
|||
"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": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
|
@ -2687,6 +2701,155 @@
|
|||
"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": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
|
||||
|
@ -3143,6 +3306,19 @@
|
|||
"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": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
|
@ -4854,6 +5030,16 @@
|
|||
"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": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
|
@ -5069,6 +5255,13 @@
|
|||
"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": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
|
@ -5460,6 +5653,13 @@
|
|||
"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": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
"eslint-ci": "eslint .",
|
||||
"docs": "typedoc",
|
||||
"depcruise": "depcruise src",
|
||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg"
|
||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
||||
"create-test": "node ./create-test-boilerplate.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.3.0",
|
||||
|
@ -31,6 +32,7 @@
|
|||
"@vitest/coverage-istanbul": "^2.0.4",
|
||||
"dependency-cruiser": "^16.3.10",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-plugin-import-x": "^4.2.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"lefthook": "^1.6.12",
|
||||
"phaser3spectorjs": "^0.0.8",
|
||||
|
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 198 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.0 KiB |
|
@ -1,38 +1,49 @@
|
|||
{
|
||||
"0": {
|
||||
"bc4524": "af5457",
|
||||
"31638c": "324a26",
|
||||
"101010": "101010",
|
||||
"5aa5ce": "40683c",
|
||||
"a5e6ff": "b6d9ac",
|
||||
"7bceef": "789c6e",
|
||||
"ced6ef": "c09e99",
|
||||
"737384": "774644",
|
||||
"ce4252": "af2c4f",
|
||||
"ffffff": "f1dcd8",
|
||||
"8c4231": "420b0c",
|
||||
"ffde4a": "c66f68",
|
||||
"c57b31": "551917",
|
||||
"ffffad": "f4bfb6",
|
||||
"ffde4a": "c66f68",
|
||||
"7bceef": "789c6e",
|
||||
"a5e6ff": "b6d9ac",
|
||||
"737384": "774644",
|
||||
"f7b531": "af5457",
|
||||
"ce4252": "af2c4f",
|
||||
"bc4524": "af5457",
|
||||
"00e5e7": "00e5e7"
|
||||
"c57b31": "551917",
|
||||
"ced6ef": "c09e99"
|
||||
},
|
||||
"1": {
|
||||
"bc4524": "3d325e",
|
||||
"31638c": "143a72",
|
||||
"101010": "101010",
|
||||
"5aa5ce": "4060bc",
|
||||
"a5e6ff": "b4b3ff",
|
||||
"7bceef": "657ddf",
|
||||
"ced6ef": "a8b5dd",
|
||||
"737384": "737384",
|
||||
"ce4252": "b75558",
|
||||
"ffffff": "e5ecff",
|
||||
"8c4231": "17103f",
|
||||
"ffde4a": "534e72",
|
||||
"c57b31": "2a1f50",
|
||||
"ffffad": "87879b",
|
||||
"ffde4a": "534e72",
|
||||
"7bceef": "657ddf",
|
||||
"a5e6ff": "b4b3ff",
|
||||
"f7b531": "3d325e",
|
||||
"ce4252": "b75558",
|
||||
"bc4524": "3d325e",
|
||||
"00e5e7": "00e5e7"
|
||||
"c57b31": "2a1f50",
|
||||
"ced6ef": "a8b5dd"
|
||||
},
|
||||
"2": {
|
||||
"ce4252": "215991",
|
||||
"ffde4a": "f16f40",
|
||||
"ffffad": "ffb274",
|
||||
"737384": "884c43",
|
||||
"c57b31": "761c03",
|
||||
"7bceef": "be3d2f",
|
||||
"8c4231": "5a0700",
|
||||
"5aa5ce": "892722",
|
||||
"8c4232": "761c03",
|
||||
"ffffff": "f5e1d1",
|
||||
"a5e6ff": "dd533a",
|
||||
"f7b531": "bc4524",
|
||||
"ced6ef": "d19e92",
|
||||
"31638c": "610f0e"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 30 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 43 KiB |
|
@ -1017,7 +1017,7 @@
|
|||
"279": [
|
||||
1,
|
||||
1,
|
||||
2
|
||||
1
|
||||
],
|
||||
"280": [
|
||||
0,
|
||||
|
@ -1691,8 +1691,8 @@
|
|||
],
|
||||
"465": [
|
||||
0,
|
||||
2,
|
||||
2
|
||||
1,
|
||||
1
|
||||
],
|
||||
"466": [
|
||||
1,
|
||||
|
@ -3980,6 +3980,11 @@
|
|||
1,
|
||||
1
|
||||
],
|
||||
"465": [
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"592": [
|
||||
1,
|
||||
1,
|
||||
|
@ -5690,7 +5695,7 @@
|
|||
"465": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
1
|
||||
],
|
||||
"466": [
|
||||
2,
|
||||
|
@ -8008,6 +8013,11 @@
|
|||
1,
|
||||
1
|
||||
],
|
||||
"465": [
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"592": [
|
||||
1,
|
||||
1,
|
||||
|
|
|
@ -8,5 +8,14 @@
|
|||
"bd216b": "21bd69",
|
||||
"31313a": "31313a",
|
||||
"d65a94": "5ad662"
|
||||
},
|
||||
"2": {
|
||||
"5a193a": "752e2e",
|
||||
"31313a": "3d1519",
|
||||
"d65a94": "e67d2f",
|
||||
"3a73ad": "ebc582",
|
||||
"295a84": "ad875a",
|
||||
"bd216b": "b35131",
|
||||
"193a63": "705040"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 34 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -3,44 +3,36 @@
|
|||
"bcb9be": "ae4c95",
|
||||
"f9f2fc": "ffc0e5",
|
||||
"7b787c": "793d6d",
|
||||
"e1dfe2": "e88cc5",
|
||||
"d0cfd0": "ce6bac",
|
||||
"b8b4ba": "d7d2f6",
|
||||
"dcd9dd": "e88cc5",
|
||||
"c9c0ce": "ce6bac",
|
||||
"cbc2d1": "d7d2f6",
|
||||
"938f94": "b465b9",
|
||||
"6c7275": "d3ffff",
|
||||
"9362e6": "80a4ff",
|
||||
"fcfcfc": "fcfcfc",
|
||||
"4a494e": "a7e6e5",
|
||||
"c6a4ff": "bed5ff",
|
||||
"101010": "101010",
|
||||
"3b3a3f": "4d8894",
|
||||
"aeadae": "e88cc5",
|
||||
"686568": "686568",
|
||||
"6f6d71": "793d6d",
|
||||
"b5b4b6": "ce6bac",
|
||||
"706e6d": "7d7c75",
|
||||
"fbf2ff": "d3ffff",
|
||||
"e66294": "80a4ff",
|
||||
"c6bbcb": "a7e6e5",
|
||||
"ffa4c5": "bed5ff",
|
||||
"7f806a": "4d8894",
|
||||
"a8a0ac": "e88cc5",
|
||||
"7c7a78": "793d6d",
|
||||
"bbb4bc": "ce6bac",
|
||||
"af9e9e": "42a2b1"
|
||||
},
|
||||
"2": {
|
||||
"bcb9be": "055946",
|
||||
"f9f2fc": "21be70",
|
||||
"7b787c": "004140",
|
||||
"e1dfe2": "12a169",
|
||||
"d0cfd0": "0a7a57",
|
||||
"b8b4ba": "567f83",
|
||||
"dcd9dd": "12a169",
|
||||
"c9c0ce": "0a7a57",
|
||||
"cbc2d1": "567f83",
|
||||
"938f94": "2b5458",
|
||||
"6c7275": "874059",
|
||||
"9362e6": "15c05f",
|
||||
"fcfcfc": "fcfcfc",
|
||||
"4a494e": "773050",
|
||||
"c6a4ff": "8ff3a3",
|
||||
"101010": "101010",
|
||||
"3b3a3f": "4b1f28",
|
||||
"aeadae": "12a169",
|
||||
"686568": "686568",
|
||||
"6f6d71": "004140",
|
||||
"b5b4b6": "0a7a57",
|
||||
"706e6d": "7d7c75",
|
||||
"fbf2ff": "874059",
|
||||
"e66294": "15c05f",
|
||||
"c6bbcb": "773050",
|
||||
"ffa4c5": "8ff3a3",
|
||||
"7f806a": "4b1f28",
|
||||
"a8a0ac": "12a169",
|
||||
"7c7a78": "004140",
|
||||
"bbb4bc": "0a7a57",
|
||||
"af9e9e": "48c492"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 205 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 179 B |
After Width: | Height: | Size: 172 B |
After Width: | Height: | Size: 205 B |
After Width: | Height: | Size: 261 B |
Before Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 800 B |
Before Width: | Height: | Size: 799 B |
After Width: | Height: | Size: 261 B |
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
||||
//TODO: this needs to be type properly in the future
|
||||
|
|
|
@ -63,7 +63,7 @@ import { Moves } from "#enums/moves";
|
|||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { Species } from "#enums/species";
|
||||
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 { TrainerType } from "#enums/trainer-type";
|
||||
import { battleSpecDialogue } from "./data/dialogue";
|
||||
|
@ -130,7 +130,7 @@ export default class BattleScene extends SceneBase {
|
|||
public gameSpeed: integer = 1;
|
||||
public damageNumbersMode: integer = 0;
|
||||
public reroll: boolean = false;
|
||||
public shopCursorTarget: number = ShopCursorTarget.CHECK_TEAM;
|
||||
public shopCursorTarget: number = ShopCursorTarget.REWARDS;
|
||||
public showMovesetFlyout: boolean = true;
|
||||
public showArenaFlyout: boolean = true;
|
||||
public showTimeOfDayWidget: boolean = true;
|
||||
|
@ -161,6 +161,13 @@ export default class BattleScene extends SceneBase {
|
|||
public moveAnimations: boolean = true;
|
||||
public expGainsSpeed: integer = 0;
|
||||
public skipSeenDialogues: boolean = false;
|
||||
/**
|
||||
* Determines if the egg hatching animation should be skipped
|
||||
* - 0 = Never (never skip animation)
|
||||
* - 1 = Ask (ask to skip animation when hatching 2 or more eggs)
|
||||
* - 2 = Always (automatically skip animation when hatching 2 or more eggs)
|
||||
*/
|
||||
public eggSkipPreference: number = 0;
|
||||
|
||||
/**
|
||||
* Defines the experience gain display mode.
|
||||
|
@ -841,12 +848,13 @@ export default class BattleScene extends SceneBase {
|
|||
}
|
||||
|
||||
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
||||
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
||||
level = Overrides.OPP_LEVEL_OVERRIDE;
|
||||
}
|
||||
if (Overrides.OPP_SPECIES_OVERRIDE) {
|
||||
species = getPokemonSpecies(Overrides.OPP_SPECIES_OVERRIDE);
|
||||
}
|
||||
|
||||
if (Overrides.OPP_LEVEL_OVERRIDE !== 0) {
|
||||
level = Overrides.OPP_LEVEL_OVERRIDE;
|
||||
// The fact that a Pokemon is a boss or not can change based on its Species and level
|
||||
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
||||
}
|
||||
|
||||
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource);
|
||||
|
@ -854,7 +862,7 @@ export default class BattleScene extends SceneBase {
|
|||
overrideModifiers(this, false);
|
||||
overrideHeldItems(this, pokemon, false);
|
||||
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++) {
|
||||
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
|
||||
|
@ -960,6 +968,16 @@ export default class BattleScene extends SceneBase {
|
|||
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 {
|
||||
return this.currentBattle?.randSeedInt(this, range, min);
|
||||
}
|
||||
|
@ -973,6 +991,7 @@ export default class BattleScene extends SceneBase {
|
|||
|
||||
this.setSeed(Overrides.SEED_OVERRIDE || Utils.randomString(24));
|
||||
console.log("Seed:", this.seed);
|
||||
this.resetSeed(); // Properly resets RNG after saving and quitting a session
|
||||
|
||||
this.disableMenu = false;
|
||||
|
||||
|
@ -1110,7 +1129,8 @@ export default class BattleScene extends SceneBase {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1327,6 +1347,13 @@ export default class BattleScene extends SceneBase {
|
|||
}
|
||||
|
||||
getEncounterBossSegments(waveIndex: integer, level: integer, species?: PokemonSpecies, forceBoss: boolean = false): integer {
|
||||
if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE > 1) {
|
||||
return Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE;
|
||||
} else if (Overrides.OPP_HEALTH_SEGMENTS_OVERRIDE === 1) {
|
||||
// The rest of the code expects to be returned 0 and not 1 if the enemy is not a boss
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.gameMode.isDaily && this.gameMode.isWaveFinal(waveIndex)) {
|
||||
return 5;
|
||||
}
|
||||
|
@ -1791,6 +1818,7 @@ export default class BattleScene extends SceneBase {
|
|||
config = config ?? {};
|
||||
try {
|
||||
const keyDetails = key.split("/");
|
||||
config["volume"] = config["volume"] ?? 1;
|
||||
switch (keyDetails[0]) {
|
||||
case "level_up_fanfare":
|
||||
case "item_fanfare":
|
||||
|
@ -1800,11 +1828,11 @@ export default class BattleScene extends SceneBase {
|
|||
case "evolution_fanfare":
|
||||
// These sounds are loaded in as BGM, but played as sound effects
|
||||
// When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM()
|
||||
config["volume"] = this.masterVolume * this.bgmVolume;
|
||||
config["volume"] *= (this.masterVolume * this.bgmVolume);
|
||||
break;
|
||||
case "battle_anims":
|
||||
case "cry":
|
||||
config["volume"] = this.masterVolume * this.fieldVolume;
|
||||
config["volume"] *= (this.masterVolume * this.fieldVolume);
|
||||
//PRSFX sound files are unusually loud
|
||||
if (keyDetails[1].startsWith("PRSFX- ")) {
|
||||
config["volume"] *= 0.5;
|
||||
|
@ -1812,10 +1840,10 @@ export default class BattleScene extends SceneBase {
|
|||
break;
|
||||
case "ui":
|
||||
//As of, right now this applies to the "select", "menu_open", "error" sound effects
|
||||
config["volume"] = this.masterVolume * this.uiVolume;
|
||||
config["volume"] *= (this.masterVolume * this.uiVolume);
|
||||
break;
|
||||
case "se":
|
||||
config["volume"] = this.masterVolume * this.seVolume;
|
||||
config["volume"] *= (this.masterVolume * this.seVolume);
|
||||
break;
|
||||
}
|
||||
this.sound.play(key, config);
|
||||
|
@ -2172,8 +2200,14 @@ export default class BattleScene extends SceneBase {
|
|||
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 {
|
||||
|
@ -2610,7 +2644,7 @@ export default class BattleScene extends SceneBase {
|
|||
if (mods.length < 1) {
|
||||
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))];
|
||||
};
|
||||
modifiers = shuffleModifiers(modifiers);
|
||||
|
@ -2732,6 +2766,35 @@ export default class BattleScene extends SceneBase {
|
|||
(window as any).gameInfo = gameInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function retrieves the sprite and audio keys for active Pokemon.
|
||||
* Active Pokemon include both enemy and player Pokemon of the current wave.
|
||||
* Note: Questions on garbage collection go to @frutescens
|
||||
* @returns a string array of active sprite and audio keys that should not be deleted
|
||||
*/
|
||||
getActiveKeys(): string[] {
|
||||
const keys: string[] = [];
|
||||
const playerParty = this.getParty();
|
||||
playerParty.forEach(p => {
|
||||
keys.push(p.getSpriteKey(true));
|
||||
keys.push(p.getBattleSpriteKey(true, true));
|
||||
keys.push("cry/" + p.species.getCryKey(p.formIndex));
|
||||
if (p.fusionSpecies) {
|
||||
keys.push("cry/"+p.fusionSpecies.getCryKey(p.fusionFormIndex));
|
||||
}
|
||||
});
|
||||
// enemyParty has to be operated on separately from playerParty because playerPokemon =/= enemyPokemon
|
||||
const enemyParty = this.getEnemyParty();
|
||||
enemyParty.forEach(p => {
|
||||
keys.push(p.getSpriteKey(true));
|
||||
keys.push("cry/" + p.species.getCryKey(p.formIndex));
|
||||
if (p.fusionSpecies) {
|
||||
keys.push("cry/"+p.fusionSpecies.getCryKey(p.fusionFormIndex));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized the 2nd phase of the final boss (e.g. form-change for Eternatus)
|
||||
* @param pokemon The (enemy) pokemon
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import BattleScene from "./battle-scene";
|
||||
import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./field/pokemon";
|
||||
import { Command } from "./ui/command-ui-handler";
|
||||
import * as Utils from "./utils";
|
||||
import Trainer, { TrainerVariant } from "./field/trainer";
|
||||
import { GameMode } from "./game-mode";
|
||||
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
|
||||
import { PokeballType } from "./data/pokeball";
|
||||
import {trainerConfigs} from "#app/data/trainer-config";
|
||||
import { trainerConfigs } from "#app/data/trainer-config";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattleSpec } from "#enums/battle-spec";
|
||||
import { Moves } from "#enums/moves";
|
||||
|
@ -31,46 +31,55 @@ export enum BattlerIndex {
|
|||
|
||||
export interface TurnCommand {
|
||||
command: Command;
|
||||
cursor?: integer;
|
||||
cursor?: number;
|
||||
move?: QueuedMove;
|
||||
targets?: BattlerIndex[];
|
||||
skip?: boolean;
|
||||
args?: any[];
|
||||
}
|
||||
|
||||
export interface FaintLogEntry {
|
||||
pokemon: Pokemon,
|
||||
turn: number
|
||||
}
|
||||
|
||||
interface TurnCommands {
|
||||
[key: integer]: TurnCommand | null
|
||||
[key: number]: TurnCommand | null
|
||||
}
|
||||
|
||||
export default class Battle {
|
||||
protected gameMode: GameMode;
|
||||
public waveIndex: integer;
|
||||
public waveIndex: number;
|
||||
public battleType: BattleType;
|
||||
public battleSpec: BattleSpec;
|
||||
public trainer: Trainer | null;
|
||||
public enemyLevels: integer[] | undefined;
|
||||
public enemyParty: EnemyPokemon[];
|
||||
public seenEnemyPartyMemberIds: Set<integer>;
|
||||
public enemyLevels: number[] | undefined;
|
||||
public enemyParty: EnemyPokemon[] = [];
|
||||
public seenEnemyPartyMemberIds: Set<number> = new Set<number>();
|
||||
public double: boolean;
|
||||
public started: boolean;
|
||||
public enemySwitchCounter: integer;
|
||||
public turn: integer;
|
||||
public started: boolean = false;
|
||||
public enemySwitchCounter: number = 0;
|
||||
public turn: number = 0;
|
||||
public turnCommands: TurnCommands;
|
||||
public playerParticipantIds: Set<integer>;
|
||||
public battleScore: integer;
|
||||
public postBattleLoot: PokemonHeldItemModifier[];
|
||||
public escapeAttempts: integer;
|
||||
public playerParticipantIds: Set<number> = new Set<number>();
|
||||
public battleScore: number = 0;
|
||||
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
||||
public escapeAttempts: number = 0;
|
||||
public lastMove: Moves;
|
||||
public battleSeed: string;
|
||||
private battleSeedState: string | null;
|
||||
public moneyScattered: number;
|
||||
public lastUsedPokeball: PokeballType | null;
|
||||
public playerFaints: number; // The amount of times pokemon on the players side have fainted
|
||||
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
|
||||
public battleSeed: string = Utils.randomString(16, true);
|
||||
private battleSeedState: string | null = null;
|
||||
public moneyScattered: number = 0;
|
||||
public lastUsedPokeball: PokeballType | null = null;
|
||||
/** The number of times a Pokemon on the player's side has fainted this battle */
|
||||
public playerFaints: number = 0;
|
||||
/** The number of times a Pokemon on the enemy's side has fainted this battle */
|
||||
public enemyFaints: number = 0;
|
||||
public playerFaintsHistory: FaintLogEntry[] = [];
|
||||
public enemyFaintsHistory: FaintLogEntry[] = [];
|
||||
|
||||
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.waveIndex = waveIndex;
|
||||
this.battleType = battleType;
|
||||
|
@ -79,22 +88,7 @@ export default class Battle {
|
|||
this.enemyLevels = battleType !== BattleType.TRAINER
|
||||
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
||||
: trainer?.getPartyLevels(this.waveIndex);
|
||||
this.enemyParty = [];
|
||||
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;
|
||||
this.double = double ?? false;
|
||||
}
|
||||
|
||||
private initBattleSpec(): void {
|
||||
|
@ -105,7 +99,7 @@ export default class Battle {
|
|||
this.battleSpec = spec;
|
||||
}
|
||||
|
||||
private getLevelForWave(): integer {
|
||||
private getLevelForWave(): number {
|
||||
const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex);
|
||||
const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
|
||||
const bossMultiplier = 1.2;
|
||||
|
@ -138,7 +132,7 @@ export default class Battle {
|
|||
return rand / value;
|
||||
}
|
||||
|
||||
getBattlerCount(): integer {
|
||||
getBattlerCount(): number {
|
||||
return this.double ? 2 : 1;
|
||||
}
|
||||
|
||||
|
@ -367,7 +361,13 @@ export default class Battle {
|
|||
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) {
|
||||
return min;
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ export default class 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);
|
||||
if (config.getEnemyParty) {
|
||||
this.enemyParty = config.getEnemyParty(scene);
|
||||
|
@ -408,7 +408,7 @@ export class FixedBattleConfig {
|
|||
public double: boolean;
|
||||
public getTrainer: GetTrainerFunc;
|
||||
public getEnemyParty: GetEnemyPartyFunc;
|
||||
public seedOffsetWaveIndex: integer;
|
||||
public seedOffsetWaveIndex: number;
|
||||
|
||||
setBattleType(battleType: BattleType): FixedBattleConfig {
|
||||
this.battleType = battleType;
|
||||
|
@ -430,7 +430,7 @@ export class FixedBattleConfig {
|
|||
return this;
|
||||
}
|
||||
|
||||
setSeedOffsetWave(seedOffsetWaveIndex: integer): FixedBattleConfig {
|
||||
setSeedOffsetWave(seedOffsetWaveIndex: number): FixedBattleConfig {
|
||||
this.seedOffsetWaveIndex = seedOffsetWaveIndex;
|
||||
return this;
|
||||
}
|
||||
|
@ -476,7 +476,7 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], rand
|
|||
}
|
||||
|
||||
export interface FixedBattleConfigs {
|
||||
[key: integer]: FixedBattleConfig
|
||||
[key: number]: FixedBattleConfig
|
||||
}
|
||||
/**
|
||||
* Youngster/Lass on 5
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const PLAYER_PARTY_MAX_SIZE = 6;
|
|
@ -7,17 +7,17 @@ import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
|
|||
import { StatusEffect } from "./status-effect";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
|
||||
import i18next from "i18next";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase.js";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
|
||||
export enum ArenaTagSide {
|
||||
BOTH,
|
||||
|
@ -786,8 +786,8 @@ class StickyWebTag extends ArenaTrapTag {
|
|||
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
|
||||
if (!cancelled.value) {
|
||||
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
|
||||
const statLevels = new Utils.NumberHolder(-1);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [BattleStat.SPD], statLevels.value));
|
||||
const stages = new Utils.NumberHolder(-1);
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -875,7 +875,7 @@ class TailwindTag extends ArenaTag {
|
|||
// Raise attack by one stage if party member has WIND_RIDER ability
|
||||
if (pokemon.hasAbility(Abilities.WIND_RIDER)) {
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK], 1, true));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -905,6 +905,21 @@ class HappyHourTag extends ArenaTag {
|
|||
}
|
||||
}
|
||||
|
||||
class SafeguardTag extends ArenaTag {
|
||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
||||
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
|
||||
}
|
||||
|
||||
onAdd(arena: Arena): void {
|
||||
arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
|
||||
}
|
||||
|
||||
onRemove(arena: Arena): void {
|
||||
arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||
switch (tagType) {
|
||||
case ArenaTagType.MIST:
|
||||
|
@ -950,6 +965,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
|
|||
return new TailwindTag(turnCount, sourceId, side);
|
||||
case ArenaTagType.HAPPY_HOUR:
|
||||
return new HappyHourTag(turnCount, sourceId, side);
|
||||
case ArenaTagType.SAFEGUARD:
|
||||
return new SafeguardTag(turnCount, sourceId, side);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -788,10 +788,10 @@ export abstract class BattleAnim {
|
|||
targetSprite.pipelineData["tone"] = [ 0.0, 0.0, 0.0, 0.0 ];
|
||||
targetSprite.setAngle(0);
|
||||
if (!this.isHideUser() && userSprite) {
|
||||
userSprite.setVisible(true);
|
||||
this.user?.getSprite().setVisible(true); // using this.user to fix context loss due to isOppAnim swap (#481)
|
||||
}
|
||||
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser())) {
|
||||
targetSprite.setVisible(true);
|
||||
this.target?.getSprite().setVisible(true); // using this.target to fix context loss due to isOppAnim swap (#481)
|
||||
}
|
||||
for (const ms of Object.values(spriteCache).flat()) {
|
||||
if (ms) {
|
||||
|
|
|
@ -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 });
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
|
||||
import { getPokemonNameWithAffix } from "../messages";
|
||||
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
||||
import { Stat, getStatName } from "./pokemon-stat";
|
||||
import { StatusEffect } from "./status-effect";
|
||||
import * as Utils from "../utils";
|
||||
import { ChargeAttr, MoveFlags, allMoves } from "./move";
|
||||
|
@ -9,20 +8,20 @@ import { Type } from "./type";
|
|||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
|
||||
import { TerrainType } from "./terrain";
|
||||
import { WeatherType } from "./weather";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { allAbilities } from "./ability";
|
||||
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import i18next from "#app/plugins/i18n.js";
|
||||
import { CommonAnimPhase } from "#app/phases/common-anim-phase.js";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase.js";
|
||||
import { MovePhase } from "#app/phases/move-phase.js";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase.js";
|
||||
import { StatChangePhase, StatChangeCallback } from "#app/phases/stat-change-phase.js";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { Stat, type BattleStat, type EffectiveStat, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
|
||||
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
|
||||
|
||||
export enum BattlerTagLapseType {
|
||||
FAINT,
|
||||
|
@ -40,13 +39,15 @@ export class BattlerTag {
|
|||
public turnCount: number;
|
||||
public sourceMove: Moves;
|
||||
public sourceId?: number;
|
||||
public isBatonPassable: boolean;
|
||||
|
||||
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) {
|
||||
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number, isBatonPassable: boolean = false) {
|
||||
this.tagType = tagType;
|
||||
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
|
||||
this.turnCount = turnCount;
|
||||
this.sourceMove = sourceMove!; // TODO: is this bang correct?
|
||||
this.sourceId = sourceId;
|
||||
this.isBatonPassable = isBatonPassable;
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
|
@ -97,6 +98,245 @@ export interface TerrainBattlerTag {
|
|||
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.
|
||||
*/
|
||||
|
@ -207,12 +447,12 @@ export class ShellTrapTag extends BattlerTag {
|
|||
|
||||
export class TrappedTag extends BattlerTag {
|
||||
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
|
||||
super(tagType, lapseType, turnCount, sourceMove, sourceId);
|
||||
super(tagType, lapseType, turnCount, sourceMove, sourceId, true);
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
const isGhost = pokemon.isOfType(Type.GHOST);
|
||||
const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED);
|
||||
const isTrapped = pokemon.getTag(TrappedTag);
|
||||
|
||||
return !isTrapped && !isGhost;
|
||||
}
|
||||
|
@ -245,6 +485,23 @@ export class TrappedTag extends BattlerTag {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BattlerTag implementing No Retreat's trapping effect.
|
||||
* This is treated separately from other trapping effects to prevent
|
||||
* Ghost-type Pokemon from being able to reuse the move.
|
||||
* @extends TrappedTag
|
||||
*/
|
||||
class NoRetreatTag extends TrappedTag {
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.NO_RETREAT, BattlerTagLapseType.CUSTOM, 0, Moves.NO_RETREAT, sourceId);
|
||||
}
|
||||
|
||||
/** overrides {@linkcode TrappedTag.apply}, removing the Ghost-type condition */
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
return !pokemon.getTag(TrappedTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BattlerTag that represents the {@link https://bulbapedia.bulbagarden.net/wiki/Flinch Flinch} status condition
|
||||
*/
|
||||
|
@ -310,7 +567,7 @@ export class InterruptedTag extends BattlerTag {
|
|||
*/
|
||||
export class ConfusedTag extends BattlerTag {
|
||||
constructor(turnCount: number, sourceMove: Moves) {
|
||||
super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove);
|
||||
super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove, undefined, true);
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
|
@ -345,9 +602,9 @@ export class ConfusedTag extends BattlerTag {
|
|||
|
||||
// 1/3 chance of hitting self with a 40 base power move
|
||||
if (pokemon.randSeedInt(3) === 0) {
|
||||
const atk = pokemon.getBattleStat(Stat.ATK);
|
||||
const def = pokemon.getBattleStat(Stat.DEF);
|
||||
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
|
||||
const atk = pokemon.getEffectiveStat(Stat.ATK);
|
||||
const def = pokemon.getEffectiveStat(Stat.DEF);
|
||||
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
|
||||
pokemon.damageAndUpdate(damage);
|
||||
pokemon.battleData.hitCount++;
|
||||
|
@ -370,7 +627,7 @@ export class ConfusedTag extends BattlerTag {
|
|||
*/
|
||||
export class DestinyBondTag extends BattlerTag {
|
||||
constructor(sourceMove: Moves, sourceId: number) {
|
||||
super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId);
|
||||
super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -489,7 +746,7 @@ export class SeedTag extends BattlerTag {
|
|||
private sourceIndex: number;
|
||||
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, Moves.LEECH_SEED, sourceId);
|
||||
super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, Moves.LEECH_SEED, sourceId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -750,7 +1007,7 @@ export class OctolockTag extends TrappedTag {
|
|||
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (shouldLapse) {
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], -1));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.DEF, Stat.SPDEF ], -1));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -760,7 +1017,7 @@ export class OctolockTag extends TrappedTag {
|
|||
|
||||
export class AquaRingTag extends BattlerTag {
|
||||
constructor() {
|
||||
super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined);
|
||||
super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined, true);
|
||||
}
|
||||
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
|
@ -792,7 +1049,7 @@ export class AquaRingTag extends BattlerTag {
|
|||
/** Tag used to allow moves that interact with {@link Moves.MINIMIZE} to function */
|
||||
export class MinimizeTag extends BattlerTag {
|
||||
constructor() {
|
||||
super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE, undefined);
|
||||
super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE);
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
|
@ -864,7 +1121,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
return !pokemon.isOfType(Type.GHOST) && !pokemon.findTag(t => t instanceof DamagingTrapTag);
|
||||
return !pokemon.getTag(TrappedTag);
|
||||
}
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
|
@ -1041,6 +1298,13 @@ export class ProtectedTag extends BattlerTag {
|
|||
}
|
||||
}
|
||||
|
||||
/** Base class for `BattlerTag`s that block damaging moves but not status moves */
|
||||
export class DamageProtectedTag extends ProtectedTag {}
|
||||
|
||||
/**
|
||||
* `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact
|
||||
* Used by {@linkcode Moves.SPIKY_SHIELD}
|
||||
*/
|
||||
export class ContactDamageProtectedTag extends ProtectedTag {
|
||||
private damageRatio: number;
|
||||
|
||||
|
@ -1076,7 +1340,11 @@ export class ContactDamageProtectedTag extends ProtectedTag {
|
|||
}
|
||||
}
|
||||
|
||||
export class ContactStatChangeProtectedTag extends ProtectedTag {
|
||||
/**
|
||||
* `BattlerTag` class for moves that block damaging moves and lower enemy stats if the enemy's move makes contact
|
||||
* Used by {@linkcode Moves.KINGS_SHIELD}, {@linkcode Moves.OBSTRUCT}, {@linkcode Moves.SILK_TRAP}
|
||||
*/
|
||||
export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
|
||||
private stat: BattleStat;
|
||||
private levels: number;
|
||||
|
||||
|
@ -1093,7 +1361,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
|
|||
*/
|
||||
loadTag(source: BattlerTag | any): void {
|
||||
super.loadTag(source);
|
||||
this.stat = source.stat as BattleStat;
|
||||
this.stat = source.stat;
|
||||
this.levels = source.levels;
|
||||
}
|
||||
|
||||
|
@ -1104,7 +1372,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
|
|||
const effectPhase = pokemon.scene.getCurrentPhase();
|
||||
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
|
||||
const attacker = effectPhase.getPokemon();
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1132,7 +1400,11 @@ export class ContactPoisonProtectedTag extends ProtectedTag {
|
|||
}
|
||||
}
|
||||
|
||||
export class ContactBurnProtectedTag extends ProtectedTag {
|
||||
/**
|
||||
* `BattlerTag` class for moves that block damaging moves and burn the enemy if the enemy's move makes contact
|
||||
* Used by {@linkcode Moves.BURNING_BULWARK}
|
||||
*/
|
||||
export class ContactBurnProtectedTag extends DamageProtectedTag {
|
||||
constructor(sourceMove: Moves) {
|
||||
super(sourceMove, BattlerTagType.BURNING_BULWARK);
|
||||
}
|
||||
|
@ -1190,7 +1462,7 @@ export class SturdyTag extends BattlerTag {
|
|||
|
||||
export class PerishSongTag extends BattlerTag {
|
||||
constructor(turnCount: number) {
|
||||
super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG);
|
||||
super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG, undefined, true);
|
||||
}
|
||||
|
||||
canAdd(pokemon: Pokemon): boolean {
|
||||
|
@ -1246,7 +1518,7 @@ export class AbilityBattlerTag extends BattlerTag {
|
|||
public ability: Abilities;
|
||||
|
||||
constructor(tagType: BattlerTagType, ability: Abilities, lapseType: BattlerTagLapseType, turnCount: number) {
|
||||
super(tagType, lapseType, turnCount, undefined);
|
||||
super(tagType, lapseType, turnCount);
|
||||
|
||||
this.ability = ability;
|
||||
}
|
||||
|
@ -1331,11 +1603,10 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
|
|||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
const stats = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
|
||||
let highestStat: Stat;
|
||||
stats.map(s => pokemon.getBattleStat(s)).reduce((highestValue: number, value: number, i: number) => {
|
||||
let highestStat: EffectiveStat;
|
||||
EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s)).reduce((highestValue: number, value: number, i: number) => {
|
||||
if (value > highestValue) {
|
||||
highestStat = stats[i];
|
||||
highestStat = EFFECTIVE_STATS[i];
|
||||
return value;
|
||||
}
|
||||
return highestValue;
|
||||
|
@ -1353,7 +1624,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
|
|||
break;
|
||||
}
|
||||
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatName(highestStat) }), null, false, null, true);
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true);
|
||||
}
|
||||
|
||||
onRemove(pokemon: Pokemon): void {
|
||||
|
@ -1423,7 +1694,7 @@ export class TypeImmuneTag extends BattlerTag {
|
|||
public immuneType: Type;
|
||||
|
||||
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) {
|
||||
super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove);
|
||||
super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove, undefined, true);
|
||||
|
||||
this.immuneType = immuneType;
|
||||
}
|
||||
|
@ -1487,7 +1758,7 @@ export class TypeBoostTag extends BattlerTag {
|
|||
|
||||
export class CritBoostTag extends BattlerTag {
|
||||
constructor(tagType: BattlerTagType, sourceMove: Moves) {
|
||||
super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove);
|
||||
super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove, undefined, true);
|
||||
}
|
||||
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
|
@ -1507,6 +1778,25 @@ export class CritBoostTag extends BattlerTag {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag for the effects of Dragon Cheer, which boosts the critical hit ratio of the user's allies.
|
||||
* @extends {CritBoostTag}
|
||||
*/
|
||||
export class DragonCheerTag extends CritBoostTag {
|
||||
/** The types of the user's ally when the tag is added */
|
||||
public typesOnAdd: Type[];
|
||||
|
||||
constructor() {
|
||||
super(BattlerTagType.CRIT_BOOST, Moves.DRAGON_CHEER);
|
||||
}
|
||||
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
this.typesOnAdd = pokemon.getTypes(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class SaltCuredTag extends BattlerTag {
|
||||
private sourceIndex: number;
|
||||
|
||||
|
@ -1560,7 +1850,7 @@ export class CursedTag extends BattlerTag {
|
|||
private sourceIndex: number;
|
||||
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, Moves.CURSE, sourceId);
|
||||
super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, Moves.CURSE, sourceId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1678,25 +1968,25 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag {
|
|||
*/
|
||||
export class StockpilingTag extends BattlerTag {
|
||||
public stockpiledCount: number = 0;
|
||||
public statChangeCounts: { [BattleStat.DEF]: number; [BattleStat.SPDEF]: number } = {
|
||||
[BattleStat.DEF]: 0,
|
||||
[BattleStat.SPDEF]: 0
|
||||
public statChangeCounts: { [Stat.DEF]: number; [Stat.SPDEF]: number } = {
|
||||
[Stat.DEF]: 0,
|
||||
[Stat.SPDEF]: 0
|
||||
};
|
||||
|
||||
constructor(sourceMove: Moves = Moves.NONE) {
|
||||
super(BattlerTagType.STOCKPILING, BattlerTagLapseType.CUSTOM, 1, sourceMove);
|
||||
}
|
||||
|
||||
private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => {
|
||||
const defChange = statChanges[statsChanged.indexOf(BattleStat.DEF)] ?? 0;
|
||||
const spDefChange = statChanges[statsChanged.indexOf(BattleStat.SPDEF)] ?? 0;
|
||||
private onStatStagesChanged: StatStageChangeCallback = (_, statsChanged, statChanges) => {
|
||||
const defChange = statChanges[statsChanged.indexOf(Stat.DEF)] ?? 0;
|
||||
const spDefChange = statChanges[statsChanged.indexOf(Stat.SPDEF)] ?? 0;
|
||||
|
||||
if (defChange) {
|
||||
this.statChangeCounts[BattleStat.DEF]++;
|
||||
this.statChangeCounts[Stat.DEF]++;
|
||||
}
|
||||
|
||||
if (spDefChange) {
|
||||
this.statChangeCounts[BattleStat.SPDEF]++;
|
||||
this.statChangeCounts[Stat.SPDEF]++;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1704,8 +1994,8 @@ export class StockpilingTag extends BattlerTag {
|
|||
super.loadTag(source);
|
||||
this.stockpiledCount = source.stockpiledCount || 0;
|
||||
this.statChangeCounts = {
|
||||
[ BattleStat.DEF ]: source.statChangeCounts?.[ BattleStat.DEF ] ?? 0,
|
||||
[ BattleStat.SPDEF ]: source.statChangeCounts?.[ BattleStat.SPDEF ] ?? 0,
|
||||
[ Stat.DEF ]: source.statChangeCounts?.[ Stat.DEF ] ?? 0,
|
||||
[ Stat.SPDEF ]: source.statChangeCounts?.[ Stat.SPDEF ] ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1725,9 +2015,9 @@ export class StockpilingTag extends BattlerTag {
|
|||
}));
|
||||
|
||||
// Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes.
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(
|
||||
pokemon.scene, pokemon.getBattlerIndex(), true,
|
||||
[BattleStat.SPDEF, BattleStat.DEF], 1, true, false, true, this.onStatsChanged
|
||||
[Stat.SPDEF, Stat.DEF], 1, true, false, true, this.onStatStagesChanged
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1741,15 +2031,15 @@ export class StockpilingTag extends BattlerTag {
|
|||
* one stage for each stack which had successfully changed that particular stat during onAdd.
|
||||
*/
|
||||
onRemove(pokemon: Pokemon): void {
|
||||
const defChange = this.statChangeCounts[BattleStat.DEF];
|
||||
const spDefChange = this.statChangeCounts[BattleStat.SPDEF];
|
||||
const defChange = this.statChangeCounts[Stat.DEF];
|
||||
const spDefChange = this.statChangeCounts[Stat.SPDEF];
|
||||
|
||||
if (defChange) {
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF], -defChange, true, false, true));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF ], -defChange, true, false, true));
|
||||
}
|
||||
|
||||
if (spDefChange) {
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.SPDEF], -spDefChange, true, false, true));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF ], -spDefChange, true, false, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1827,7 +2117,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 {
|
||||
switch (tagType) {
|
||||
case BattlerTagType.RECHARGING:
|
||||
|
@ -1864,6 +2185,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||
return new DrowsyTag();
|
||||
case BattlerTagType.TRAPPED:
|
||||
return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
case BattlerTagType.NO_RETREAT:
|
||||
return new NoRetreatTag(sourceId);
|
||||
case BattlerTagType.BIND:
|
||||
return new BindTag(turnCount, sourceId);
|
||||
case BattlerTagType.WRAP:
|
||||
|
@ -1889,11 +2212,11 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||
case BattlerTagType.SPIKY_SHIELD:
|
||||
return new ContactDamageProtectedTag(sourceMove, 8);
|
||||
case BattlerTagType.KINGS_SHIELD:
|
||||
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.ATK, -1);
|
||||
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
|
||||
case BattlerTagType.OBSTRUCT:
|
||||
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.DEF, -2);
|
||||
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
|
||||
case BattlerTagType.SILK_TRAP:
|
||||
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.SPD, -1);
|
||||
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
|
||||
case BattlerTagType.BANEFUL_BUNKER:
|
||||
return new ContactPoisonProtectedTag(sourceMove);
|
||||
case BattlerTagType.BURNING_BULWARK:
|
||||
|
@ -1923,6 +2246,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||
return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false);
|
||||
case BattlerTagType.CRIT_BOOST:
|
||||
return new CritBoostTag(tagType, sourceMove);
|
||||
case BattlerTagType.DRAGON_CHEER:
|
||||
return new DragonCheerTag();
|
||||
case BattlerTagType.ALWAYS_CRIT:
|
||||
case BattlerTagType.IGNORE_ACCURACY:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
|
||||
|
@ -1955,6 +2280,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||
return new StockpilingTag(sourceMove);
|
||||
case BattlerTagType.OCTOLOCK:
|
||||
return new OctolockTag(sourceId);
|
||||
case BattlerTagType.DISABLED:
|
||||
return new DisabledTag(sourceId);
|
||||
case BattlerTagType.IGNORE_GHOST:
|
||||
return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]);
|
||||
case BattlerTagType.IGNORE_DARK:
|
||||
|
@ -1962,6 +2289,12 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||
case BattlerTagType.GULP_MISSILE_ARROKUDA:
|
||||
case BattlerTagType.GULP_MISSILE_PIKACHU:
|
||||
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:
|
||||
default:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { getPokemonNameWithAffix } from "../messages";
|
||||
import Pokemon, { HitResult } from "../field/pokemon";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { getStatusEffectHealText } from "./status-effect";
|
||||
import * as Utils from "../utils";
|
||||
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
|
||||
import i18next from "i18next";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase.js";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase.js";
|
||||
import { Stat, type BattleStat } from "#app/enums/stat";
|
||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
|
||||
export function getBerryName(berryType: BerryType): string {
|
||||
return i18next.t(`berry:${BerryType[berryType]}.name`);
|
||||
|
@ -35,9 +35,10 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
|||
case BerryType.SALAC:
|
||||
return (pokemon: Pokemon) => {
|
||||
const threshold = new Utils.NumberHolder(0.25);
|
||||
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
|
||||
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold);
|
||||
return pokemon.getHpRatio() < threshold.value && pokemon.summonData.battleStats[battleStat] < 6;
|
||||
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
|
||||
};
|
||||
case BerryType.LANSAT:
|
||||
return (pokemon: Pokemon) => {
|
||||
|
@ -95,10 +96,11 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
if (pokemon.battleData) {
|
||||
pokemon.battleData.berriesEaten.push(berryType);
|
||||
}
|
||||
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
|
||||
const statLevels = new Utils.NumberHolder(1);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value));
|
||||
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||
const statStages = new Utils.NumberHolder(1);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages);
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
|
||||
};
|
||||
case BerryType.LANSAT:
|
||||
return (pokemon: Pokemon) => {
|
||||
|
@ -112,9 +114,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
if (pokemon.battleData) {
|
||||
pokemon.battleData.berriesEaten.push(berryType);
|
||||
}
|
||||
const statLevels = new Utils.NumberHolder(2);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statLevels);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], statLevels.value));
|
||||
const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK);
|
||||
const stages = new Utils.NumberHolder(2);
|
||||
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages);
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value));
|
||||
};
|
||||
case BerryType.LEPPA:
|
||||
return (pokemon: Pokemon) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
import {pokemonEvolutions, SpeciesFormEvolution} from "./pokemon-evolutions";
|
||||
import { pokemonEvolutions, SpeciesFormEvolution } from "./pokemon-evolutions";
|
||||
import i18next from "i18next";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { Species } from "#enums/species";
|
||||
|
@ -46,7 +46,7 @@ export const biomeLinks: BiomeLinks = {
|
|||
[Biome.SEABED]: [ Biome.CAVE, [ Biome.VOLCANO, 3 ] ],
|
||||
[Biome.MOUNTAIN]: [ Biome.VOLCANO, [ Biome.WASTELAND, 2 ], [ Biome.SPACE, 3 ] ],
|
||||
[Biome.BADLANDS]: [ Biome.DESERT, Biome.MOUNTAIN ],
|
||||
[Biome.CAVE]: [ Biome.BADLANDS, Biome.LAKE [ Biome.LABORATORY, 2 ] ],
|
||||
[Biome.CAVE]: [ Biome.BADLANDS, Biome.LAKE, [ Biome.LABORATORY, 2 ] ],
|
||||
[Biome.DESERT]: [ Biome.RUINS, [ Biome.CONSTRUCTION_SITE, 2 ] ],
|
||||
[Biome.ICE_CAVE]: Biome.SNOWY_FOREST,
|
||||
[Biome.MEADOW]: [ Biome.PLAINS, Biome.FAIRY_CAVE ],
|
||||
|
@ -7666,7 +7666,7 @@ export function initBiomes() {
|
|||
if (biome === Biome.END) {
|
||||
const biomeList = Object.keys(Biome).filter(key => !isNaN(Number(key)));
|
||||
biomeList.pop(); // Removes Biome.END from the list
|
||||
const randIndex = Utils.randInt(biomeList.length, 2); // Will never be Biome.TOWN or Biome.PLAINS
|
||||
const randIndex = Utils.randInt(biomeList.length, 1); // Will never be Biome.TOWN
|
||||
biome = Biome[biomeList[randIndex]];
|
||||
}
|
||||
const linkedBiomes: (Biome | [ Biome, integer ])[] = Array.isArray(biomeLinks[biome])
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import * as Utils from "../utils";
|
||||
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 Pokemon, { PokemonMove } from "#app/field/pokemon.js";
|
||||
import { BattleType, FixedBattleConfig } from "#app/battle.js";
|
||||
import Trainer, { TrainerVariant } from "#app/field/trainer.js";
|
||||
import { GameMode } from "#app/game-mode.js";
|
||||
import Pokemon, { PokemonMove } from "#app/field/pokemon";
|
||||
import { BattleType, FixedBattleConfig } from "#app/battle";
|
||||
import Trainer, { TrainerVariant } from "#app/field/trainer";
|
||||
import { GameMode } from "#app/game-mode";
|
||||
import { Type } from "./type";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
import { Species } from "#enums/species";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { Nature } from "./nature";
|
||||
import { Moves } from "#app/enums/moves.js";
|
||||
import { TypeColor, TypeShadow } from "#app/enums/color.js";
|
||||
import { Gender } from "./gender";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { TypeColor, TypeShadow } from "#app/enums/color";
|
||||
import { pokemonEvolutions } from "./pokemon-evolutions";
|
||||
import { pokemonFormChanges } from "./pokemon-forms";
|
||||
|
||||
|
@ -659,7 +658,6 @@ export class FreshStartChallenge extends Challenge {
|
|||
pokemon.luck = 0; // No luck
|
||||
pokemon.shiny = false; // Not shiny
|
||||
pokemon.variant = 0; // Not shiny
|
||||
pokemon.gender = Gender.MALE; // Starters default to male
|
||||
pokemon.formIndex = 0; // Froakie should be base form
|
||||
pokemon.ivs = [10, 10, 10, 10, 10, 10]; // Default IVs of 10 for all stats
|
||||
return true;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { BattleSpec } from "#enums/battle-spec";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import {trainerConfigs} from "./trainer-config";
|
||||
import { trainerConfigs } from "./trainer-config";
|
||||
|
||||
export interface TrainerTypeMessages {
|
||||
encounter?: string | string[],
|
||||
|
@ -707,6 +707,20 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
|||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.ROOD]: [
|
||||
{
|
||||
encounter: [
|
||||
"dialogue:rood.encounter.1",
|
||||
"dialogue:rood.encounter.2",
|
||||
"dialogue:rood.encounter.3",
|
||||
],
|
||||
victory: [
|
||||
"dialogue:rood.victory.1",
|
||||
"dialogue:rood.victory.2",
|
||||
"dialogue:rood.victory.3",
|
||||
]
|
||||
}
|
||||
],
|
||||
[TrainerType.FLARE_GRUNT]: [
|
||||
{
|
||||
encounter: [
|
||||
|
@ -1555,8 +1569,7 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
|
|||
"dialogue:roark.victory.1",
|
||||
"dialogue:roark.victory.2",
|
||||
"dialogue:roark.victory.3",
|
||||
"dialogue:roark.victory.4",
|
||||
"dialogue:roark.victory.5"
|
||||
"dialogue:roark.victory.4"
|
||||
],
|
||||
defeat: [
|
||||
"dialogue:roark.defeat.1",
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|