mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 15:32:18 +02:00
Fixed test
This commit is contained in:
commit
1018e1f7ff
@ -1,6 +1,19 @@
|
|||||||
/** @type {import('dependency-cruiser').IConfiguration} */
|
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
forbidden: [
|
forbidden: [
|
||||||
|
{
|
||||||
|
name: "no-non-type-@type-exports",
|
||||||
|
severity: "error",
|
||||||
|
comment:
|
||||||
|
"Files in @types should not export anything but types and interfaces. " +
|
||||||
|
"The folder is intended to house imports that are removed at runtime, " +
|
||||||
|
"and thus should not contain anything with a bearing on runtime code.",
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
path: "(^|/)src/@types",
|
||||||
|
dependencyTypesNot: ["type-only"],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "only-type-imports",
|
name: "only-type-imports",
|
||||||
severity: "error",
|
severity: "error",
|
||||||
@ -310,7 +323,7 @@ module.exports = {
|
|||||||
conditionNames: ["import", "require", "node", "default", "types"],
|
conditionNames: ["import", "require", "node", "default", "types"],
|
||||||
/*
|
/*
|
||||||
The extensions, by default are the same as the ones dependency-cruiser
|
The extensions, by default are the same as the ones dependency-cruiser
|
||||||
can access (run `npx depcruise --info` to see which ones that are in
|
can access (run `pnpm exec depcruise --info` to see which ones that are in
|
||||||
_your_ environment). If that list is larger than you need you can pass
|
_your_ environment). If that list is larger than you need you can pass
|
||||||
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
|
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
|
||||||
up module resolution, which is the most expensive step.
|
up module resolution, which is the most expensive step.
|
||||||
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -68,8 +68,8 @@ Do the reviewers need to do something special in order to test your changes?
|
|||||||
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
- [ ] The PR is self-contained and cannot be split into smaller PRs?
|
||||||
- [ ] Have I provided a clear explanation of the changes?
|
- [ ] Have I provided a clear explanation of the changes?
|
||||||
- [ ] Have I tested the changes manually?
|
- [ ] Have I tested the changes manually?
|
||||||
- [ ] Are all unit tests still passing? (`npm run test:silent`)
|
- [ ] Are all unit tests still passing? (`pnpm test:silent`)
|
||||||
- [ ] Have I created new automated tests (`npm run test:create`) or updated existing tests related to the PR's changes?
|
- [ ] Have I created new automated tests (`pnpm test:create`) or updated existing tests related to the PR's changes?
|
||||||
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
|
- [ ] Have I provided screenshots/videos of the changes (if applicable)?
|
||||||
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
|
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
|
||||||
|
|
||||||
|
14
.github/workflows/deploy-beta.yml
vendored
14
.github/workflows/deploy-beta.yml
vendored
@ -18,15 +18,24 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
ref: ${{ vars.BETA_DEPLOY_BRANCH || 'beta'}}
|
ref: ${{ vars.BETA_DEPLOY_BRANCH || 'beta'}}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: pnpm i
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build:beta
|
run: pnpm build:beta
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
|
|
||||||
- name: Set up SSH
|
- name: Set up SSH
|
||||||
run: |
|
run: |
|
||||||
mkdir ~/.ssh
|
mkdir ~/.ssh
|
||||||
@ -34,6 +43,7 @@ jobs:
|
|||||||
echo "${{ secrets.BETA_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
echo "${{ secrets.BETA_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/*
|
chmod 600 ~/.ssh/*
|
||||||
ssh-keyscan -H ${{ secrets.BETA_SSH_HOST }} >> ~/.ssh/known_hosts
|
ssh-keyscan -H ${{ secrets.BETA_SSH_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
- name: Deploy build on server
|
- name: Deploy build on server
|
||||||
run: |
|
run: |
|
||||||
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.BETA_SSH_USER }}@${{ secrets.BETA_SSH_HOST }}:${{ secrets.BETA_DESTINATION_DIR }}
|
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.BETA_SSH_USER }}@${{ secrets.BETA_SSH_HOST }}:${{ secrets.BETA_DESTINATION_DIR }}
|
||||||
|
15
.github/workflows/deploy.yml
vendored
15
.github/workflows/deploy.yml
vendored
@ -16,15 +16,24 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: pnpm i
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: pnpm build
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
|
|
||||||
- name: Set up SSH
|
- name: Set up SSH
|
||||||
if: github.event_name == 'push' && github.ref_name == 'main'
|
if: github.event_name == 'push' && github.ref_name == 'main'
|
||||||
run: |
|
run: |
|
||||||
@ -33,11 +42,13 @@ jobs:
|
|||||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/*
|
chmod 600 ~/.ssh/*
|
||||||
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
|
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
- name: Deploy build on server
|
- name: Deploy build on server
|
||||||
if: github.event_name == 'push' && github.ref_name == 'main'
|
if: github.event_name == 'push' && github.ref_name == 'main'
|
||||||
run: |
|
run: |
|
||||||
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }}
|
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }}
|
||||||
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
|
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
|
||||||
|
|
||||||
- name: Purge Cloudflare Cache
|
- name: Purge Cloudflare Cache
|
||||||
if: github.event_name == 'push' && github.ref_name == 'main'
|
if: github.event_name == 'push' && github.ref_name == 'main'
|
||||||
id: purge-cache
|
id: purge-cache
|
||||||
|
9
.github/workflows/github-pages.yml
vendored
9
.github/workflows/github-pages.yml
vendored
@ -34,6 +34,11 @@ jobs:
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y git openssh-client
|
sudo apt install -y git openssh-client
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
- name: Setup Node 22.14.1
|
- name: Setup Node 22.14.1
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@ -50,13 +55,13 @@ jobs:
|
|||||||
working-directory: ${{env.api-dir}}
|
working-directory: ${{env.api-dir}}
|
||||||
run: |
|
run: |
|
||||||
cd pokerogue_docs
|
cd pokerogue_docs
|
||||||
npm ci
|
pnpm i
|
||||||
|
|
||||||
- name: Generate Typedoc docs
|
- name: Generate Typedoc docs
|
||||||
working-directory: ${{env.api-dir}}
|
working-directory: ${{env.api-dir}}
|
||||||
run: |
|
run: |
|
||||||
cd pokerogue_docs
|
cd pokerogue_docs
|
||||||
npm run docs -- --out /tmp/docs --githubPages false --entryPoints ./src/
|
pnpm exec typedoc --out /tmp/docs --githubPages false --entryPoints ./src/
|
||||||
|
|
||||||
- name: Commit & Push docs
|
- name: Commit & Push docs
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
16
.github/workflows/linting.yml
vendored
16
.github/workflows/linting.yml
vendored
@ -23,20 +23,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'npm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install Node.js dependencies
|
- name: Install Node.js dependencies
|
||||||
run: npm ci
|
run: pnpm i
|
||||||
|
|
||||||
- name: Run ESLint
|
|
||||||
run: npm run eslint-ci
|
|
||||||
|
|
||||||
- name: Lint with Biome
|
- name: Lint with Biome
|
||||||
run: npm run biome-ci
|
run: pnpm biome-ci
|
||||||
|
|
||||||
- name: Check dependencies with depcruise
|
- name: Check dependencies with depcruise
|
||||||
run: npm run depcruise
|
run: pnpm depcruise
|
14
.github/workflows/test-shard-template.yml
vendored
14
.github/workflows/test-shard-template.yml
vendored
@ -28,12 +28,20 @@ jobs:
|
|||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: "npm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node.js dependencies
|
- name: Install Node.js dependencies
|
||||||
run: npm ci
|
run: pnpm i
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npx vitest --project ${{ inputs.project }} --no-isolate --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }}
|
run: pnpm exec vitest --project ${{ inputs.project }} --no-isolate --shard=${{ inputs.shard }}/${{ inputs.totalShards }} ${{ !runner.debug && '--silent' || '' }}
|
||||||
|
11
.github/workflows/tests.yml
vendored
11
.github/workflows/tests.yml
vendored
@ -1,16 +1,14 @@
|
|||||||
name: Tests
|
name: Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Trigger the workflow on push or pull request,
|
|
||||||
# but only for the main branch
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main # Trigger on push events to the main branch
|
- main
|
||||||
- beta # Trigger on push events to the beta branch
|
- beta
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main # Trigger on pull request events targeting the main branch
|
- main
|
||||||
- beta # Trigger on pull request events targeting the beta branch
|
- beta
|
||||||
merge_group:
|
merge_group:
|
||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
|
|
||||||
@ -24,6 +22,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36
|
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
|
164
CONTRIBUTING.md
Normal file
164
CONTRIBUTING.md
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# Contributing to PokéRogue
|
||||||
|
|
||||||
|
Thank you for taking the time to contribute, every little bit helps. This project is entirely open-source and unmonetized - community contributions are what keep it alive!
|
||||||
|
|
||||||
|
Please make sure you understand everything relevant to your changes from the [Table of Contents](#-table-of-contents), and absolutely *feel free to reach out reach out in the **#dev-corner** channel on [Discord](https://discord.gg/pokerogue)*. We are here to help and the better you understand what you're working on, the easier it will be for it to find its way into the game.
|
||||||
|
|
||||||
|
## 📄 Table of Contents
|
||||||
|
|
||||||
|
- [Development Basics](#️-development-basics)
|
||||||
|
- [Environment Setup](#-environment-setup)
|
||||||
|
- [Getting Started](#-getting-started)
|
||||||
|
- [Documentation](#-documentation)
|
||||||
|
- [Testing Your Changes](#-testing-your-changes)
|
||||||
|
- [Localization](#-localization)
|
||||||
|
- [Development Save File (Unlock Everything)](#-development-save-file)
|
||||||
|
|
||||||
|
## 🛠️ Development Basics
|
||||||
|
|
||||||
|
PokéRogue is built with [Typescript](https://www.typescriptlang.org/docs/handbook/intro.html), using the [Phaser](https://github.com/phaserjs/phaser) game framework.
|
||||||
|
|
||||||
|
If you have the motivation and experience with Typescript/Javascript (or are willing to learn) you can contribute by forking the repository and making pull requests with contributions.
|
||||||
|
|
||||||
|
## 💻 Environment Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- node: >=22.14.0 - [manage with pnpm](https://pnpm.io/cli/env) | [manage with fnm](https://github.com/Schniz/fnm) | [manage with nvm](https://github.com/nvm-sh/nvm)
|
||||||
|
- pnpm: 10.x - [how to install](https://pnpm.io/installation) (not recommended to install via `npm` on Windows native) | [alternate method - volta.sh](https://volta.sh/)
|
||||||
|
|
||||||
|
### Running Locally
|
||||||
|
|
||||||
|
1. Clone the repo and in the root directory run `pnpm install`
|
||||||
|
- *if you run into any errors, reach out in the **#dev-corner** channel on Discord*
|
||||||
|
2. Run `pnpm start:dev` to locally run the project at `localhost:8000`
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
Check out our [in-depth file](./docs/linting.md) on linting and formatting!
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
A great way to develop an understanding of how the project works is to look at test cases (located in [the `test` folder](./test/)).
|
||||||
|
Tests show you both how things are supposed to work and the expected "flow" to get from point A to point B in battles.
|
||||||
|
|
||||||
|
*This is a big project and you will be confused at times - never be afraid to reach out and ask questions in **#dev-corner***!
|
||||||
|
|
||||||
|
### Where to Look
|
||||||
|
|
||||||
|
Once you have your feet under you, check out the [Issues](https://github.com/pagefaultgames/pokerogue/issues) page to see how you can help us!
|
||||||
|
Most issues are bugs and are labeled with their area, such as `Move`, `Ability`, `UI/UX`, etc. There are also priority labels:
|
||||||
|
- `P0`: Completely gamebreaking (very rare)
|
||||||
|
- `P1`: Major - Game crash
|
||||||
|
- `P2`: Minor - Incorrect (but non-crashing) move/ability/interaction
|
||||||
|
- `P3`: No gameplay impact - typo, minor graphical error, etc.
|
||||||
|
|
||||||
|
Also under issues, you can take a look at the [List of Partial / Unimplemented Moves and Abilities](https://github.com/pagefaultgames/pokerogue/issues/3503) and the [Bug Board](https://github.com/orgs/pagefaultgames/projects/3) (the latter is essentially the same as the issues page but easier to work with).
|
||||||
|
|
||||||
|
You are free to comment on any issue so that you may be assigned to it and we can avoid multiple people working on the same thing.
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html).
|
||||||
|
For information on enemy AI, check out the [enemy-ai.md](./docs/enemy-ai.md) file.
|
||||||
|
For detailed guidelines on documenting your code, refer to the [comments.md](./docs/comments.md) file.
|
||||||
|
|
||||||
|
Again, if you have unanswered questions please feel free to ask!
|
||||||
|
|
||||||
|
## 🧪 Testing Your Changes
|
||||||
|
|
||||||
|
You've just made a change - how can you check if it works? You have two areas to hit:
|
||||||
|
|
||||||
|
### 1 - Manual Testing
|
||||||
|
|
||||||
|
> This will likely be your first stop. After making a change, you'll want to spin the game up and make sure everything is as you expect. To do this, you will need a way to manipulate the game to produce the situation you're looking to test.
|
||||||
|
|
||||||
|
[src/overrides.ts](../src/overrides.ts) contains overrides for most values you'll need to change for testing, controlled through the `overrides` object.
|
||||||
|
For example, here is how you could test a scenario where the player Pokemon has the ability Drought and the enemy Pokemon has the move Water Gun:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const overrides = {
|
||||||
|
ABILITY_OVERRIDE: AbilityId.DROUGHT,
|
||||||
|
OPP_MOVESET_OVERRIDE: MoveId.WATER_GUN,
|
||||||
|
} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Read through `src/overrides.ts` file to find the override that fits your needs - there are a lot of them!
|
||||||
|
If the situation you're trying to test can't be created using existing overrides (or with the [Dev Save](#-development-save-file)), reach out in **#dev-corner**.
|
||||||
|
You can get help testing your specific changes, and you might have found a new override that needs to be created!
|
||||||
|
|
||||||
|
### 2 - Automatic Testing
|
||||||
|
|
||||||
|
> PokéRogue uses [Vitest](https://vitest.dev/) for automatic testing. Checking out the existing tests in the [test](./test/) folder is a great way to understand how this works, and to get familiar with the project as a whole.
|
||||||
|
|
||||||
|
To make sure your changes didn't break any existing test cases, run `pnpm test:silent` in your terminal. You can also provide an argument to the command: to run only the Dancer (ability) tests, you could write `pnpm test:silent dancer`.
|
||||||
|
- __Note that passing all test cases does *not* guarantee that everything is working properly__. The project does not have complete regression testing.
|
||||||
|
|
||||||
|
Most non-trivial changes (*especially bug fixes*) should come along with new test cases.
|
||||||
|
- To make a new test file, run `pnpm test:create` and follow the prompts. If the move/ability/etc. you're modifying already has tests, simply add new cases to the end of the file. As mentioned before, the easiest way to get familiar with the system and understand how to write your own tests is simply to read the existing tests, particularly ones similar to the tests you intend to write.
|
||||||
|
- Ensure that new tests:
|
||||||
|
- Are deterministic. In other words, the test should never pass or fail when it shouldn't due to randomness. This involves primarily ensuring that abilities and moves are never randomly selected.
|
||||||
|
- As much as possible, are unit tests. If you have made two distinct changes, they should be tested in two separate cases.
|
||||||
|
- Test edge cases. A good strategy is to think of edge cases beforehand and create tests for them using `it.todo`. Once the edge case has been handled, you can remove the `todo` marker.
|
||||||
|
|
||||||
|
## 📜 Localization
|
||||||
|
|
||||||
|
The project intends for all text to be localized. That is, strings are pulled from translation files using keys (depending on the current language) and *never* hardcoded as a particular language. Note that there is a PDF in a message pinned in **#dev-corner** which gives the following information in greater detail.
|
||||||
|
|
||||||
|
### Setting Up and Updating the Locales Submodule
|
||||||
|
> The locales (translation) files are set up as a git submodule. A project-in-a-project, if you will.
|
||||||
|
|
||||||
|
To fetch translations when you first start development in your fork or to update them on your local branch, run:
|
||||||
|
```bash
|
||||||
|
git submodule update --progress --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
### How Localizations Work
|
||||||
|
> This project uses the [i18next](https://www.i18next.com/) library to integrate translations from public/locales
|
||||||
|
into the source code based on the user's settings or location. The basic process for
|
||||||
|
fetching translated text is as follows:
|
||||||
|
1. The source code fetches text by a given key, e.g.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
i18next.t("fileName:keyName", { arg1: "Hello", arg2: "an example", ... })
|
||||||
|
```
|
||||||
|
2. The game looks up the key in the corresponding JSON file in the user's
|
||||||
|
language, e.g.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// from "en/file-name.json"...
|
||||||
|
"keyName": "{{arg1}}! This is {{arg2}} of translated text!"
|
||||||
|
```
|
||||||
|
If the key doesn't exist for the user's language, the game will default to the
|
||||||
|
corresponding English key (in the case of LATAM Spanish, it will first default to ES Spanish).
|
||||||
|
|
||||||
|
3. The game shows the text to the user:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
"Hello! This is an example of translated text!"
|
||||||
|
```
|
||||||
|
### Adding Translated Text
|
||||||
|
> If your feature involves new or modified text in any form, then you will be modifying the [locales](https://github.com/pagefaultgames/pokerogue-locales) repository. ***Never hardcode new text in any language!***
|
||||||
|
|
||||||
|
The workflow is:
|
||||||
|
|
||||||
|
1. Make a pull request to the main repository for your new feature.
|
||||||
|
If this feature requires new text, the text should be integrated into the code with a new i18next key pointing to where you plan to add it into the pokerogue-locales repository.
|
||||||
|
|
||||||
|
2. Make another pull request -- this time to the [pokerogue-locales](https://github.com/pagefaultgames/pokerogue-locales)
|
||||||
|
repository -- adding a new entry to the English locale with text for each key
|
||||||
|
you added to your main PR. You *only* need to add the English key and value - the translation team will handle the rest.
|
||||||
|
|
||||||
|
3. If your feature is pulled from the mainline Pokémon games (e.g. a Move or Ability implementation), add a source link for any added text within the locale PR.
|
||||||
|
[Poké Corpus](https://abcboy101.github.io/poke-corpus) is a great resource for finding text from the latest mainline games; otherwise, a YouTube video link showing the text in mainline is sufficient.
|
||||||
|
|
||||||
|
4. Ping @lugiadrien in **#dev-corner** or the current callout thread to make sure your locales PR is seen.
|
||||||
|
It'll be merged into the locales repository after any necessary corrections, at which point you can test it in your main PR (after updating locales from remote)
|
||||||
|
|
||||||
|
5. The Dev team will approve your main PR, and your changes will be in the beta environment!
|
||||||
|
|
||||||
|
## 😈 Development Save File
|
||||||
|
> Some issues may require you to have unlocks on your save file which go beyond normal overrides. For this reason, the repository contains a [save file](../test/testUtils/saves/everything.psrv) with _everything_ unlocked (even ones not legitimately obtainable, like unimplemented variant shinies).
|
||||||
|
|
||||||
|
1. Start the game up locally and navigate to `Menu -> Manage Data -> Import Data`
|
||||||
|
2. Select [everything.prsv](test/testUtils/saves/everything.prsv) (`test/testUtils/saves/everything.prsv`) and confirm.
|
42
README.md
42
README.md
@ -4,47 +4,7 @@ PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite
|
|||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
## 🛠️ Development
|
See [CONTRIBUTING.md](./CONTRIBUTING.md), this includes instructions on how to set up the game locally.
|
||||||
|
|
||||||
If you have the motivation and experience with Typescript/Javascript (or are willing to learn) please feel free to fork the repository and make pull requests with contributions. If you don't know what to work on but want to help, reference the below **To-Do** section or the **#feature-vote** channel in the discord.
|
|
||||||
|
|
||||||
### 💻 Environment Setup
|
|
||||||
|
|
||||||
#### Prerequisites
|
|
||||||
|
|
||||||
- node: 22.14.0
|
|
||||||
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
|
||||||
|
|
||||||
#### Running Locally
|
|
||||||
|
|
||||||
1. Clone the repo and in the root directory run `npm install`
|
|
||||||
- *if you run into any errors, reach out in the **#dev-corner** channel in discord*
|
|
||||||
2. Run `npm run start:dev` to locally run the project in `localhost:8000`
|
|
||||||
|
|
||||||
#### Linting
|
|
||||||
|
|
||||||
We're using Biome as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run biome` script. To view the complete rules, check out the [biome.jsonc](./biome.jsonc) file.
|
|
||||||
|
|
||||||
### 📚 Documentation
|
|
||||||
|
|
||||||
You can find the auto-generated documentation [here](https://pagefaultgames.github.io/pokerogue/main/index.html).
|
|
||||||
For information on enemy AI, check out the [enemy-ai.md](./docs/enemy-ai.md) file.
|
|
||||||
For detailed guidelines on documenting your code, refer to the [comments.md](./docs/comments.md) file.
|
|
||||||
|
|
||||||
### ❔ FAQ
|
|
||||||
|
|
||||||
**How do I test a new _______?**
|
|
||||||
|
|
||||||
- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing
|
|
||||||
|
|
||||||
**How do I retrieve the translations?**
|
|
||||||
|
|
||||||
- The translations were moved to the [dedicated translation repository](https://github.com/pagefaultgames/pokerogue-locales) and are now applied as a submodule in this project.
|
|
||||||
- The command to retrieve the translations is `git submodule update --init --recursive`. If you still struggle to get it working, please reach out to #dev-corner channel in Discord.
|
|
||||||
|
|
||||||
## 🪧 To Do
|
|
||||||
|
|
||||||
Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us!
|
|
||||||
|
|
||||||
# 📝 Credits
|
# 📝 Credits
|
||||||
>
|
>
|
||||||
|
146
biome.jsonc
146
biome.jsonc
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
@ -10,35 +10,47 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"useEditorconfig": true,
|
"useEditorconfig": true,
|
||||||
"indentStyle": "space",
|
"indentStyle": "space",
|
||||||
"ignore": ["src/enums/*", "src/data/balance/*"],
|
"includes": ["**", "!**/src/enums/**/*", "!**/src/data/balance/**/*"],
|
||||||
"lineWidth": 120
|
"lineWidth": 120
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": true,
|
"ignoreUnknown": true,
|
||||||
// Adding folders to the ignore list is GREAT for performance because it prevents biome from descending into them
|
// Adding folders to the ignore list is GREAT for performance because it prevents biome from descending into them
|
||||||
// and having to verify whether each individual file is ignored
|
// and having to verify whether each individual file is ignored
|
||||||
"ignore": [
|
"includes": [
|
||||||
"**/*.d.ts",
|
"**",
|
||||||
"dist/*",
|
"!**/*.d.ts",
|
||||||
"build/*",
|
"!**/dist/**/*",
|
||||||
"coverage/*",
|
"!**/build/**/*",
|
||||||
"public/*",
|
"!**/coverage/**/*",
|
||||||
".github/*",
|
"!**/public/**/*",
|
||||||
"node_modules/*",
|
"!**/.github/**/*",
|
||||||
".vscode/*",
|
"!**/node_modules/**/*",
|
||||||
"*.css", // TODO?
|
"!**/.vscode/**/*",
|
||||||
"*.html", // TODO?
|
"!**/typedoc/**/*",
|
||||||
// TODO: these files are too big and complex, ignore them until their respective refactors
|
// TODO: lint css and html?
|
||||||
"src/data/moves/move.ts",
|
"!**/*.css",
|
||||||
|
"!**/*.html",
|
||||||
// this file is just too big:
|
// TODO: enable linting this file
|
||||||
"src/data/balance/tms.ts"
|
"!**/src/data/moves/move.ts",
|
||||||
|
// this file is too big
|
||||||
|
"!**/src/data/balance/tms.ts"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// While it'd be nice to enable consistent sorting, enabling this causes issues due to circular import resolution order
|
// TODO: Configure and enable import sorting
|
||||||
// TODO: Remove if we ever get down to 0 circular imports
|
"assist": {
|
||||||
"organizeImports": { "enabled": false },
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": {
|
||||||
|
"level": "off",
|
||||||
|
"options": {
|
||||||
|
"groups": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
@ -48,10 +60,15 @@
|
|||||||
"noUnusedVariables": "error",
|
"noUnusedVariables": "error",
|
||||||
"noSwitchDeclarations": "error",
|
"noSwitchDeclarations": "error",
|
||||||
"noVoidTypeReturn": "error",
|
"noVoidTypeReturn": "error",
|
||||||
"noUnusedImports": "error"
|
"noUnusedImports": {
|
||||||
|
"level": "error",
|
||||||
|
"fix": "safe"
|
||||||
|
},
|
||||||
|
"noUnusedFunctionParameters": "error",
|
||||||
|
"noUnusedLabels": "error",
|
||||||
|
"noPrivateImports": "error"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"noVar": "error",
|
|
||||||
"useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome
|
"useEnumInitializers": "off", // large enums like Moves/Species would make this cumbersome
|
||||||
"useBlockStatements": "error",
|
"useBlockStatements": "error",
|
||||||
"useConst": "error",
|
"useConst": "error",
|
||||||
@ -59,11 +76,31 @@
|
|||||||
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files
|
"noNonNullAssertion": "off", // TODO: Turn this on ASAP and fix all non-null assertions in non-test files
|
||||||
"noParameterAssign": "off",
|
"noParameterAssign": "off",
|
||||||
"useExponentiationOperator": "off", // Too typo-prone and easy to mixup with standard multiplication (* vs **)
|
"useExponentiationOperator": "off", // Too typo-prone and easy to mixup with standard multiplication (* vs **)
|
||||||
"useDefaultParameterLast": "off", // TODO: Fix spots in the codebase where this flag would be triggered, and then enable
|
"useDefaultParameterLast": {
|
||||||
|
// TODO: Fix spots in the codebase where this flag would be triggered
|
||||||
|
// and then set to "error" and re-enable the fixer
|
||||||
|
"level": "warn",
|
||||||
|
"fix": "none"
|
||||||
|
},
|
||||||
"useSingleVarDeclarator": "off",
|
"useSingleVarDeclarator": "off",
|
||||||
"useNodejsImportProtocol": "off",
|
"useNodejsImportProtocol": "off",
|
||||||
"useTemplate": "off", // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation
|
"useTemplate": "off", // string concatenation is faster: https://stackoverflow.com/questions/29055518/are-es6-template-literals-faster-than-string-concatenation
|
||||||
"noNamespaceImport": "error"
|
"useAsConstAssertion": "error",
|
||||||
|
"noUnusedTemplateLiteral": "error",
|
||||||
|
"useNumberNamespace": "error",
|
||||||
|
"noInferrableTypes": "error",
|
||||||
|
"noUselessElse": "error",
|
||||||
|
"noRestrictedTypes": {
|
||||||
|
"level": "error",
|
||||||
|
"options": {
|
||||||
|
"types": {
|
||||||
|
"integer": {
|
||||||
|
"message": "This is an alias for 'number' that can provide false impressions of what values can actually be contained in this variable. Use 'number' instead.",
|
||||||
|
"use": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noDoubleEquals": "error",
|
"noDoubleEquals": "error",
|
||||||
@ -77,45 +114,62 @@
|
|||||||
"noImplicitAnyLet": "warn", // TODO: Refactor and make this an error
|
"noImplicitAnyLet": "warn", // TODO: Refactor and make this an error
|
||||||
"noRedeclare": "info", // TODO: Refactor and make this an error
|
"noRedeclare": "info", // TODO: Refactor and make this an error
|
||||||
"noGlobalIsNan": "off",
|
"noGlobalIsNan": "off",
|
||||||
"noAsyncPromiseExecutor": "warn" // TODO: Refactor and make this an error
|
"noAsyncPromiseExecutor": "warn", // TODO: Refactor and make this an error
|
||||||
|
"noVar": "error",
|
||||||
|
"noDocumentCookie": "off" // Firefox has minimal support for the "Cookie Store API"
|
||||||
},
|
},
|
||||||
"complexity": {
|
"complexity": {
|
||||||
"noExcessiveCognitiveComplexity": "warn", // TODO: Refactor and make this an error
|
"noExcessiveCognitiveComplexity": "info", // TODO: Refactor and make this an error
|
||||||
"useLiteralKeys": "off",
|
"useLiteralKeys": "off",
|
||||||
"noForEach": "off", // Foreach vs for of is not that simple.
|
"noForEach": "off", // Foreach vs for of is not that simple.
|
||||||
"noUselessSwitchCase": "off", // Explicit > Implicit
|
"noUselessSwitchCase": "off", // Explicit > Implicit
|
||||||
"noUselessConstructor": "error",
|
"noUselessConstructor": "error",
|
||||||
"noBannedTypes": "warn" // TODO: Refactor and make this an error
|
"noBannedTypes": "warn", // TODO: Refactor and make this an error
|
||||||
|
"noThisInStatic": "error",
|
||||||
|
"noUselessThisAlias": "error",
|
||||||
|
"noUselessTernary": "error"
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"noNamespaceImport": "error",
|
||||||
|
"noDelete": "error"
|
||||||
},
|
},
|
||||||
"nursery": {
|
"nursery": {
|
||||||
"noRestrictedTypes": {
|
"useAdjacentGetterSetter": "error",
|
||||||
"level": "error",
|
"noConstantBinaryExpression": "error",
|
||||||
"options": {
|
"noTsIgnore": "error",
|
||||||
"types": {
|
"noAwaitInLoop": "warn",
|
||||||
"integer": {
|
"useJsonImportAttribute": "off", // "Import attributes are only supported when the '--module' option is set to 'esnext', 'node18', 'nodenext', or 'preserve'. ts(2823)"
|
||||||
"message": "This is an alias for 'number' that can provide false impressions of what values can actually be contained in this variable. Use 'number' instead.",
|
"useIndexOf": "error",
|
||||||
"use": "number"
|
"useObjectSpread": "error",
|
||||||
}
|
"useNumericSeparators": "off", // TODO: enable?
|
||||||
}
|
"useIterableCallbackReturn": "warn", // TODO: refactor and make "error"
|
||||||
}
|
"noShadow": "warn" // TODO: refactor and make "error"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": { "quoteStyle": "double", "arrowParentheses": "asNeeded" }
|
"formatter": {
|
||||||
|
"quoteStyle": "double",
|
||||||
|
"arrowParentheses": "asNeeded"
|
||||||
|
},
|
||||||
|
"parser": {
|
||||||
|
"jsxEverywhere": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"include": ["test/**/*.test.ts"],
|
"includes": ["**/test/**/*.test.ts"],
|
||||||
"javascript": { "globals": [] },
|
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"performance": {
|
"performance": {
|
||||||
"noDelete": "off" // TODO: evaluate if this is necessary for the test(s) to function
|
"noDelete": "off", // TODO: evaluate if this is necessary for the test(s) to function
|
||||||
|
"noNamespaceImport": "off" // this is required for `vi.spyOn` to work in some tests
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"noNamespaceImport": "off" // this is required for `vi.spyOn` to work in some tests
|
"noNonNullAssertion": "off"
|
||||||
|
},
|
||||||
|
"nursery": {
|
||||||
|
"noFloatingPromises": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +177,7 @@
|
|||||||
|
|
||||||
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes)
|
// Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes)
|
||||||
{
|
{
|
||||||
"include": ["src/overrides.ts", "src/enums/*"],
|
"includes": ["**/src/overrides.ts", "**/src/enums/**/*"],
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"correctness": {
|
"correctness": {
|
||||||
@ -133,7 +187,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": ["src/overrides.ts"],
|
"includes": ["**/src/overrides.ts"],
|
||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"style": {
|
"style": {
|
||||||
|
@ -23,7 +23,7 @@ When formatted correctly, these comments are shown within VS Code or similar IDE
|
|||||||
- Functions also show the comment for each parameter as you type them, making keeping track of arguments inside lengthy functions much more clear.
|
- Functions also show the comment for each parameter as you type them, making keeping track of arguments inside lengthy functions much more clear.
|
||||||
|
|
||||||
They can also be used to generate a commentated overview of the codebase. There is a GitHub action that automatically updates [this docs site](https://pagefaultgames.github.io/pokerogue/main/index.html)
|
They can also be used to generate a commentated overview of the codebase. There is a GitHub action that automatically updates [this docs site](https://pagefaultgames.github.io/pokerogue/main/index.html)
|
||||||
and you can generate it locally as well via `npm run docs` which will generate into the `typedoc/` directory.
|
and you can generate it locally as well via `pnpm run docs` which will generate into the `typedoc/` directory.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
For an example of how TSDoc comments work, here are some TSDoc comments taken from `src/data/moves/move.ts`:
|
For an example of how TSDoc comments work, here are some TSDoc comments taken from `src/data/moves/move.ts`:
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
# Linting & Formatting
|
# Linting & Formatting
|
||||||
|
|
||||||
> "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
|
|
||||||
>
|
|
||||||
> — Martin Fowler
|
|
||||||
|
|
||||||
Writing clean, readable code is important, and linters and formatters are an integral part of ensuring code quality and readability.
|
Writing clean, readable code is important, and linters and formatters are an integral part of ensuring code quality and readability.
|
||||||
It is for this reason we are using [Biome](https://biomejs.dev), an opinionated linter/formatter (akin to Prettier) with a heavy focus on speed and performance.
|
It is for this reason we are using [Biome](https://biomejs.dev), an opinionated linter/formatter (akin to Prettier) with a heavy focus on speed and performance.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
You probably installed Biome already without noticing it - it's included inside `package.json` and should've been downloaded when you ran `npm install` after cloning the repo (assuming you followed proper instructions, that is). If you haven't done that yet, go do it.
|
You probably installed Biome already without noticing it - it's included inside `package.json` and should've been downloaded when you ran `pnpm install` after cloning the repo. If you haven't done that yet, go do it.
|
||||||
|
|
||||||
# Using Biome
|
# Using Biome
|
||||||
|
|
||||||
@ -24,17 +20,11 @@ You will **not** be able to push code with `error`-level linting problems - fix
|
|||||||
|
|
||||||
We also have a [Github Action](../.github/workflows/quality.yml) to verify code quality each time a PR is updated, preventing bad code from inadvertently making its way upstream.
|
We also have a [Github Action](../.github/workflows/quality.yml) to verify code quality each time a PR is updated, preventing bad code from inadvertently making its way upstream.
|
||||||
|
|
||||||
### Why am I getting errors for code I didn't write?
|
|
||||||
<!-- TODO: Remove this if/when we perform a project wide linting spree -->
|
|
||||||
To save time and minimize friction with existing code, both the pre-commit hook and workflow run will only check files **directly changed** by a given PR or commit.
|
|
||||||
As a result, changes to files not updated since Biome's introduction can cause any _prior_ linting errors in them to resurface and get flagged.
|
|
||||||
This should occur less and less often as time passes and more files are updated to the new standard.
|
|
||||||
|
|
||||||
## Running Biome via CLI
|
## Running Biome via CLI
|
||||||
If you want Biome to check your files manually, you can run it from the command line like so:
|
If you want Biome to check your files manually, you can run it from the command line like so:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npx biome check --[flags]
|
pnpm exec biome check --[flags]
|
||||||
```
|
```
|
||||||
|
|
||||||
A full list of flags and options can be found on [their website](https://biomejs.dev/reference/cli/), but here's a few useful ones to keep in mind:
|
A full list of flags and options can be found on [their website](https://biomejs.dev/reference/cli/), but here's a few useful ones to keep in mind:
|
||||||
@ -56,10 +46,3 @@ Some things to consider:
|
|||||||
Any questions about linting rules should be brought up in the `#dev-corner` channel in the discord.
|
Any questions about linting rules should be brought up in the `#dev-corner` channel in the discord.
|
||||||
|
|
||||||
[^1]: A complete list of rules can be found in the `biome.jsonc` file in the project root.
|
[^1]: A complete list of rules can be found in the `biome.jsonc` file in the project root.
|
||||||
|
|
||||||
## What about ESLint?
|
|
||||||
|
|
||||||
<!-- Remove if/when we finally ditch eslint for good -->
|
|
||||||
Our project migrated away from ESLint around March 2025 due to it simply not scaling well enough with the codebase's ever-growing size. The [existing eslint rules](../eslint.config.js) are considered _deprecated_, only kept due to Biome lacking the corresponding rules in its current ruleset.
|
|
||||||
|
|
||||||
No additional ESLint rules should be added under any circumstances - even the few currently in circulation take longer to run than the entire Biome formatting/linting suite combined.
|
|
@ -1,43 +0,0 @@
|
|||||||
/** @ts-check */
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
import stylisticTs from "@stylistic/eslint-plugin-ts";
|
|
||||||
import parser from "@typescript-eslint/parser";
|
|
||||||
import importX from "eslint-plugin-import-x";
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{
|
|
||||||
name: "eslint-config",
|
|
||||||
files: ["src/**/*.{ts,tsx,js,jsx}", "test/**/*.{ts,tsx,js,jsx}"],
|
|
||||||
ignores: ["dist/*", "build/*", "coverage/*", "public/*", ".github/*", "node_modules/*", ".vscode/*"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: parser,
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"import-x": importX,
|
|
||||||
"@stylistic/ts": stylisticTs,
|
|
||||||
"@typescript-eslint": tseslint.plugin,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this)
|
|
||||||
"no-extra-semi": "error", // Disallows unnecessary semicolons for TypeScript-specific syntax
|
|
||||||
"import-x/extensions": ["error", "never", { json: "always" }], // Enforces no extension for imports unless json
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "eslint-tests",
|
|
||||||
files: ["test/**/**.test.ts"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: parser,
|
|
||||||
parserOptions: {
|
|
||||||
project: ["./tsconfig.json"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"@typescript-eslint": tseslint.plugin,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-floating-promises": "error", // Require Promise-like statements to be handled appropriately. - https://typescript-eslint.io/rules/no-floating-promises/
|
|
||||||
"@typescript-eslint/no-misused-promises": "error", // Disallow Promises in places not designed to handle them. - https://typescript-eslint.io/rules/no-misused-promises/
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
@ -1,8 +1,7 @@
|
|||||||
pre-commit:
|
pre-commit:
|
||||||
parallel: true
|
|
||||||
commands:
|
commands:
|
||||||
biome-lint:
|
biome-lint:
|
||||||
run: npx biome check --write --reporter=summary --staged --no-errors-on-unmatched
|
run: pnpm exec biome check --write --reporter=summary --staged --no-errors-on-unmatched
|
||||||
stage_fixed: true
|
stage_fixed: true
|
||||||
skip:
|
skip:
|
||||||
- merge
|
- merge
|
||||||
|
8029
package-lock.json
generated
8029
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -22,24 +22,19 @@
|
|||||||
"docs": "typedoc",
|
"docs": "typedoc",
|
||||||
"depcruise": "depcruise src test",
|
"depcruise": "depcruise src test",
|
||||||
"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",
|
||||||
"postinstall": "npx lefthook install && npx lefthook run post-merge",
|
"postinstall": "lefthook install && lefthook run post-merge",
|
||||||
"update-version:patch": "npm version patch --force --no-git-tag-version",
|
"update-version:patch": "pnpm version patch --force --no-git-tag-version",
|
||||||
"update-version:minor": "npm version minor --force --no-git-tag-version",
|
"update-version:minor": "pnpm version minor --force --no-git-tag-version",
|
||||||
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
|
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "2.0.0",
|
||||||
"@eslint/js": "^9.23.0",
|
|
||||||
"@hpcc-js/wasm": "^2.22.4",
|
"@hpcc-js/wasm": "^2.22.4",
|
||||||
"@stylistic/eslint-plugin-ts": "^4.1.0",
|
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^22.13.14",
|
"@types/node": "^22.13.14",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
|
||||||
"@typescript-eslint/parser": "^8.28.0",
|
|
||||||
"@vitest/coverage-istanbul": "^3.0.9",
|
"@vitest/coverage-istanbul": "^3.0.9",
|
||||||
|
"chalk": "^5.4.1",
|
||||||
"dependency-cruiser": "^16.3.10",
|
"dependency-cruiser": "^16.3.10",
|
||||||
"eslint": "^9.23.0",
|
|
||||||
"eslint-plugin-import-x": "^4.9.4",
|
|
||||||
"inquirer": "^12.4.2",
|
"inquirer": "^12.4.2",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"lefthook": "^1.11.5",
|
"lefthook": "^1.11.5",
|
||||||
@ -47,7 +42,6 @@
|
|||||||
"phaser3spectorjs": "^0.0.8",
|
"phaser3spectorjs": "^0.0.8",
|
||||||
"typedoc": "^0.28.1",
|
"typedoc": "^0.28.1",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.28.0",
|
|
||||||
"vite": "^6.3.4",
|
"vite": "^6.3.4",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^3.0.9",
|
"vitest": "^3.0.9",
|
||||||
|
3910
pnpm-lock.yaml
Normal file
3910
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.7 KiB |
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* This script creates a test boilerplate file in the appropriate
|
* This script creates a test boilerplate file in the appropriate
|
||||||
* directory based on the type selected.
|
* directory based on the type selected.
|
||||||
* @example npm run test:create
|
* @example pnpm test:create
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import type { AbAttr } from "#app/data/abilities/ability";
|
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { BattleStat } from "#enums/stat";
|
import type { BattleStat } from "#enums/stat";
|
||||||
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
|
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
|
||||||
|
|
||||||
// Intentionally re-export all types from the ability attributes module
|
// intentionally re-export all types from abilities to have this be the centralized place to import ability types
|
||||||
export type * from "#app/data/abilities/ability";
|
export type * from "#app/data/abilities/ability";
|
||||||
|
|
||||||
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => void;
|
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
|
||||||
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => boolean;
|
import type { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
|
|
||||||
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
||||||
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
||||||
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
||||||
@ -25,3 +25,22 @@ export type AbAttrString = keyof AbAttrConstructorMap;
|
|||||||
export type AbAttrMap = {
|
export type AbAttrMap = {
|
||||||
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
|
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subset of ability attribute classes that may be passed to {@linkcode applyAbAttrs} method
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Our AbAttr classes violate Liskov Substitution Principle.
|
||||||
|
*
|
||||||
|
* AbAttrs that are not in this have subclasses with apply methods requiring different parameters than
|
||||||
|
* the base apply method.
|
||||||
|
*
|
||||||
|
* Such attributes may not be passed to the {@linkcode applyAbAttrs} method
|
||||||
|
*/
|
||||||
|
export type CallableAbAttrString =
|
||||||
|
| Exclude<AbAttrString, "PreDefendAbAttr" | "PreAttackAbAttr">
|
||||||
|
| "PreApplyBattlerTagAbAttr";
|
||||||
|
|
||||||
|
export type AbAttrParamMap = {
|
||||||
|
[K in keyof AbAttrMap]: Parameters<AbAttrMap[K]["apply"]>[0];
|
||||||
|
};
|
||||||
|
34
src/@types/type-helpers.ts
Normal file
34
src/@types/type-helpers.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* A collection of custom utility types that aid in type checking and ensuring strict type conformity
|
||||||
|
*/
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
|
||||||
|
import type { AbAttr } from "./ability-types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exactly matches the type of the argument, preventing adding additional properties.
|
||||||
|
*
|
||||||
|
* ⚠️ Should never be used with `extends`, as this will nullify the exactness of the type.
|
||||||
|
*
|
||||||
|
* As an example, used to ensure that the parameters of {@linkcode AbAttr.canApply} and {@linkcode AbAttr.getTriggerMessage} are compatible with
|
||||||
|
* the type of the apply method
|
||||||
|
*
|
||||||
|
* @typeParam T - The type to match exactly
|
||||||
|
*/
|
||||||
|
export type Exact<T> = {
|
||||||
|
[K in keyof T]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type hint that indicates that the type is intended to be closed to a specific shape.
|
||||||
|
* Does not actually do anything special, is really just an alias for X.
|
||||||
|
*/
|
||||||
|
export type Closed<X> = X;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove `readonly` from all properties of the provided type
|
||||||
|
* @typeParam T - The type to make mutable
|
||||||
|
*/
|
||||||
|
export type Mutable<T> = {
|
||||||
|
-readonly [P in keyof T]: T[P];
|
||||||
|
};
|
@ -67,7 +67,7 @@ import { modifierTypes } from "./data/data-lists";
|
|||||||
import { getModifierPoolForType } from "./utils/modifier-utils";
|
import { getModifierPoolForType } from "./utils/modifier-utils";
|
||||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import AbilityBar from "#app/ui/ability-bar";
|
import AbilityBar from "#app/ui/ability-bar";
|
||||||
import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "./data/abilities/apply-ab-attrs";
|
||||||
import { allAbilities } from "./data/data-lists";
|
import { allAbilities } from "./data/data-lists";
|
||||||
import type { FixedBattleConfig } from "#app/battle";
|
import type { FixedBattleConfig } from "#app/battle";
|
||||||
import Battle from "#app/battle";
|
import Battle from "#app/battle";
|
||||||
@ -468,7 +468,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
//@ts-ignore (the defined types in the package are incromplete...)
|
//@ts-expect-error (the defined types in the package are incromplete...)
|
||||||
transition.transit({
|
transition.transit({
|
||||||
mode: "blinds",
|
mode: "blinds",
|
||||||
ease: "Cubic.easeInOut",
|
ease: "Cubic.easeInOut",
|
||||||
@ -894,9 +894,19 @@ export default class BattleScene extends SceneBase {
|
|||||||
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
|
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPokemonById(pokemonId: number): Pokemon | null {
|
/**
|
||||||
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
|
* Return the {@linkcode Pokemon} associated with a given ID.
|
||||||
return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null;
|
* @param pokemonId - The ID whose Pokemon will be retrieved.
|
||||||
|
* @returns The {@linkcode Pokemon} associated with the given id.
|
||||||
|
* Returns `null` if the ID is `undefined` or not present in either party.
|
||||||
|
*/
|
||||||
|
getPokemonById(pokemonId: number | undefined): Pokemon | null {
|
||||||
|
if (isNullOrUndefined(pokemonId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const party = (this.getPlayerParty() as Pokemon[]).concat(this.getEnemyParty());
|
||||||
|
return party.find(p => p.id === pokemonId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlayerPokemon(
|
addPlayerPokemon(
|
||||||
@ -1167,7 +1177,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
this.field.remove(this.currentBattle.mysteryEncounter?.introVisuals, true);
|
this.field.remove(this.currentBattle.mysteryEncounter?.introVisuals, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
|
//@ts-expect-error - allowing `null` for currentBattle causes a lot of trouble
|
||||||
this.currentBattle = null; // TODO: resolve ts-ignore
|
this.currentBattle = null; // TODO: resolve ts-ignore
|
||||||
|
|
||||||
// Reset RNG after end of game or save & quit.
|
// Reset RNG after end of game or save & quit.
|
||||||
@ -1256,7 +1266,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
||||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
||||||
for (const p of playerField) {
|
for (const p of playerField) {
|
||||||
applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
|
applyAbAttrs("DoubleBattleChanceAbAttr", { pokemon: p, chance: doubleChance });
|
||||||
}
|
}
|
||||||
return Math.max(doubleChance.value, 1);
|
return Math.max(doubleChance.value, 1);
|
||||||
}
|
}
|
||||||
@ -1461,7 +1471,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
for (const pokemon of this.getPlayerParty()) {
|
for (const pokemon of this.getPlayerParty()) {
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
pokemon.resetTera();
|
pokemon.resetTera();
|
||||||
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
|
||||||
if (
|
if (
|
||||||
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
||||||
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
||||||
@ -2743,7 +2753,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -2783,13 +2793,13 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (target.isPlayer()) {
|
if (target.isPlayer()) {
|
||||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
||||||
if (source && itemLost) {
|
if (source && itemLost) {
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon: source });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
||||||
if (source && itemLost) {
|
if (source && itemLost) {
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon: source });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2812,7 +2822,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -3237,7 +3247,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
(!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE) &&
|
(!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE) &&
|
||||||
achv.validate(args)
|
achv.validate(args)
|
||||||
) {
|
) {
|
||||||
this.gameData.achvUnlocks[achv.id] = new Date().getTime();
|
this.gameData.achvUnlocks[achv.id] = Date.now();
|
||||||
this.ui.achvBar.showAchv(achv);
|
this.ui.achvBar.showAchv(achv);
|
||||||
if (vouchers.hasOwnProperty(achv.id)) {
|
if (vouchers.hasOwnProperty(achv.id)) {
|
||||||
this.validateVoucher(vouchers[achv.id]);
|
this.validateVoucher(vouchers[achv.id]);
|
||||||
@ -3250,7 +3260,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
validateVoucher(voucher: Voucher, args?: unknown[]): boolean {
|
validateVoucher(voucher: Voucher, args?: unknown[]): boolean {
|
||||||
if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(args)) {
|
if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(args)) {
|
||||||
this.gameData.voucherUnlocks[voucher.id] = new Date().getTime();
|
this.gameData.voucherUnlocks[voucher.id] = Date.now();
|
||||||
this.ui.achvBar.showAchv(voucher);
|
this.ui.achvBar.showAchv(voucher);
|
||||||
this.gameData.voucherCounts[voucher.voucherType]++;
|
this.gameData.voucherCounts[voucher.voucherType]++;
|
||||||
return true;
|
return true;
|
||||||
|
@ -178,7 +178,7 @@ export default class Battle {
|
|||||||
)
|
)
|
||||||
.map(i => {
|
.map(i => {
|
||||||
const ret = i as PokemonHeldItemModifier;
|
const ret = i as PokemonHeldItemModifier;
|
||||||
//@ts-ignore - this is awful to fix/change
|
//@ts-expect-error - this is awful to fix/change
|
||||||
ret.pokemonId = null;
|
ret.pokemonId = null;
|
||||||
return ret;
|
return ret;
|
||||||
}),
|
}),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,63 +1,14 @@
|
|||||||
import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types";
|
import type { AbAttrParamMap } from "#app/@types/ability-types";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type { AbAttrBaseParams, AbAttrString, CallableAbAttrString } from "#app/@types/ability-types";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { BooleanHolder, NumberHolder } from "#app/utils/common";
|
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
|
||||||
import type { HitResult } from "#enums/hit-result";
|
|
||||||
import type { BattleStat, Stat } from "#enums/stat";
|
|
||||||
import type { StatusEffect } from "#enums/status-effect";
|
|
||||||
import type { WeatherType } from "#enums/weather-type";
|
|
||||||
import type { BattlerTag } from "../battler-tags";
|
|
||||||
import type Move from "../moves/move";
|
|
||||||
import type { PokemonMove } from "../moves/pokemon-move";
|
|
||||||
import type { TerrainType } from "../terrain";
|
|
||||||
import type { Weather } from "../weather";
|
|
||||||
import type {
|
|
||||||
PostBattleInitAbAttr,
|
|
||||||
PreDefendAbAttr,
|
|
||||||
PostDefendAbAttr,
|
|
||||||
PostMoveUsedAbAttr,
|
|
||||||
StatMultiplierAbAttr,
|
|
||||||
AllyStatMultiplierAbAttr,
|
|
||||||
PostSetStatusAbAttr,
|
|
||||||
PostDamageAbAttr,
|
|
||||||
FieldMultiplyStatAbAttr,
|
|
||||||
PreAttackAbAttr,
|
|
||||||
ExecutedMoveAbAttr,
|
|
||||||
PostAttackAbAttr,
|
|
||||||
PostKnockOutAbAttr,
|
|
||||||
PostVictoryAbAttr,
|
|
||||||
PostSummonAbAttr,
|
|
||||||
PreSummonAbAttr,
|
|
||||||
PreSwitchOutAbAttr,
|
|
||||||
PreLeaveFieldAbAttr,
|
|
||||||
PreStatStageChangeAbAttr,
|
|
||||||
PostStatStageChangeAbAttr,
|
|
||||||
PreSetStatusAbAttr,
|
|
||||||
PreApplyBattlerTagAbAttr,
|
|
||||||
PreWeatherEffectAbAttr,
|
|
||||||
PreWeatherDamageAbAttr,
|
|
||||||
PostTurnAbAttr,
|
|
||||||
PostWeatherChangeAbAttr,
|
|
||||||
PostWeatherLapseAbAttr,
|
|
||||||
PostTerrainChangeAbAttr,
|
|
||||||
CheckTrappedAbAttr,
|
|
||||||
PostBattleAbAttr,
|
|
||||||
PostFaintAbAttr,
|
|
||||||
PostItemLostAbAttr,
|
|
||||||
} from "./ability";
|
|
||||||
|
|
||||||
function applySingleAbAttrs<T extends AbAttrString>(
|
function applySingleAbAttrs<T extends AbAttrString>(
|
||||||
pokemon: Pokemon,
|
|
||||||
passive: boolean,
|
|
||||||
attrType: T,
|
attrType: T,
|
||||||
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
|
params: AbAttrParamMap[T],
|
||||||
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
|
|
||||||
args: any[],
|
|
||||||
gainedMidTurn = false,
|
gainedMidTurn = false,
|
||||||
simulated = false,
|
|
||||||
messages: string[] = [],
|
messages: string[] = [],
|
||||||
) {
|
) {
|
||||||
|
const { simulated = false, passive = false, pokemon } = params;
|
||||||
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -75,7 +26,11 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
for (const attr of ability.getAttrs(attrType)) {
|
for (const attr of ability.getAttrs(attrType)) {
|
||||||
const condition = attr.getCondition();
|
const condition = attr.getCondition();
|
||||||
let abShown = false;
|
let abShown = false;
|
||||||
if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) {
|
// We require an `as any` cast to suppress an error about the `params` type not being assignable to
|
||||||
|
// the type of the argument expected by `attr.canApply()`. This is OK, because we know that
|
||||||
|
// `attr` is an instance of the `attrType` class provided to the method, and typescript _will_ check
|
||||||
|
// that the `params` object has the correct properties for that class at the callsites.
|
||||||
|
if ((condition && !condition(pokemon)) || !attr.canApply(params as any)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,15 +40,16 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||||
abShown = true;
|
abShown = true;
|
||||||
}
|
}
|
||||||
const message = attr.getTriggerMessage(pokemon, ability.name, args);
|
|
||||||
|
const message = attr.getTriggerMessage(params as any, ability.name);
|
||||||
if (message) {
|
if (message) {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
globalScene.phaseManager.queueMessage(message);
|
globalScene.phaseManager.queueMessage(message);
|
||||||
}
|
}
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
|
// The `as any` cast here uses the same reasoning as above.
|
||||||
applyFunc(attr, passive);
|
attr.apply(params as any);
|
||||||
|
|
||||||
if (abShown) {
|
if (abShown) {
|
||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
||||||
@ -107,726 +63,60 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAbAttrsInternal<T extends AbAttrString>(
|
function applyAbAttrsInternal<T extends CallableAbAttrString>(
|
||||||
attrType: T,
|
attrType: T,
|
||||||
pokemon: Pokemon | null,
|
params: AbAttrParamMap[T],
|
||||||
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
|
|
||||||
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
|
|
||||||
args: any[],
|
|
||||||
simulated = false,
|
|
||||||
messages: string[] = [],
|
messages: string[] = [],
|
||||||
gainedMidTurn = false,
|
gainedMidTurn = false,
|
||||||
) {
|
) {
|
||||||
for (const passive of [false, true]) {
|
// If the pokemon is not defined, no ability attributes to be applied.
|
||||||
if (pokemon) {
|
// TODO: Evaluate whether this check is even necessary anymore
|
||||||
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages);
|
if (!params.pokemon) {
|
||||||
globalScene.phaseManager.clearPhaseQueueSplice();
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (params.passive !== undefined) {
|
||||||
|
applySingleAbAttrs(attrType, params, gainedMidTurn, messages);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const passive of [false, true]) {
|
||||||
|
params.passive = passive;
|
||||||
|
applySingleAbAttrs(attrType, params, gainedMidTurn, messages);
|
||||||
|
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||||
|
}
|
||||||
|
// We need to restore passive to its original state in the case that it was undefined on entry
|
||||||
|
// this is necessary in case this method is called with an object that is reused.
|
||||||
|
params.passive = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyAbAttrs<T extends AbAttrString>(
|
/**
|
||||||
|
* @param attrType - The type of the ability attribute to apply. (note: may not be any attribute that extends PostSummonAbAttr)
|
||||||
|
* @param params - The parameters to pass to the ability attribute's apply method
|
||||||
|
* @param messages - An optional array to which ability trigger messges will be added
|
||||||
|
*/
|
||||||
|
export function applyAbAttrs<T extends CallableAbAttrString>(
|
||||||
attrType: T,
|
attrType: T,
|
||||||
pokemon: Pokemon,
|
params: AbAttrParamMap[T],
|
||||||
cancelled: BooleanHolder | null,
|
messages?: string[],
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
): void {
|
||||||
applyAbAttrsInternal<T>(
|
applyAbAttrsInternal(attrType, params, messages);
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
// @ts-expect-error: TODO: fix the error on `cancelled`
|
|
||||||
(attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args),
|
|
||||||
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Improve the type signatures of the following methods / refactor the apply methods
|
// TODO: Improve the type signatures of the following methods / refactor the apply methods
|
||||||
|
|
||||||
export function applyPostBattleInitAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreDefendAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
attacker: Pokemon,
|
|
||||||
move: Move | null,
|
|
||||||
cancelled: BooleanHolder | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostDefendAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
attacker: Pokemon,
|
|
||||||
move: Move,
|
|
||||||
hitResult: HitResult | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostMoveUsedAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
move: PokemonMove,
|
|
||||||
source: Pokemon,
|
|
||||||
targets: BattlerIndex[],
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args),
|
|
||||||
(attr, _passive) =>
|
|
||||||
(attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyStatMultiplierAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stat: BattleStat,
|
|
||||||
statValue: NumberHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies an ally's Stat multiplier attribute
|
|
||||||
* @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
|
|
||||||
* @param pokemon - The {@linkcode Pokemon} with the ability
|
|
||||||
* @param stat - The type of the checked {@linkcode Stat}
|
|
||||||
* @param statValue - {@linkcode NumberHolder} containing the value of the checked stat
|
|
||||||
* @param checkedPokemon - The {@linkcode Pokemon} with the checked stat
|
|
||||||
* @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move.
|
|
||||||
* @param args - unused
|
|
||||||
*/
|
|
||||||
export function applyAllyStatMultiplierAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stat: BattleStat,
|
|
||||||
statValue: NumberHolder,
|
|
||||||
simulated = false,
|
|
||||||
checkedPokemon: Pokemon,
|
|
||||||
ignoreAbility: boolean,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as AllyStatMultiplierAbAttr).applyAllyStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
ignoreAbility,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as AllyStatMultiplierAbAttr).canApplyAllyStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
ignoreAbility,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostSetStatusAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
effect: StatusEffect,
|
|
||||||
sourcePokemon?: Pokemon | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostDamageAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
damage: number,
|
|
||||||
_passive: boolean,
|
|
||||||
simulated = false,
|
|
||||||
args: any[],
|
|
||||||
source?: Pokemon,
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source),
|
|
||||||
(attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Applies a field Stat multiplier attribute
|
|
||||||
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
|
|
||||||
* @param pokemon {@linkcode Pokemon} the Pokemon applying this ability
|
|
||||||
* @param stat {@linkcode Stat} the type of the checked stat
|
|
||||||
* @param statValue {@linkcode NumberHolder} the value of the checked stat
|
|
||||||
* @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat
|
|
||||||
* @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat
|
|
||||||
* @param args unused
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function applyFieldStatMultiplierAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stat: Stat,
|
|
||||||
statValue: NumberHolder,
|
|
||||||
checkedPokemon: Pokemon,
|
|
||||||
hasApplied: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as FieldMultiplyStatAbAttr).applyFieldStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
hasApplied,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as FieldMultiplyStatAbAttr).canApplyFieldStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
hasApplied,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreAttackAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
defender: Pokemon | null,
|
|
||||||
move: Move,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args),
|
|
||||||
(attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyExecutedMoveAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated),
|
|
||||||
attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostAttackAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
defender: Pokemon,
|
|
||||||
move: Move,
|
|
||||||
hitResult: HitResult | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostKnockOutAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
knockedOut: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
|
|
||||||
(attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostVictoryAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostSummonAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
passive = false,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applySingleAbAttrs(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
attrType,
|
|
||||||
(attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
false,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreSummonAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args),
|
|
||||||
(attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreSwitchOutAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreLeaveFieldAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreStatStageChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon | null,
|
|
||||||
stat: BattleStat,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostStatStageChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stats: BattleStat[],
|
|
||||||
stages: number,
|
|
||||||
selfTarget: boolean,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, _passive) =>
|
|
||||||
(attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args),
|
|
||||||
(attr, _passive) =>
|
|
||||||
(attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange(
|
|
||||||
pokemon,
|
|
||||||
simulated,
|
|
||||||
stats,
|
|
||||||
stages,
|
|
||||||
selfTarget,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreSetStatusAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
effect: StatusEffect | undefined,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreApplyBattlerTagAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
tag: BattlerTag,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreWeatherEffectAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
weather: Weather | null,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostTurnAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostWeatherChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
weather: WeatherType,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostWeatherLapseAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
weather: Weather | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostTerrainChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
terrain: TerrainType,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyCheckTrappedAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
trapped: BooleanHolder,
|
|
||||||
otherPokemon: Pokemon,
|
|
||||||
messages: string[],
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
messages,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostBattleAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostFaintAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
attacker?: Pokemon,
|
|
||||||
move?: Move,
|
|
||||||
hitResult?: HitResult,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostItemLostAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args),
|
|
||||||
(attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies abilities when they become active mid-turn (ability switch)
|
* Applies abilities when they become active mid-turn (ability switch)
|
||||||
*
|
*
|
||||||
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
|
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
|
||||||
*/
|
*/
|
||||||
export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
|
export function applyOnGainAbAttrs(params: AbAttrBaseParams): void {
|
||||||
applySingleAbAttrs(
|
applySingleAbAttrs("PostSummonAbAttr", params, true);
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
"PostSummonAbAttr",
|
|
||||||
(attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
true,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
|
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
|
||||||
*/
|
*/
|
||||||
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
|
export function applyOnLoseAbAttrs(params: AbAttrBaseParams): void {
|
||||||
applySingleAbAttrs(
|
applySingleAbAttrs("PreLeaveFieldAbAttr", params, true);
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
"PreLeaveFieldAbAttr",
|
|
||||||
(attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]),
|
|
||||||
(attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]),
|
|
||||||
args,
|
|
||||||
true,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
|
|
||||||
applySingleAbAttrs(
|
applySingleAbAttrs("IllusionBreakAbAttr", params, true);
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
"IllusionBreakAbAttr",
|
|
||||||
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
|
|
||||||
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
true,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -72,10 +72,11 @@ export abstract class ArenaTag {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that retrieves the source Pokemon
|
* Helper function that retrieves the source Pokemon
|
||||||
* @returns The source {@linkcode Pokemon} or `null` if none is found
|
* @returns - The source {@linkcode Pokemon} for this tag.
|
||||||
|
* Returns `null` if `this.sourceId` is `undefined`
|
||||||
*/
|
*/
|
||||||
public getSourcePokemon(): Pokemon | null {
|
public getSourcePokemon(): Pokemon | null {
|
||||||
return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
return globalScene.getPokemonById(this.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,19 +108,22 @@ export class MistTag extends ArenaTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
if (this.sourceId) {
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
const source = globalScene.getPokemonById(this.sourceId);
|
if (quiet) {
|
||||||
|
return;
|
||||||
if (!quiet && source) {
|
|
||||||
globalScene.phaseManager.queueMessage(
|
|
||||||
i18next.t("arenaTag:mistOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (!quiet) {
|
|
||||||
console.warn("Failed to get source for MistTag onAdd");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for MistTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:mistOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,7 +141,7 @@ export class MistTag extends ArenaTag {
|
|||||||
if (attacker) {
|
if (attacker) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
// TODO: Allow this to be simulated
|
// TODO: Allow this to be simulated
|
||||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, simulated: false, bypassed });
|
||||||
if (bypassed.value) {
|
if (bypassed.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -202,7 +206,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (this.weakenedCategories.includes(moveCategory)) {
|
if (this.weakenedCategories.includes(moveCategory)) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
|
||||||
if (bypassed.value) {
|
if (bypassed.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -440,18 +444,18 @@ class MatBlockTag extends ConditionalProtectTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena) {
|
onAdd(_arena: Arena) {
|
||||||
if (this.sourceId) {
|
const source = this.getSourcePokemon();
|
||||||
const source = globalScene.getPokemonById(this.sourceId);
|
if (!source) {
|
||||||
if (source) {
|
console.warn(`Failed to get source Pokemon for Mat Block message; id: ${this.sourceId}`);
|
||||||
globalScene.phaseManager.queueMessage(
|
return;
|
||||||
i18next.t("arenaTag:matBlockOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.warn("Failed to get source for MatBlockTag onAdd");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onAdd(_arena);
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:matBlockOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,7 +515,12 @@ export class NoCritTag extends ArenaTag {
|
|||||||
|
|
||||||
/** Queues a message upon removing this effect from the field */
|
/** Queues a message upon removing this effect from the field */
|
||||||
onRemove(_arena: Arena): void {
|
onRemove(_arena: Arena): void {
|
||||||
const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for NoCritTag on remove message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("arenaTag:noCritOnRemove", {
|
i18next.t("arenaTag:noCritOnRemove", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
|
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
|
||||||
@ -522,7 +531,7 @@ export class NoCritTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) Wish}.
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) | Wish}.
|
||||||
* Heals the Pokémon in the user's position the turn after Wish is used.
|
* Heals the Pokémon in the user's position the turn after Wish is used.
|
||||||
*/
|
*/
|
||||||
class WishTag extends ArenaTag {
|
class WishTag extends ArenaTag {
|
||||||
@ -535,18 +544,20 @@ class WishTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena): void {
|
onAdd(_arena: Arena): void {
|
||||||
if (this.sourceId) {
|
const source = this.getSourcePokemon();
|
||||||
const user = globalScene.getPokemonById(this.sourceId);
|
if (!source) {
|
||||||
if (user) {
|
console.warn(`Failed to get source Pokemon for WishTag on add message; id: ${this.sourceId}`);
|
||||||
this.battlerIndex = user.getBattlerIndex();
|
return;
|
||||||
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
|
||||||
});
|
|
||||||
this.healHp = toDmgValue(user.getMaxHp() / 2);
|
|
||||||
} else {
|
|
||||||
console.warn("Failed to get source for WishTag onAdd");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onAdd(_arena);
|
||||||
|
this.healHp = toDmgValue(source.getMaxHp() / 2);
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:wishTagOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(_arena: Arena): void {
|
onRemove(_arena: Arena): void {
|
||||||
@ -741,15 +752,23 @@ class SpikesTag extends ArenaTrapTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
if (!quiet && source) {
|
if (quiet) {
|
||||||
globalScene.phaseManager.queueMessage(
|
return;
|
||||||
i18next.t("arenaTag:spikesOnAdd", {
|
|
||||||
moveName: this.getMoveName(),
|
|
||||||
opponentDesc: source.getOpponentDescriptor(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SpikesTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:spikesOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
@ -758,7 +777,7 @@ class SpikesTag extends ArenaTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
if (simulated || cancelled.value) {
|
if (simulated || cancelled.value) {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
}
|
}
|
||||||
@ -794,15 +813,23 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
if (quiet) {
|
||||||
if (!quiet && source) {
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
globalScene.phaseManager.queueMessage(
|
return;
|
||||||
i18next.t("arenaTag:toxicSpikesOnAdd", {
|
|
||||||
moveName: this.getMoveName(),
|
|
||||||
opponentDesc: source.getOpponentDescriptor(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for ToxicSpikesTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:toxicSpikesOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(arena: Arena): void {
|
onRemove(arena: Arena): void {
|
||||||
@ -905,7 +932,11 @@ class StealthRockTag extends ArenaTrapTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
if (quiet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
if (!quiet && source) {
|
if (!quiet && source) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("arenaTag:stealthRockOnAdd", {
|
i18next.t("arenaTag:stealthRockOnAdd", {
|
||||||
@ -946,7 +977,7 @@ class StealthRockTag extends ArenaTrapTag {
|
|||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -989,21 +1020,35 @@ class StickyWebTag extends ArenaTrapTag {
|
|||||||
|
|
||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
|
||||||
if (!quiet && source) {
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
globalScene.phaseManager.queueMessage(
|
if (quiet) {
|
||||||
i18next.t("arenaTag:stickyWebOnAdd", {
|
return;
|
||||||
moveName: this.getMoveName(),
|
|
||||||
opponentDesc: source.getOpponentDescriptor(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SpikesTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:stickyWebOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
if (pokemon.isGrounded()) {
|
if (pokemon.isGrounded()) {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
applyAbAttrs("ProtectStatAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
cancelled,
|
||||||
|
stat: Stat.SPD,
|
||||||
|
stages: -1,
|
||||||
|
});
|
||||||
|
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
@ -1061,14 +1106,20 @@ export class TrickRoomTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena): void {
|
onAdd(_arena: Arena): void {
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
super.onAdd(_arena);
|
||||||
if (source) {
|
|
||||||
globalScene.phaseManager.queueMessage(
|
const source = this.getSourcePokemon();
|
||||||
i18next.t("arenaTag:trickRoomOnAdd", {
|
if (!source) {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
console.warn(`Failed to get source Pokemon for TrickRoomTag on add message; id: ${this.sourceId}`);
|
||||||
}),
|
return;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:trickRoomOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(_arena: Arena): void {
|
onRemove(_arena: Arena): void {
|
||||||
@ -1115,6 +1166,13 @@ class TailwindTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena, quiet = false): void {
|
onAdd(_arena: Arena, quiet = false): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onAdd(_arena, quiet);
|
||||||
|
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t(
|
i18next.t(
|
||||||
@ -1123,15 +1181,14 @@ class TailwindTag extends ArenaTag {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
|
const field = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? [];
|
|
||||||
const phaseManager = globalScene.phaseManager;
|
|
||||||
|
|
||||||
for (const pokemon of party) {
|
for (const pokemon of field) {
|
||||||
// Apply the CHARGED tag to party members with the WIND_POWER ability
|
// Apply the CHARGED tag to party members with the WIND_POWER ability
|
||||||
|
// TODO: This should not be handled here
|
||||||
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
|
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
|
||||||
pokemon.addTag(BattlerTagType.CHARGED);
|
pokemon.addTag(BattlerTagType.CHARGED);
|
||||||
phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("abilityTriggers:windPowerCharged", {
|
i18next.t("abilityTriggers:windPowerCharged", {
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
moveName: this.getMoveName(),
|
moveName: this.getMoveName(),
|
||||||
@ -1142,9 +1199,16 @@ class TailwindTag extends ArenaTag {
|
|||||||
// Raise attack by one stage if party member has WIND_RIDER ability
|
// Raise attack by one stage if party member has WIND_RIDER ability
|
||||||
// TODO: Ability displays should be handled by the ability
|
// TODO: Ability displays should be handled by the ability
|
||||||
if (pokemon.hasAbility(AbilityId.WIND_RIDER)) {
|
if (pokemon.hasAbility(AbilityId.WIND_RIDER)) {
|
||||||
phaseManager.queueAbilityDisplay(pokemon, false, true);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, false, true);
|
||||||
phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true);
|
globalScene.phaseManager.unshiftNew(
|
||||||
phaseManager.queueAbilityDisplay(pokemon, false, false);
|
"StatStageChangePhase",
|
||||||
|
pokemon.getBattlerIndex(),
|
||||||
|
true,
|
||||||
|
[Stat.ATK],
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1216,24 +1280,26 @@ class ImprisonTag extends ArenaTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
|
* Apply the effects of Imprison to all opposing on-field Pokemon.
|
||||||
* @param arena
|
|
||||||
*/
|
*/
|
||||||
override onAdd() {
|
override onAdd() {
|
||||||
const source = this.getSourcePokemon();
|
const source = this.getSourcePokemon();
|
||||||
if (source) {
|
if (!source) {
|
||||||
const party = this.getAffectedPokemon();
|
return;
|
||||||
party?.forEach((p: Pokemon) => {
|
|
||||||
if (p.isAllowedInBattle()) {
|
|
||||||
p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
globalScene.phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:imprisonOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const party = this.getAffectedPokemon();
|
||||||
|
party.forEach(p => {
|
||||||
|
if (p.isAllowedInBattle()) {
|
||||||
|
p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:imprisonOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1243,7 +1309,7 @@ class ImprisonTag extends ArenaTrapTag {
|
|||||||
*/
|
*/
|
||||||
override lapse(): boolean {
|
override lapse(): boolean {
|
||||||
const source = this.getSourcePokemon();
|
const source = this.getSourcePokemon();
|
||||||
return source ? source.isActive(true) : false;
|
return !!source?.isActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1265,9 +1331,7 @@ class ImprisonTag extends ArenaTrapTag {
|
|||||||
*/
|
*/
|
||||||
override onRemove(): void {
|
override onRemove(): void {
|
||||||
const party = this.getAffectedPokemon();
|
const party = this.getAffectedPokemon();
|
||||||
party?.forEach((p: Pokemon) => {
|
party.forEach(p => p.removeTag(BattlerTagType.IMPRISON));
|
||||||
p.removeTag(BattlerTagType.IMPRISON);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1416,7 +1480,9 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
|
|
||||||
for (const fieldPokemon of globalScene.getField(true)) {
|
for (const fieldPokemon of globalScene.getField(true)) {
|
||||||
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
|
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
|
||||||
[true, false].forEach(passive => applyOnLoseAbAttrs(fieldPokemon, passive));
|
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
|
||||||
|
// the appropriate attributes (preLEaveField and IllusionBreak)
|
||||||
|
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: fieldPokemon, passive }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1438,7 +1504,10 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
const setter = globalScene
|
const setter = globalScene
|
||||||
.getField()
|
.getField()
|
||||||
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
|
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
|
||||||
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
|
applyOnGainAbAttrs({
|
||||||
|
pokemon: setter,
|
||||||
|
passive: setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1451,7 +1520,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
||||||
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
||||||
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
|
[true, false].forEach(passive => applyOnGainAbAttrs({ pokemon, passive }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -595,13 +595,13 @@ function parseEggMoves(content: string): void {
|
|||||||
const cols = line.split(",").slice(0, 5);
|
const cols = line.split(",").slice(0, 5);
|
||||||
const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase());
|
const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase());
|
||||||
const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_");
|
const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_");
|
||||||
const species = speciesValues[speciesNames.findIndex(s => s === enumSpeciesName)];
|
const species = speciesValues[speciesNames.indexOf(enumSpeciesName)];
|
||||||
|
|
||||||
const eggMoves: MoveId[] = [];
|
const eggMoves: MoveId[] = [];
|
||||||
|
|
||||||
for (let m = 0; m < 4; m++) {
|
for (let m = 0; m < 4; m++) {
|
||||||
const moveName = cols[m + 1].trim();
|
const moveName = cols[m + 1].trim();
|
||||||
const moveIndex = moveName !== "N/A" ? moveNames.findIndex(mn => mn === moveName.toLowerCase()) : -1;
|
const moveIndex = moveName !== "N/A" ? moveNames.indexOf(moveName.toLowerCase()) : -1;
|
||||||
eggMoves.push(moveIndex > -1 ? moveIndex as MoveId : MoveId.NONE);
|
eggMoves.push(moveIndex > -1 ? moveIndex as MoveId : MoveId.NONE);
|
||||||
|
|
||||||
if (moveIndex === -1) {
|
if (moveIndex === -1) {
|
||||||
|
@ -650,8 +650,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
|||||||
new SpeciesEvolution(SpeciesId.KIRLIA, 20, null, null)
|
new SpeciesEvolution(SpeciesId.KIRLIA, 20, null, null)
|
||||||
],
|
],
|
||||||
[SpeciesId.KIRLIA]: [
|
[SpeciesId.KIRLIA]: [
|
||||||
new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE}),
|
new SpeciesEvolution(SpeciesId.GARDEVOIR, 30, null, null),
|
||||||
new SpeciesEvolution(SpeciesId.GALLADE, 30, null, {key: EvoCondKey.GENDER, gender: Gender.MALE})
|
new SpeciesEvolution(SpeciesId.GALLADE, 1, EvolutionItem.DAWN_STONE, {key: EvoCondKey.GENDER, gender: Gender.MALE})
|
||||||
],
|
],
|
||||||
[SpeciesId.SURSKIT]: [
|
[SpeciesId.SURSKIT]: [
|
||||||
new SpeciesEvolution(SpeciesId.MASQUERAIN, 22, null, null)
|
new SpeciesEvolution(SpeciesId.MASQUERAIN, 22, null, null)
|
||||||
@ -739,8 +739,8 @@ export const pokemonEvolutions: PokemonEvolutions = {
|
|||||||
new SpeciesEvolution(SpeciesId.DUSCLOPS, 37, null, null)
|
new SpeciesEvolution(SpeciesId.DUSCLOPS, 37, null, null)
|
||||||
],
|
],
|
||||||
[SpeciesId.SNORUNT]: [
|
[SpeciesId.SNORUNT]: [
|
||||||
new SpeciesEvolution(SpeciesId.GLALIE, 42, null, {key: EvoCondKey.GENDER, gender: Gender.MALE}),
|
new SpeciesEvolution(SpeciesId.GLALIE, 42, null, null),
|
||||||
new SpeciesEvolution(SpeciesId.FROSLASS, 42, null, {key: EvoCondKey.GENDER, gender: Gender.FEMALE})
|
new SpeciesEvolution(SpeciesId.FROSLASS, 1, EvolutionItem.DAWN_STONE, {key: EvoCondKey.GENDER, gender: Gender.FEMALE})
|
||||||
],
|
],
|
||||||
[SpeciesId.SPHEAL]: [
|
[SpeciesId.SPHEAL]: [
|
||||||
new SpeciesEvolution(SpeciesId.SEALEO, 32, null, null)
|
new SpeciesEvolution(SpeciesId.SEALEO, 32, null, null)
|
||||||
|
@ -346,7 +346,7 @@ abstract class AnimTimedBgEvent extends AnimTimedEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
|
class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: seems intentional
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: seems intentional
|
||||||
execute(moveAnim: MoveAnim, priority?: number): number {
|
execute(moveAnim: MoveAnim, priority?: number): number {
|
||||||
const tweenProps = {};
|
const tweenProps = {};
|
||||||
if (this.bgX !== undefined) {
|
if (this.bgX !== undefined) {
|
||||||
@ -359,15 +359,11 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent {
|
|||||||
tweenProps["alpha"] = (this.opacity || 0) / 255;
|
tweenProps["alpha"] = (this.opacity || 0) / 255;
|
||||||
}
|
}
|
||||||
if (Object.keys(tweenProps).length) {
|
if (Object.keys(tweenProps).length) {
|
||||||
globalScene.tweens.add(
|
globalScene.tweens.add({
|
||||||
Object.assign(
|
targets: moveAnim.bgSprite,
|
||||||
{
|
duration: getFrameMs(this.duration * 3),
|
||||||
targets: moveAnim.bgSprite,
|
...tweenProps,
|
||||||
duration: getFrameMs(this.duration * 3),
|
});
|
||||||
},
|
|
||||||
tweenProps,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return this.duration * 2;
|
return this.duration * 2;
|
||||||
}
|
}
|
||||||
@ -423,7 +419,7 @@ export function initCommonAnims(): Promise<void> {
|
|||||||
const commonAnimId = commonAnimIds[ca];
|
const commonAnimId = commonAnimIds[ca];
|
||||||
commonAnimFetches.push(
|
commonAnimFetches.push(
|
||||||
globalScene
|
globalScene
|
||||||
.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`)
|
.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/_/g, "-")}.json`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
|
.then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas))),
|
||||||
);
|
);
|
||||||
@ -535,7 +531,7 @@ export async function initEncounterAnims(encounterAnim: EncounterAnim | Encounte
|
|||||||
}
|
}
|
||||||
encounterAnimFetches.push(
|
encounterAnimFetches.push(
|
||||||
globalScene
|
globalScene
|
||||||
.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
|
.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/_/g, "-")}.json`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))),
|
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))),
|
||||||
);
|
);
|
||||||
@ -559,7 +555,7 @@ export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise<void> {
|
|||||||
} else {
|
} else {
|
||||||
chargeAnims.set(chargeAnim, null);
|
chargeAnims.set(chargeAnim, null);
|
||||||
globalScene
|
globalScene
|
||||||
.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`)
|
.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/_/g, "-")}.json`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(ca => {
|
.then(ca => {
|
||||||
if (Array.isArray(ca)) {
|
if (Array.isArray(ca)) {
|
||||||
@ -1405,15 +1401,15 @@ export class EncounterBattleAnim extends BattleAnim {
|
|||||||
|
|
||||||
export async function populateAnims() {
|
export async function populateAnims() {
|
||||||
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
|
const commonAnimNames = getEnumKeys(CommonAnim).map(k => k.toLowerCase());
|
||||||
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/\_/g, ""));
|
const commonAnimMatchNames = commonAnimNames.map(k => k.replace(/_/g, ""));
|
||||||
const commonAnimIds = getEnumValues(CommonAnim) as CommonAnim[];
|
const commonAnimIds = getEnumValues(CommonAnim) as CommonAnim[];
|
||||||
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
|
const chargeAnimNames = getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
|
||||||
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/\_/g, " "));
|
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/_/g, " "));
|
||||||
const chargeAnimIds = getEnumValues(ChargeAnim) as ChargeAnim[];
|
const chargeAnimIds = getEnumValues(ChargeAnim) as ChargeAnim[];
|
||||||
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
|
const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
|
||||||
const moveNameToId = {};
|
const moveNameToId = {};
|
||||||
for (const move of getEnumValues(MoveId).slice(1)) {
|
for (const move of getEnumValues(MoveId).slice(1)) {
|
||||||
const moveName = MoveId[move].toUpperCase().replace(/\_/g, "");
|
const moveName = MoveId[move].toUpperCase().replace(/_/g, "");
|
||||||
moveNameToId[moveName] = move;
|
moveNameToId[moveName] = move;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1469,7 +1465,7 @@ export async function populateAnims() {
|
|||||||
const frameData = framesData[fd];
|
const frameData = framesData[fd];
|
||||||
const focusFramesData = frameData.split(" - - ");
|
const focusFramesData = frameData.split(" - - ");
|
||||||
for (let tf = 0; tf < focusFramesData.length; tf++) {
|
for (let tf = 0; tf < focusFramesData.length; tf++) {
|
||||||
const values = focusFramesData[tf].replace(/ {6}\- /g, "").split("\n");
|
const values = focusFramesData[tf].replace(/ {6}- /g, "").split("\n");
|
||||||
const targetFrame = new AnimFrame(
|
const targetFrame = new AnimFrame(
|
||||||
Number.parseFloat(values[0]),
|
Number.parseFloat(values[0]),
|
||||||
Number.parseFloat(values[1]),
|
Number.parseFloat(values[1]),
|
||||||
@ -1516,7 +1512,7 @@ export async function populateAnims() {
|
|||||||
.replace(/[a-z]+: ! '', /gi, "")
|
.replace(/[a-z]+: ! '', /gi, "")
|
||||||
.replace(/name: (.*?),/, 'name: "$1",')
|
.replace(/name: (.*?),/, 'name: "$1",')
|
||||||
.replace(
|
.replace(
|
||||||
/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/,
|
/flashColor: !ruby\/object:Color { alpha: ([\d.]+), blue: ([\d.]+), green: ([\d.]+), red: ([\d.]+)}/,
|
||||||
"flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1",
|
"flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1",
|
||||||
);
|
);
|
||||||
const frameIndex = Number.parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
|
const frameIndex = Number.parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct?
|
||||||
@ -1641,12 +1637,12 @@ export async function populateAnims() {
|
|||||||
let props: string[];
|
let props: string[];
|
||||||
for (let p = 0; p < propSets.length; p++) {
|
for (let p = 0; p < propSets.length; p++) {
|
||||||
props = propSets[p];
|
props = propSets[p];
|
||||||
// @ts-ignore TODO
|
// @ts-expect-error TODO
|
||||||
const ai = props.indexOf(a.key);
|
const ai = props.indexOf(a.key);
|
||||||
if (ai === -1) {
|
if (ai === -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// @ts-ignore TODO
|
// @ts-expect-error TODO
|
||||||
const bi = props.indexOf(b.key);
|
const bi = props.indexOf(b.key);
|
||||||
|
|
||||||
return ai < bi ? -1 : ai > bi ? 1 : 0;
|
return ai < bi ? -1 : ai > bi ? 1 : 0;
|
||||||
|
@ -111,7 +111,7 @@ export class BattlerTag {
|
|||||||
* @returns The source {@linkcode Pokemon}, or `null` if none is found
|
* @returns The source {@linkcode Pokemon}, or `null` if none is found
|
||||||
*/
|
*/
|
||||||
public getSourcePokemon(): Pokemon | null {
|
public getSourcePokemon(): Pokemon | null {
|
||||||
return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
return globalScene.getPokemonById(this.sourceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,9 +540,13 @@ export class TrappedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
const source = globalScene.getPokemonById(this.sourceId!)!;
|
const source = this.getSourcePokemon();
|
||||||
const move = allMoves[this.sourceMove];
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for TrappedTag canAdd; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = allMoves[this.sourceMove];
|
||||||
const isGhost = pokemon.isOfType(PokemonType.GHOST);
|
const isGhost = pokemon.isOfType(PokemonType.GHOST);
|
||||||
const isTrapped = pokemon.getTag(TrappedTag);
|
const isTrapped = pokemon.getTag(TrappedTag);
|
||||||
const hasSubstitute = move.hitsSubstitute(source, pokemon);
|
const hasSubstitute = move.hitsSubstitute(source, pokemon);
|
||||||
@ -621,7 +625,7 @@ export class FlinchedTag extends BattlerTag {
|
|||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
|
applyAbAttrs("FlinchEffectAbAttr", { pokemon });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,12 +767,20 @@ export class DestinyBondTag extends BattlerTag {
|
|||||||
if (lapseType !== BattlerTagLapseType.CUSTOM) {
|
if (lapseType !== BattlerTagLapseType.CUSTOM) {
|
||||||
return super.lapse(pokemon, lapseType);
|
return super.lapse(pokemon, lapseType);
|
||||||
}
|
}
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
|
||||||
if (!source?.isFainted()) {
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for DestinyBondTag lapse; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destiny bond stays active until the user faints
|
||||||
|
if (!source.isFainted()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source?.getAlly() === pokemon) {
|
// Don't kill allies or opposing bosses.
|
||||||
|
if (source.getAlly() === pokemon) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,6 +793,7 @@ export class DestinyBondTag extends BattlerTag {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drag the foe down with the user
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:destinyBondLapse", {
|
i18next.t("battlerTags:destinyBondLapse", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
@ -798,17 +811,13 @@ export class InfatuatedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
if (this.sourceId) {
|
const source = this.getSourcePokemon();
|
||||||
const pkm = globalScene.getPokemonById(this.sourceId);
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for InfatuatedTag canAdd; id: ${this.sourceId}`);
|
||||||
if (pkm) {
|
|
||||||
return pokemon.isOppositeGender(pkm);
|
|
||||||
}
|
|
||||||
console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.warn("canAdd: this.sourceId is undefined");
|
|
||||||
return false;
|
return pokemon.isOppositeGender(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -817,7 +826,7 @@ export class InfatuatedTag extends BattlerTag {
|
|||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:infatuatedOnAdd", {
|
i18next.t("battlerTags:infatuatedOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonName: getPokemonNameWithAffix(this.getSourcePokemon()!), // Tag not added + console warns if no source
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -835,28 +844,36 @@ export class InfatuatedTag extends BattlerTag {
|
|||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
const phaseManager = globalScene.phaseManager;
|
if (!ret) {
|
||||||
|
return false;
|
||||||
if (ret) {
|
|
||||||
phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:infatuatedLapse", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT);
|
|
||||||
|
|
||||||
if (pokemon.randBattleSeedInt(2)) {
|
|
||||||
phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:infatuatedLapseImmobilize", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
(phaseManager.getCurrentPhase() as MovePhase).cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for InfatuatedTag lapse; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const phaseManager = globalScene.phaseManager;
|
||||||
|
phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:infatuatedLapse", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT);
|
||||||
|
|
||||||
|
// 50% chance to disrupt the target's action
|
||||||
|
if (pokemon.randBattleSeedInt(2)) {
|
||||||
|
phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:infatuatedLapseImmobilize", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
(phaseManager.getCurrentPhase() as MovePhase).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(pokemon: Pokemon): void {
|
onRemove(pokemon: Pokemon): void {
|
||||||
@ -899,6 +916,12 @@ export class SeedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SeedTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
super.onAdd(pokemon);
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -906,47 +929,51 @@ export class SeedTag extends BattlerTag {
|
|||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
|
this.sourceIndex = source.getBattlerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
if (ret) {
|
if (!ret) {
|
||||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
return false;
|
||||||
if (source) {
|
|
||||||
const cancelled = new BooleanHolder(false);
|
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
|
||||||
|
|
||||||
if (!cancelled.value) {
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
|
||||||
"CommonAnimPhase",
|
|
||||||
source.getBattlerIndex(),
|
|
||||||
pokemon.getBattlerIndex(),
|
|
||||||
CommonAnim.LEECH_SEED,
|
|
||||||
);
|
|
||||||
|
|
||||||
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
|
||||||
const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
|
||||||
"PokemonHealPhase",
|
|
||||||
source.getBattlerIndex(),
|
|
||||||
!reverseDrain ? damage : damage * -1,
|
|
||||||
!reverseDrain
|
|
||||||
? i18next.t("battlerTags:seededLapse", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
})
|
|
||||||
: i18next.t("battlerTags:seededLapseShed", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
}),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
// Check which opponent to restore HP to
|
||||||
|
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SeedTag lapse; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelled = new BooleanHolder(false);
|
||||||
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
|
if (cancelled.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.unshiftNew(
|
||||||
|
"CommonAnimPhase",
|
||||||
|
source.getBattlerIndex(),
|
||||||
|
pokemon.getBattlerIndex(),
|
||||||
|
CommonAnim.LEECH_SEED,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Damage the target and restore our HP (or take damage in the case of liquid ooze)
|
||||||
|
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
|
const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
|
||||||
|
globalScene.phaseManager.unshiftNew(
|
||||||
|
"PokemonHealPhase",
|
||||||
|
source.getBattlerIndex(),
|
||||||
|
reverseDrain ? -damage : damage,
|
||||||
|
i18next.t(reverseDrain ? "battlerTags:seededLapseShed" : "battlerTags:seededLapse", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescriptor(): string {
|
getDescriptor(): string {
|
||||||
@ -1006,7 +1033,7 @@ export class PowderTag extends BattlerTag {
|
|||||||
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
||||||
|
|
||||||
const cancelDamage = new BooleanHolder(false);
|
const cancelDamage = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled: cancelDamage });
|
||||||
if (!cancelDamage.value) {
|
if (!cancelDamage.value) {
|
||||||
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
}
|
}
|
||||||
@ -1056,7 +1083,7 @@ export class NightmareTag extends BattlerTag {
|
|||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
@ -1195,9 +1222,15 @@ export class HelpingHandTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for HelpingHandTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:helpingHandOnAdd", {
|
i18next.t("battlerTags:helpingHandOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -1219,9 +1252,7 @@ export class IngrainTag extends TrappedTag {
|
|||||||
* @returns boolean True if the tag can be added, false otherwise
|
* @returns boolean True if the tag can be added, false otherwise
|
||||||
*/
|
*/
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED);
|
return !pokemon.getTag(BattlerTagType.TRAPPED);
|
||||||
|
|
||||||
return !isTrapped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -1409,7 +1440,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
@ -1420,15 +1451,22 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Condense all these tags into 1 singular tag with a modified message func
|
||||||
export class BindTag extends DamagingTrapTag {
|
export class BindTag extends DamagingTrapTag {
|
||||||
constructor(turnCount: number, sourceId: number) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(BattlerTagType.BIND, CommonAnim.BIND, turnCount, MoveId.BIND, sourceId);
|
super(BattlerTagType.BIND, CommonAnim.BIND, turnCount, MoveId.BIND, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for BindTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:bindOnTrap", {
|
return i18next.t("battlerTags:bindOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonName: getPokemonNameWithAffix(source),
|
||||||
moveName: this.getMoveName(),
|
moveName: this.getMoveName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1440,9 +1478,16 @@ export class WrapTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for WrapTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:wrapOnTrap", {
|
return i18next.t("battlerTags:wrapOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonName: getPokemonNameWithAffix(source),
|
||||||
|
moveName: this.getMoveName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1473,8 +1518,14 @@ export class ClampTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for ClampTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT ASAP";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:clampOnTrap", {
|
return i18next.t("battlerTags:clampOnTrap", {
|
||||||
sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1523,9 +1574,15 @@ export class ThunderCageTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for ThunderCageTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - PLEASE REPORT ASAP";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:thunderCageOnTrap", {
|
return i18next.t("battlerTags:thunderCageOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1536,9 +1593,15 @@ export class InfestationTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for InfestationTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:infestationOnTrap", {
|
return i18next.t("battlerTags:infestationOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1642,7 +1705,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
|
|||||||
*/
|
*/
|
||||||
override onContact(attacker: Pokemon, user: Pokemon): void {
|
override onContact(attacker: Pokemon, user: Pokemon): void {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled });
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
||||||
result: HitResult.INDIRECT,
|
result: HitResult.INDIRECT,
|
||||||
@ -2221,14 +2284,19 @@ export class SaltCuredTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
super.onAdd(pokemon);
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SaltCureTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onAdd(pokemon);
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:saltCuredOnAdd", {
|
i18next.t("battlerTags:saltCuredOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
|
this.sourceIndex = source.getBattlerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -2243,7 +2311,7 @@ export class SaltCuredTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
||||||
@ -2281,8 +2349,14 @@ export class CursedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for CursedTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
super.onAdd(pokemon);
|
super.onAdd(pokemon);
|
||||||
this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
|
this.sourceIndex = source.getBattlerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -2297,7 +2371,7 @@ export class CursedTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
@ -2632,7 +2706,7 @@ export class GulpMissileTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: attacker, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
|
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
|
||||||
@ -2902,7 +2976,13 @@ export class SubstituteTag extends BattlerTag {
|
|||||||
|
|
||||||
/** Sets the Substitute's HP and queues an on-add battle animation that initializes the Substitute's sprite. */
|
/** Sets the Substitute's HP and queues an on-add battle animation that initializes the Substitute's sprite. */
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
this.hp = Math.floor(globalScene.getPokemonById(this.sourceId!)!.getMaxHp() / 4);
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SubstituteTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hp = Math.floor(source.getMaxHp() / 4);
|
||||||
this.sourceInFocus = false;
|
this.sourceInFocus = false;
|
||||||
|
|
||||||
// Queue battle animation and message
|
// Queue battle animation and message
|
||||||
@ -3021,14 +3101,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
|||||||
const ret = super.lapse(pokemon, lapseType);
|
const ret = super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||||
const cancelled = new BooleanHolder(false);
|
pokemon.mysteryEncounterBattleEffects?.(pokemon);
|
||||||
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
|
||||||
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
|
|
||||||
if (!cancelled.value) {
|
|
||||||
if (pokemon.mysteryEncounterBattleEffects) {
|
|
||||||
pokemon.mysteryEncounterBattleEffects(pokemon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -3182,13 +3255,14 @@ export class ImprisonTag extends MoveRestrictionBattlerTag {
|
|||||||
*/
|
*/
|
||||||
public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const source = this.getSourcePokemon();
|
const source = this.getSourcePokemon();
|
||||||
if (source) {
|
if (!source) {
|
||||||
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
console.warn(`Failed to get source Pokemon for ImprisonTag lapse; id: ${this.sourceId}`);
|
||||||
return super.lapse(pokemon, lapseType) && source.isActive(true);
|
return false;
|
||||||
}
|
|
||||||
return source.isActive(true);
|
|
||||||
}
|
}
|
||||||
return false;
|
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||||
|
return super.lapse(pokemon, lapseType) && source.isActive(true);
|
||||||
|
}
|
||||||
|
return source.isActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3248,12 +3322,20 @@ export class SyrupBombTag extends BattlerTag {
|
|||||||
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
|
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
|
||||||
* @param pokemon - The target {@linkcode Pokemon}
|
* @param pokemon - The target {@linkcode Pokemon}
|
||||||
* @param _lapseType - N/A
|
* @param _lapseType - N/A
|
||||||
* @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field
|
* @returns Whether the tag should persist (`turnsRemaining > 0` and source still on field)
|
||||||
*/
|
*/
|
||||||
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||||
if (this.sourceId && !globalScene.getPokemonById(this.sourceId)?.isActive(true)) {
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SyrupBombTag lapse; id: ${this.sourceId}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Syrup bomb clears immediately if source leaves field/faints
|
||||||
|
if (!source.isActive(true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Custom message in lieu of an animation in mainline
|
// Custom message in lieu of an animation in mainline
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:syrupBombLapse", {
|
i18next.t("battlerTags:syrupBombLapse", {
|
||||||
@ -3270,7 +3352,7 @@ export class SyrupBombTag extends BattlerTag {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
return --this.turnCount > 0;
|
return super.lapse(pokemon, _lapseType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,28 +35,28 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
|||||||
case BerryType.APICOT:
|
case BerryType.APICOT:
|
||||||
case BerryType.SALAC:
|
case BerryType.SALAC:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
||||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
|
return pokemon.getHpRatio() < hpRatioReq.value && pokemon.getStatStage(stat) < 6;
|
||||||
};
|
};
|
||||||
case BerryType.LANSAT:
|
case BerryType.LANSAT:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
|
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
|
||||||
};
|
};
|
||||||
case BerryType.STARF:
|
case BerryType.STARF:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return pokemon.getHpRatio() < 0.25;
|
return pokemon.getHpRatio() < 0.25;
|
||||||
};
|
};
|
||||||
case BerryType.LEPPA:
|
case BerryType.LEPPA:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
|
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
case BerryType.ENIGMA:
|
case BerryType.ENIGMA:
|
||||||
{
|
{
|
||||||
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
||||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed);
|
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: hpHealed });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"PokemonHealPhase",
|
"PokemonHealPhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
@ -105,7 +105,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
|
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
|
||||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||||
const statStages = new NumberHolder(1);
|
const statStages = new NumberHolder(1);
|
||||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages);
|
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: statStages });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
{
|
{
|
||||||
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
||||||
const stages = new NumberHolder(2);
|
const stages = new NumberHolder(2);
|
||||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages);
|
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: stages });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
|
@ -175,7 +175,7 @@ export class Egg {
|
|||||||
|
|
||||||
this._sourceType = eggOptions?.sourceType ?? undefined;
|
this._sourceType = eggOptions?.sourceType ?? undefined;
|
||||||
this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
|
this._hatchWaves = eggOptions?.hatchWaves ?? this.getEggTierDefaultHatchWaves();
|
||||||
this._timestamp = eggOptions?.timestamp ?? new Date().getTime();
|
this._timestamp = eggOptions?.timestamp ?? Date.now();
|
||||||
|
|
||||||
// First roll shiny and variant so we can filter if species with an variant exist
|
// First roll shiny and variant so we can filter if species with an variant exist
|
||||||
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
|
this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny());
|
||||||
@ -255,7 +255,7 @@ export class Egg {
|
|||||||
|
|
||||||
// Sets the hidden ability if a hidden ability exists and
|
// Sets the hidden ability if a hidden ability exists and
|
||||||
// the override is set or the egg hits the chance
|
// the override is set or the egg hits the chance
|
||||||
let abilityIndex: number | undefined = undefined;
|
let abilityIndex: number | undefined;
|
||||||
const sameSpeciesEggHACheck =
|
const sameSpeciesEggHACheck =
|
||||||
this._sourceType === EggSourceType.SAME_SPECIES_EGG && !randSeedInt(SAME_SPECIES_EGG_HA_RATE);
|
this._sourceType === EggSourceType.SAME_SPECIES_EGG && !randSeedInt(SAME_SPECIES_EGG_HA_RATE);
|
||||||
const gachaEggHACheck = !(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !randSeedInt(GACHA_EGG_HA_RATE);
|
const gachaEggHACheck = !(this._sourceType === EggSourceType.SAME_SPECIES_EGG) && !randSeedInt(GACHA_EGG_HA_RATE);
|
||||||
@ -524,7 +524,7 @@ export class Egg {
|
|||||||
/**
|
/**
|
||||||
* Rolls whether the egg is shiny or not.
|
* Rolls whether the egg is shiny or not.
|
||||||
* @returns `true` if the egg is shiny
|
* @returns `true` if the egg is shiny
|
||||||
**/
|
*/
|
||||||
private rollShiny(): boolean {
|
private rollShiny(): boolean {
|
||||||
let shinyChance = GACHA_DEFAULT_SHINY_RATE;
|
let shinyChance = GACHA_DEFAULT_SHINY_RATE;
|
||||||
switch (this._sourceType) {
|
switch (this._sourceType) {
|
||||||
|
@ -33,11 +33,7 @@ import type { ArenaTrapTag } from "../arena-tag";
|
|||||||
import { WeakenMoveTypeTag } from "../arena-tag";
|
import { WeakenMoveTypeTag } from "../arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import {
|
import {
|
||||||
applyAbAttrs,
|
applyAbAttrs
|
||||||
applyPostAttackAbAttrs,
|
|
||||||
applyPostItemLostAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
applyPreDefendAbAttrs
|
|
||||||
} from "../abilities/apply-ab-attrs";
|
} from "../abilities/apply-ab-attrs";
|
||||||
import { allAbilities, allMoves } from "../data-lists";
|
import { allAbilities, allMoves } from "../data-lists";
|
||||||
import {
|
import {
|
||||||
@ -89,9 +85,10 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
|
|||||||
import { MultiHitType } from "#enums/MultiHitType";
|
import { MultiHitType } from "#enums/MultiHitType";
|
||||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves";
|
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves";
|
||||||
import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||||
import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
||||||
import { applyMoveAttrs } from "./apply-attrs";
|
import { applyMoveAttrs } from "./apply-attrs";
|
||||||
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
||||||
|
import { AbAttrBaseParams, AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "../abilities/ability";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
|
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
|
||||||
@ -347,7 +344,7 @@ export default abstract class Move implements Localizable {
|
|||||||
|
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
// TODO: Allow this to be simulated
|
// TODO: Allow this to be simulated
|
||||||
applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", {pokemon: user, bypassed});
|
||||||
|
|
||||||
return !bypassed.value
|
return !bypassed.value
|
||||||
&& !this.hasFlag(MoveFlags.SOUND_BASED)
|
&& !this.hasFlag(MoveFlags.SOUND_BASED)
|
||||||
@ -645,7 +642,7 @@ export default abstract class Move implements Localizable {
|
|||||||
case MoveFlags.IGNORE_ABILITIES:
|
case MoveFlags.IGNORE_ABILITIES:
|
||||||
if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
|
if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
|
||||||
const abilityEffectsIgnored = new BooleanHolder(false);
|
const abilityEffectsIgnored = new BooleanHolder(false);
|
||||||
applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this);
|
applyAbAttrs("MoveAbilityBypassAbAttr", {pokemon: user, cancelled: abilityEffectsIgnored, move: this});
|
||||||
if (abilityEffectsIgnored.value) {
|
if (abilityEffectsIgnored.value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -683,13 +680,7 @@ export default abstract class Move implements Localizable {
|
|||||||
* @returns boolean: false if any of the apply()'s return false, else true
|
* @returns boolean: false if any of the apply()'s return false, else true
|
||||||
*/
|
*/
|
||||||
applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean {
|
applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||||
for (const condition of this.conditions) {
|
return this.conditions.every(cond => cond.apply(user, target, move));
|
||||||
if (!condition.apply(user, target, move)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -762,7 +753,7 @@ export default abstract class Move implements Localizable {
|
|||||||
const moveAccuracy = new NumberHolder(this.accuracy);
|
const moveAccuracy = new NumberHolder(this.accuracy);
|
||||||
|
|
||||||
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
|
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
|
||||||
applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy);
|
applyAbAttrs("WonderSkinAbAttr", {pokemon: target, opponent: user, move: this, simulated, accuracy: moveAccuracy});
|
||||||
|
|
||||||
if (moveAccuracy.value === -1) {
|
if (moveAccuracy.value === -1) {
|
||||||
return moveAccuracy.value;
|
return moveAccuracy.value;
|
||||||
@ -805,17 +796,25 @@ export default abstract class Move implements Localizable {
|
|||||||
const typeChangeMovePowerMultiplier = new NumberHolder(1);
|
const typeChangeMovePowerMultiplier = new NumberHolder(1);
|
||||||
const typeChangeHolder = new NumberHolder(this.type);
|
const typeChangeHolder = new NumberHolder(this.type);
|
||||||
|
|
||||||
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
|
applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier});
|
||||||
|
|
||||||
const sourceTeraType = source.getTeraType();
|
const sourceTeraType = source.getTeraType();
|
||||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||||
power.value = 60;
|
power.value = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPreAttackAbAttrs("VariableMovePowerAbAttr", source, target, this, simulated, power);
|
const abAttrParams: PreAttackModifyPowerAbAttrParams = {
|
||||||
|
pokemon: source,
|
||||||
|
opponent: target,
|
||||||
|
simulated,
|
||||||
|
power,
|
||||||
|
move: this,
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAbAttrs("VariableMovePowerAbAttr", abAttrParams);
|
||||||
const ally = source.getAlly();
|
const ally = source.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power);
|
applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldAuras = new Set(
|
const fieldAuras = new Set(
|
||||||
@ -827,11 +826,12 @@ export default abstract class Move implements Localizable {
|
|||||||
.flat(),
|
.flat(),
|
||||||
);
|
);
|
||||||
for (const aura of fieldAuras) {
|
for (const aura of fieldAuras) {
|
||||||
aura.applyPreAttack(source, null, simulated, target, this, [ power ]);
|
// TODO: Refactor the fieldAura attribute so that its apply method is not directly called
|
||||||
|
aura.apply({pokemon: source, simulated, opponent: target, move: this, power});
|
||||||
}
|
}
|
||||||
|
|
||||||
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
alliedField.forEach(p => applyPreAttackAbAttrs("UserFieldMoveTypePowerBoostAbAttr", p, target, this, simulated, power));
|
alliedField.forEach(p => applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power}));
|
||||||
|
|
||||||
power.value *= typeChangeMovePowerMultiplier.value;
|
power.value *= typeChangeMovePowerMultiplier.value;
|
||||||
|
|
||||||
@ -858,7 +858,7 @@ export default abstract class Move implements Localizable {
|
|||||||
const priority = new NumberHolder(this.priority);
|
const priority = new NumberHolder(this.priority);
|
||||||
|
|
||||||
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
|
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
|
||||||
applyAbAttrs("ChangeMovePriorityAbAttr", user, null, simulated, this, priority);
|
applyAbAttrs("ChangeMovePriorityAbAttr", {pokemon: user, simulated, move: this, priority});
|
||||||
|
|
||||||
return priority.value;
|
return priority.value;
|
||||||
}
|
}
|
||||||
@ -1310,7 +1310,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
|
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
|
||||||
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
|
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
|
||||||
|
|
||||||
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", user, null, !showAbility, moveChance, move);
|
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", {pokemon: user, simulated: !showAbility, chance: moveChance, move});
|
||||||
|
|
||||||
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
|
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
|
||||||
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
@ -1318,7 +1318,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!selfEffect) {
|
if (!selfEffect) {
|
||||||
applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance);
|
applyAbAttrs("IgnoreMoveEffectsAbAttr", {pokemon: target, move, simulated: !showAbility, chance: moveChance});
|
||||||
}
|
}
|
||||||
return moveChance.value;
|
return moveChance.value;
|
||||||
}
|
}
|
||||||
@ -1709,8 +1709,9 @@ export class RecoilAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
if (!this.unblockable) {
|
if (!this.unblockable) {
|
||||||
applyAbAttrs("BlockRecoilDamageAttr", user, cancelled);
|
const abAttrParams: AbAttrParamsWithCancel = {pokemon: user, cancelled};
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockRecoilDamageAttr", abAttrParams);
|
||||||
|
applyAbAttrs("BlockNonDirectDamageAbAttr", abAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -1843,7 +1844,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
// Check to see if the Pokemon has an ability that blocks non-direct damage
|
// Check to see if the Pokemon has an ability that blocks non-direct damage
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
|
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
|
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
|
||||||
@ -2042,7 +2043,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (!isNullOrUndefined(targetAlly)) {
|
if (!isNullOrUndefined(targetAlly)) {
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: targetAlly, cancelled});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
|
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
|
||||||
@ -2414,7 +2415,7 @@ export class MultiHitAttr extends MoveAttr {
|
|||||||
{
|
{
|
||||||
const rand = user.randBattleSeedInt(20);
|
const rand = user.randBattleSeedInt(20);
|
||||||
const hitValue = new NumberHolder(rand);
|
const hitValue = new NumberHolder(rand);
|
||||||
applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue);
|
applyAbAttrs("MaxMultiHitAbAttr", {pokemon: user, hits: hitValue});
|
||||||
if (hitValue.value >= 13) {
|
if (hitValue.value >= 13) {
|
||||||
return 2;
|
return 2;
|
||||||
} else if (hitValue.value >= 6) {
|
} else if (hitValue.value >= 6) {
|
||||||
@ -2522,7 +2523,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||||
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
|
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
|
||||||
applyPostAttackAbAttrs("ConfusionOnStatusEffectAbAttr", user, target, move, null, false, this.effect);
|
applyAbAttrs("ConfusionOnStatusEffectAbAttr", {pokemon: user, opponent: target, move, effect: this.effect});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2574,7 +2575,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||||
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||||
|
|
||||||
if (target.status) {
|
if (target.status || !statusToApply) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||||
@ -2590,7 +2591,8 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
|
const statusToApply = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||||
|
return !target.status && statusToApply && target.canSetStatus(statusToApply, true, false, user) ? -10 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2678,7 +2680,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
|||||||
// Check for abilities that block item theft
|
// Check for abilities that block item theft
|
||||||
// TODO: This should not trigger if the target would faint beforehand
|
// TODO: This should not trigger if the target would faint beforehand
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
@ -2795,8 +2797,8 @@ export class EatBerryAttr extends MoveEffectAttr {
|
|||||||
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
|
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
|
||||||
// consumer eats berry, owner triggers unburden and similar effects
|
// consumer eats berry, owner triggers unburden and similar effects
|
||||||
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
|
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false);
|
applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner});
|
||||||
applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false));
|
applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer});
|
||||||
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
|
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2821,7 +2823,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
|||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
// check for abilities that block item theft
|
// check for abilities that block item theft
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
|
||||||
if (cancelled.value === true) {
|
if (cancelled.value === true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2835,7 +2837,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
|||||||
|
|
||||||
// pick a random berry and eat it
|
// pick a random berry and eat it
|
||||||
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", target, false);
|
applyAbAttrs("PostItemLostAbAttr", {pokemon: target});
|
||||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
||||||
globalScene.phaseManager.queueMessage(message);
|
globalScene.phaseManager.queueMessage(message);
|
||||||
this.reduceBerryModifier(target);
|
this.reduceBerryModifier(target);
|
||||||
@ -3026,7 +3028,7 @@ export class OneHitKOAttr extends MoveAttr {
|
|||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => {
|
return (user, target, move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled);
|
applyAbAttrs("BlockOneHitKOAbAttr", {pokemon: target, cancelled});
|
||||||
return !cancelled.value && user.level >= target.level;
|
return !cancelled.value && user.level >= target.level;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -5438,7 +5440,7 @@ export class NoEffectAttr extends MoveAttr {
|
|||||||
|
|
||||||
const crashDamageFunc = (user: Pokemon, move: Move) => {
|
const crashDamageFunc = (user: Pokemon, move: Move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6437,9 +6439,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
|
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
|
||||||
const blockedByAbility = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
|
applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled});
|
||||||
if (blockedByAbility.value) {
|
if (cancelled.value) {
|
||||||
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
|
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6478,7 +6480,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blockedByAbility = new BooleanHolder(false);
|
const blockedByAbility = new BooleanHolder(false);
|
||||||
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
|
applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled: blockedByAbility});
|
||||||
if (blockedByAbility.value) {
|
if (blockedByAbility.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6887,12 +6889,12 @@ export class RandomMovesetMoveAttr extends CallMoveAttr {
|
|||||||
// includeParty will be true for Assist, false for Sleep Talk
|
// includeParty will be true for Assist, false for Sleep Talk
|
||||||
let allies: Pokemon[];
|
let allies: Pokemon[];
|
||||||
if (this.includeParty) {
|
if (this.includeParty) {
|
||||||
allies = user.isPlayer() ? globalScene.getPlayerParty().filter(p => p !== user) : globalScene.getEnemyParty().filter(p => p !== user);
|
allies = (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user);
|
||||||
} else {
|
} else {
|
||||||
allies = [ user ];
|
allies = [ user ];
|
||||||
}
|
}
|
||||||
const partyMoveset = allies.map(p => p.moveset).flat();
|
const partyMoveset = allies.flatMap(p => p.moveset);
|
||||||
const moves = partyMoveset.filter(m => !this.invalidMoves.has(m!.moveId) && !m!.getMove().name.endsWith(" (N)"));
|
const moves = partyMoveset.filter(m => !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)"));
|
||||||
if (moves.length === 0) {
|
if (moves.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -7987,7 +7989,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen
|
|||||||
|
|
||||||
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled));
|
globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled}));
|
||||||
// Queue a message if an ability prevented usage of the move
|
// Queue a message if an ability prevented usage of the move
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));
|
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));
|
||||||
|
@ -16,7 +16,7 @@ import type Move from "./move";
|
|||||||
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
|
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
|
||||||
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
|
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
|
||||||
* @see {@linkcode getName} - returns name of {@linkcode Move}.
|
* @see {@linkcode getName} - returns name of {@linkcode Move}.
|
||||||
**/
|
*/
|
||||||
export class PokemonMove {
|
export class PokemonMove {
|
||||||
public moveId: MoveId;
|
public moveId: MoveId;
|
||||||
public ppUsed: number;
|
public ppUsed: number;
|
||||||
|
@ -135,7 +135,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
);
|
);
|
||||||
clownConfig.setPartyTemplates(clownPartyTemplate);
|
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||||
clownConfig.setDoubleOnly();
|
clownConfig.setDoubleOnly();
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||||
|
|
||||||
// Generate random ability for Blacephalon from pool
|
// Generate random ability for Blacephalon from pool
|
||||||
|
@ -328,7 +328,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
.withOptionPhase(async () => {
|
.withOptionPhase(async () => {
|
||||||
// Show the Oricorio a dance, and recruit it
|
// Show the Oricorio a dance, and recruit it
|
||||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||||
const oricorio = encounter.misc.oricorioData.toPokemon();
|
const oricorio = encounter.misc.oricorioData.toPokemon() as EnemyPokemon;
|
||||||
oricorio.passive = true;
|
oricorio.passive = true;
|
||||||
|
|
||||||
// Ensure the Oricorio's moveset gains the Dance move the player used
|
// Ensure the Oricorio's moveset gains the Dance move the player used
|
||||||
|
@ -92,7 +92,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounter
|
|||||||
const brutalConfig = trainerConfigs[brutalTrainerType].clone();
|
const brutalConfig = trainerConfigs[brutalTrainerType].clone();
|
||||||
brutalConfig.title = trainerConfigs[brutalTrainerType].title;
|
brutalConfig.title = trainerConfigs[brutalTrainerType].title;
|
||||||
brutalConfig.setPartyTemplates(e4Template);
|
brutalConfig.setPartyTemplates(e4Template);
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
|
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
|
||||||
female = false;
|
female = false;
|
||||||
if (brutalConfig.hasGenders) {
|
if (brutalConfig.hasGenders) {
|
||||||
|
@ -24,7 +24,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -221,7 +221,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||||||
|
|
||||||
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
|
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
||||||
|
@ -1226,7 +1226,7 @@ export function calculateMEAggregateStats(baseSpawnWeight: number) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const value of meanEncountersPerRunPerBiomeSorted) {
|
for (const value of meanEncountersPerRunPerBiomeSorted) {
|
||||||
stats += value[0] + "avg valid floors " + meanMEFloorsPerRunPerBiome.get(value[0]) + ", avg MEs ${value[1]},\n";
|
stats += value[0] + "avg valid floors " + meanMEFloorsPerRunPerBiome.get(value[0]) + `, avg MEs ${value[1]},\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(stats);
|
console.log(stats);
|
||||||
|
@ -96,8 +96,8 @@ export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): Po
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string {
|
export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string {
|
||||||
const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-\']+)(.*?)$/i;
|
const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-']+)(.*?)$/i;
|
||||||
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-\'])(.*?)$/i;
|
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-'])(.*?)$/i;
|
||||||
|
|
||||||
const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n));
|
const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n));
|
||||||
const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : ""));
|
const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : ""));
|
||||||
@ -134,7 +134,7 @@ export function getFusedSpeciesName(speciesAName: string, speciesBName: string):
|
|||||||
if (fragBMatch) {
|
if (fragBMatch) {
|
||||||
const lastCharA = fragA.slice(fragA.length - 1);
|
const lastCharA = fragA.slice(fragA.length - 1);
|
||||||
const prevCharB = fragBMatch[1].slice(fragBMatch.length - 1);
|
const prevCharB = fragBMatch[1].slice(fragBMatch.length - 1);
|
||||||
fragB = (/[\-']/.test(prevCharB) ? prevCharB : "") + fragBMatch[2] || prevCharB;
|
fragB = (/[-']/.test(prevCharB) ? prevCharB : "") + fragBMatch[2] || prevCharB;
|
||||||
if (lastCharA === fragB[0]) {
|
if (lastCharA === fragB[0]) {
|
||||||
if (/[aiu]/.test(lastCharA)) {
|
if (/[aiu]/.test(lastCharA)) {
|
||||||
fragB = fragB.slice(1);
|
fragB = fragB.slice(1);
|
||||||
@ -379,7 +379,7 @@ export abstract class PokemonSpeciesForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string {
|
getSpriteAtlasPath(female: boolean, formIndex?: number, shiny?: boolean, variant?: number, back?: boolean): string {
|
||||||
const spriteId = this.getSpriteId(female, formIndex, shiny, variant, back).replace(/\_{2}/g, "/");
|
const spriteId = this.getSpriteId(female, formIndex, shiny, variant, back).replace(/_{2}/g, "/");
|
||||||
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,8 +478,8 @@ export abstract class PokemonSpeciesForm {
|
|||||||
case SpeciesId.DUDUNSPARCE:
|
case SpeciesId.DUDUNSPARCE:
|
||||||
break;
|
break;
|
||||||
case SpeciesId.ZACIAN:
|
case SpeciesId.ZACIAN:
|
||||||
|
// biome-ignore lint/suspicious/noFallthroughSwitchClause: Intentionally falls through
|
||||||
case SpeciesId.ZAMAZENTA:
|
case SpeciesId.ZAMAZENTA:
|
||||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: Falls through
|
|
||||||
if (formSpriteKey.startsWith("behemoth")) {
|
if (formSpriteKey.startsWith("behemoth")) {
|
||||||
formSpriteKey = "crowned";
|
formSpriteKey = "crowned";
|
||||||
}
|
}
|
||||||
@ -569,7 +569,7 @@ export abstract class PokemonSpeciesForm {
|
|||||||
const rootSpeciesId = this.getRootSpeciesId();
|
const rootSpeciesId = this.getRootSpeciesId();
|
||||||
for (const moveId of moveset) {
|
for (const moveId of moveset) {
|
||||||
if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) {
|
if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) {
|
||||||
const eggMoveIndex = speciesEggMoves[rootSpeciesId].findIndex(m => m === moveId);
|
const eggMoveIndex = speciesEggMoves[rootSpeciesId].indexOf(moveId);
|
||||||
if (eggMoveIndex > -1 && eggMoves & (1 << eggMoveIndex)) {
|
if (eggMoveIndex > -1 && eggMoves & (1 << eggMoveIndex)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ export class TrainerConfig {
|
|||||||
* @param {string} [nameFemale] The name of the female trainer. If 'Ivy', a localized name will be assigned.
|
* @param {string} [nameFemale] The name of the female trainer. If 'Ivy', a localized name will be assigned.
|
||||||
* @param {TrainerType | string} [femaleEncounterBgm] The encounter BGM for the female trainer, which can be a TrainerType or a string.
|
* @param {TrainerType | string} [femaleEncounterBgm] The encounter BGM for the female trainer, which can be a TrainerType or a string.
|
||||||
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
||||||
**/
|
*/
|
||||||
setHasGenders(nameFemale?: string, femaleEncounterBgm?: TrainerType | string): TrainerConfig {
|
setHasGenders(nameFemale?: string, femaleEncounterBgm?: TrainerType | string): TrainerConfig {
|
||||||
// If the female name is 'Ivy' (the rival), assign a localized name.
|
// If the female name is 'Ivy' (the rival), assign a localized name.
|
||||||
if (nameFemale === "Ivy") {
|
if (nameFemale === "Ivy") {
|
||||||
@ -335,7 +335,7 @@ export class TrainerConfig {
|
|||||||
if (doubleEncounterBgm) {
|
if (doubleEncounterBgm) {
|
||||||
this.doubleEncounterBgm =
|
this.doubleEncounterBgm =
|
||||||
typeof doubleEncounterBgm === "number"
|
typeof doubleEncounterBgm === "number"
|
||||||
? TrainerType[doubleEncounterBgm].toString().replace(/\_/g, " ").toLowerCase()
|
? TrainerType[doubleEncounterBgm].toString().replace(/_/g, " ").toLowerCase()
|
||||||
: doubleEncounterBgm;
|
: doubleEncounterBgm;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
@ -540,7 +540,7 @@ export class TrainerConfig {
|
|||||||
* @param {SpeciesId | SpeciesId[]} signatureSpecies The signature species for the evil team leader.
|
* @param {SpeciesId | SpeciesId[]} signatureSpecies The signature species for the evil team leader.
|
||||||
* @param specialtyType The specialty Type of the admin, if they have one
|
* @param specialtyType The specialty Type of the admin, if they have one
|
||||||
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
||||||
* **/
|
*/
|
||||||
initForEvilTeamAdmin(
|
initForEvilTeamAdmin(
|
||||||
title: string,
|
title: string,
|
||||||
poolName: EvilTeam,
|
poolName: EvilTeam,
|
||||||
@ -581,7 +581,7 @@ export class TrainerConfig {
|
|||||||
* Initializes the trainer configuration for a Stat Trainer, as part of the Trainer's Test Mystery Encounter.
|
* Initializes the trainer configuration for a Stat Trainer, as part of the Trainer's Test Mystery Encounter.
|
||||||
* @param _isMale Whether the stat trainer is Male or Female (for localization of the title).
|
* @param _isMale Whether the stat trainer is Male or Female (for localization of the title).
|
||||||
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
||||||
**/
|
*/
|
||||||
initForStatTrainer(_isMale = false): TrainerConfig {
|
initForStatTrainer(_isMale = false): TrainerConfig {
|
||||||
if (!getIsInitialized()) {
|
if (!getIsInitialized()) {
|
||||||
initI18n();
|
initI18n();
|
||||||
@ -608,7 +608,7 @@ export class TrainerConfig {
|
|||||||
* @param {PokemonType} specialtyType The specialty type for the evil team Leader.
|
* @param {PokemonType} specialtyType The specialty type for the evil team Leader.
|
||||||
* @param boolean Whether or not this is the rematch fight
|
* @param boolean Whether or not this is the rematch fight
|
||||||
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
||||||
* **/
|
*/
|
||||||
initForEvilTeamLeader(
|
initForEvilTeamLeader(
|
||||||
title: string,
|
title: string,
|
||||||
signatureSpecies: (SpeciesId | SpeciesId[])[],
|
signatureSpecies: (SpeciesId | SpeciesId[])[],
|
||||||
@ -651,7 +651,7 @@ export class TrainerConfig {
|
|||||||
* @param ignoreMinTeraWave Whether the Gym Leader always uses Tera (true), or only Teras after {@linkcode GYM_LEADER_TERA_WAVE} (false). Defaults to false.
|
* @param ignoreMinTeraWave Whether the Gym Leader always uses Tera (true), or only Teras after {@linkcode GYM_LEADER_TERA_WAVE} (false). Defaults to false.
|
||||||
* @param teraSlot Optional, sets the party member in this slot to Terastallize. Wraps based on party size.
|
* @param teraSlot Optional, sets the party member in this slot to Terastallize. Wraps based on party size.
|
||||||
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
||||||
* **/
|
*/
|
||||||
initForGymLeader(
|
initForGymLeader(
|
||||||
signatureSpecies: (SpeciesId | SpeciesId[])[],
|
signatureSpecies: (SpeciesId | SpeciesId[])[],
|
||||||
isMale: boolean,
|
isMale: boolean,
|
||||||
@ -709,7 +709,7 @@ export class TrainerConfig {
|
|||||||
* @param specialtyType - The specialty type for the Elite Four member.
|
* @param specialtyType - The specialty type for the Elite Four member.
|
||||||
* @param teraSlot - Optional, sets the party member in this slot to Terastallize.
|
* @param teraSlot - Optional, sets the party member in this slot to Terastallize.
|
||||||
* @returns The updated TrainerConfig instance.
|
* @returns The updated TrainerConfig instance.
|
||||||
**/
|
*/
|
||||||
initForEliteFour(
|
initForEliteFour(
|
||||||
signatureSpecies: (SpeciesId | SpeciesId[])[],
|
signatureSpecies: (SpeciesId | SpeciesId[])[],
|
||||||
isMale: boolean,
|
isMale: boolean,
|
||||||
@ -765,7 +765,7 @@ export class TrainerConfig {
|
|||||||
* @param {SpeciesId | SpeciesId[]} signatureSpecies The signature species for the Champion.
|
* @param {SpeciesId | SpeciesId[]} signatureSpecies The signature species for the Champion.
|
||||||
* @param isMale Whether the Champion is Male or Female (for localization of the title).
|
* @param isMale Whether the Champion is Male or Female (for localization of the title).
|
||||||
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
* @returns {TrainerConfig} The updated TrainerConfig instance.
|
||||||
**/
|
*/
|
||||||
initForChampion(isMale: boolean): TrainerConfig {
|
initForChampion(isMale: boolean): TrainerConfig {
|
||||||
// Check if the internationalization (i18n) system is initialized.
|
// Check if the internationalization (i18n) system is initialized.
|
||||||
if (!getIsInitialized()) {
|
if (!getIsInitialized()) {
|
||||||
@ -815,7 +815,7 @@ export class TrainerConfig {
|
|||||||
* @param {TrainerSlot} trainerSlot - The slot to determine which title to use. Defaults to TrainerSlot.NONE.
|
* @param {TrainerSlot} trainerSlot - The slot to determine which title to use. Defaults to TrainerSlot.NONE.
|
||||||
* @param {TrainerVariant} variant - The variant of the trainer to determine the specific title.
|
* @param {TrainerVariant} variant - The variant of the trainer to determine the specific title.
|
||||||
* @returns {string} - The title of the trainer.
|
* @returns {string} - The title of the trainer.
|
||||||
**/
|
*/
|
||||||
getTitle(trainerSlot: TrainerSlot = TrainerSlot.NONE, variant: TrainerVariant): string {
|
getTitle(trainerSlot: TrainerSlot = TrainerSlot.NONE, variant: TrainerVariant): string {
|
||||||
const ret = this.name;
|
const ret = this.name;
|
||||||
|
|
||||||
|
@ -20,11 +20,7 @@ import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
|
|||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { Terrain, TerrainType } from "#app/data/terrain";
|
import { Terrain, TerrainType } from "#app/data/terrain";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostTerrainChangeAbAttrs,
|
|
||||||
applyPostWeatherChangeAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
@ -372,7 +368,7 @@ export class Arena {
|
|||||||
pokemon.findAndRemoveTags(
|
pokemon.findAndRemoveTags(
|
||||||
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
|
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
|
||||||
);
|
);
|
||||||
applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather);
|
applyAbAttrs("PostWeatherChangeAbAttr", { pokemon, weather });
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -461,8 +457,8 @@ export class Arena {
|
|||||||
pokemon.findAndRemoveTags(
|
pokemon.findAndRemoveTags(
|
||||||
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
|
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
|
||||||
);
|
);
|
||||||
applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain);
|
applyAbAttrs("PostTerrainChangeAbAttr", { pokemon, terrain });
|
||||||
applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false);
|
applyAbAttrs("TerrainEventTypeChangeAbAttr", { pokemon });
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -108,23 +108,8 @@ import { WeatherType } from "#enums/weather-type";
|
|||||||
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||||
import type { Ability } from "#app/data/abilities/ability";
|
import type { Ability, PreAttackModifyDamageAbAttrParams } from "#app/data/abilities/ability";
|
||||||
import {
|
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyStatMultiplierAbAttrs,
|
|
||||||
applyPreApplyBattlerTagAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
applyPreDefendAbAttrs,
|
|
||||||
applyPreSetStatusAbAttrs,
|
|
||||||
applyFieldStatMultiplierAbAttrs,
|
|
||||||
applyCheckTrappedAbAttrs,
|
|
||||||
applyPostDamageAbAttrs,
|
|
||||||
applyPostItemLostAbAttrs,
|
|
||||||
applyOnGainAbAttrs,
|
|
||||||
applyPreLeaveFieldAbAttrs,
|
|
||||||
applyOnLoseAbAttrs,
|
|
||||||
applyAllyStatMultiplierAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { allAbilities } from "#app/data/data-lists";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import type PokemonData from "#app/system/pokemon-data";
|
import type PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
@ -189,7 +174,7 @@ import { HitResult } from "#enums/hit-result";
|
|||||||
import { AiType } from "#enums/ai-type";
|
import { AiType } from "#enums/ai-type";
|
||||||
import type { MoveResult } from "#enums/move-result";
|
import type { MoveResult } from "#enums/move-result";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types";
|
import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types";
|
||||||
|
|
||||||
/** Base typeclass for damage parameter methods, used for DRY */
|
/** Base typeclass for damage parameter methods, used for DRY */
|
||||||
type damageParams = {
|
type damageParams = {
|
||||||
@ -898,12 +883,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSpriteAtlasPath(ignoreOverride?: boolean): string {
|
getSpriteAtlasPath(ignoreOverride?: boolean): string {
|
||||||
const spriteId = this.getSpriteId(ignoreOverride).replace(/\_{2}/g, "/");
|
const spriteId = this.getSpriteId(ignoreOverride).replace(/_{2}/g, "/");
|
||||||
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string {
|
getBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string {
|
||||||
const spriteId = this.getBattleSpriteId(back, ignoreOverride).replace(/\_{2}/g, "/");
|
const spriteId = this.getBattleSpriteId(back, ignoreOverride).replace(/_{2}/g, "/");
|
||||||
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -977,7 +962,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFusionBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string {
|
getFusionBattleSpriteAtlasPath(back?: boolean, ignoreOverride?: boolean): string {
|
||||||
return this.getFusionBattleSpriteId(back, ignoreOverride).replace(/\_{2}/g, "/");
|
return this.getFusionBattleSpriteId(back, ignoreOverride).replace(/_{2}/g, "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
getIconAtlasKey(ignoreOverride = false, useIllusion = true): string {
|
getIconAtlasKey(ignoreOverride = false, useIllusion = true): string {
|
||||||
@ -1364,7 +1349,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
||||||
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
||||||
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
||||||
applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
|
applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage });
|
||||||
const critBoostTag = source.getTag(CritBoostTag);
|
const critBoostTag = source.getTag(CritBoostTag);
|
||||||
if (critBoostTag) {
|
if (critBoostTag) {
|
||||||
// Dragon cheer only gives +1 crit stage to non-dragon types
|
// Dragon cheer only gives +1 crit stage to non-dragon types
|
||||||
@ -1415,46 +1400,52 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
simulated = true,
|
simulated = true,
|
||||||
ignoreHeldItems = false,
|
ignoreHeldItems = false,
|
||||||
): number {
|
): number {
|
||||||
const statValue = new NumberHolder(this.getStat(stat, false));
|
const statVal = new NumberHolder(this.getStat(stat, false));
|
||||||
if (!ignoreHeldItems) {
|
if (!ignoreHeldItems) {
|
||||||
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
|
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
||||||
const fieldApplied = new BooleanHolder(false);
|
const fieldApplied = new BooleanHolder(false);
|
||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
applyFieldStatMultiplierAbAttrs(
|
applyAbAttrs("FieldMultiplyStatAbAttr", {
|
||||||
"FieldMultiplyStatAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
statValue,
|
statVal,
|
||||||
this,
|
target: this,
|
||||||
fieldApplied,
|
hasApplied: fieldApplied,
|
||||||
simulated,
|
simulated,
|
||||||
);
|
});
|
||||||
if (fieldApplied.value) {
|
if (fieldApplied.value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, stat, statValue, simulated);
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
|
pokemon: this,
|
||||||
|
stat,
|
||||||
|
statVal,
|
||||||
|
simulated,
|
||||||
|
// TODO: maybe just don't call this if the move is none?
|
||||||
|
move: move ?? allMoves[MoveId.NONE],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyAllyStatMultiplierAbAttrs(
|
applyAbAttrs("AllyStatMultiplierAbAttr", {
|
||||||
"AllyStatMultiplierAbAttr",
|
pokemon: ally,
|
||||||
ally,
|
|
||||||
stat,
|
stat,
|
||||||
statValue,
|
statVal,
|
||||||
simulated,
|
simulated,
|
||||||
this,
|
// TODO: maybe just don't call this if the move is none?
|
||||||
move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
|
move: move ?? allMoves[MoveId.NONE],
|
||||||
);
|
ignoreAbility: move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret =
|
let ret =
|
||||||
statValue.value *
|
statVal.value *
|
||||||
this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
|
this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
|
||||||
|
|
||||||
switch (stat) {
|
switch (stat) {
|
||||||
@ -2045,20 +2036,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ability New Ability
|
* @param ability New Ability
|
||||||
*/
|
*/
|
||||||
public setTempAbility(ability: Ability, passive = false): void {
|
public setTempAbility(ability: Ability, passive = false): void {
|
||||||
applyOnLoseAbAttrs(this, passive);
|
applyOnLoseAbAttrs({ pokemon: this, passive });
|
||||||
if (passive) {
|
if (passive) {
|
||||||
this.summonData.passiveAbility = ability.id;
|
this.summonData.passiveAbility = ability.id;
|
||||||
} else {
|
} else {
|
||||||
this.summonData.ability = ability.id;
|
this.summonData.ability = ability.id;
|
||||||
}
|
}
|
||||||
applyOnGainAbAttrs(this, passive);
|
applyOnGainAbAttrs({ pokemon: this, passive });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suppresses an ability and calls its onlose attributes
|
* Suppresses an ability and calls its onlose attributes
|
||||||
*/
|
*/
|
||||||
public suppressAbility() {
|
public suppressAbility() {
|
||||||
[true, false].forEach(passive => applyOnLoseAbAttrs(this, passive));
|
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: this, passive }));
|
||||||
this.summonData.abilitySuppressed = true;
|
this.summonData.abilitySuppressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2194,7 +2185,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
||||||
|
|
||||||
// This will trigger the ability overlay so only call this function when necessary
|
// This will trigger the ability overlay so only call this function when necessary
|
||||||
applyAbAttrs("WeightMultiplierAbAttr", this, null, false, weight);
|
applyAbAttrs("WeightMultiplierAbAttr", { pokemon: this, weight });
|
||||||
return Math.max(minWeight, weight.value);
|
return Math.max(minWeight, weight.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2256,7 +2247,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trappedByAbility = new BooleanHolder(false);
|
/** Holds whether the pokemon is trapped due to an ability */
|
||||||
|
const trapped = new BooleanHolder(false);
|
||||||
/**
|
/**
|
||||||
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
|
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
|
||||||
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
|
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
|
||||||
@ -2265,14 +2257,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
||||||
|
|
||||||
for (const opponent of opposingField) {
|
for (const opponent of opposingField) {
|
||||||
applyCheckTrappedAbAttrs("CheckTrappedAbAttr", opponent, trappedByAbility, this, trappedAbMessages, simulated);
|
applyAbAttrs("CheckTrappedAbAttr", { pokemon: opponent, trapped, opponent: this, simulated }, trappedAbMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
return (
|
return (
|
||||||
trappedByAbility.value ||
|
trapped.value || !!this.getTag(TrappedTag) || !!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
|
||||||
!!this.getTag(TrappedTag) ||
|
|
||||||
!!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2287,7 +2277,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const moveTypeHolder = new NumberHolder(move.type);
|
const moveTypeHolder = new NumberHolder(move.type);
|
||||||
|
|
||||||
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
|
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
|
||||||
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", this, null, move, simulated, moveTypeHolder);
|
|
||||||
|
const power = new NumberHolder(move.power);
|
||||||
|
applyAbAttrs("MoveTypeChangeAbAttr", {
|
||||||
|
pokemon: this,
|
||||||
|
move,
|
||||||
|
simulated,
|
||||||
|
moveType: moveTypeHolder,
|
||||||
|
power,
|
||||||
|
opponent: this,
|
||||||
|
});
|
||||||
|
|
||||||
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
||||||
// then bypass the check for ion deluge and electrify
|
// then bypass the check for ion deluge and electrify
|
||||||
@ -2351,17 +2350,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelledHolder = cancelled ?? new BooleanHolder(false);
|
const cancelledHolder = cancelled ?? new BooleanHolder(false);
|
||||||
|
// TypeMultiplierAbAttrParams is shared amongst the type of AbAttrs we will be invoking
|
||||||
|
const commonAbAttrParams: TypeMultiplierAbAttrParams = {
|
||||||
|
pokemon: this,
|
||||||
|
opponent: source,
|
||||||
|
move,
|
||||||
|
cancelled: cancelledHolder,
|
||||||
|
simulated,
|
||||||
|
typeMultiplier,
|
||||||
|
};
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyAbAttrs("TypeImmunityAbAttr", commonAbAttrParams);
|
||||||
|
|
||||||
if (!cancelledHolder.value) {
|
if (!cancelledHolder.value) {
|
||||||
applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyAbAttrs("MoveImmunityAbAttr", commonAbAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelledHolder.value) {
|
if (!cancelledHolder.value) {
|
||||||
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
defendingSidePlayField.forEach(p =>
|
defendingSidePlayField.forEach(p =>
|
||||||
applyPreDefendAbAttrs("FieldPriorityMoveImmunityAbAttr", p, source, move, cancelledHolder),
|
applyAbAttrs("FieldPriorityMoveImmunityAbAttr", {
|
||||||
|
pokemon: p,
|
||||||
|
opponent: source,
|
||||||
|
move,
|
||||||
|
cancelled: cancelledHolder,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2376,7 +2389,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
||||||
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
||||||
applyPreDefendAbAttrs("FullHpResistTypeAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyAbAttrs("FullHpResistTypeAbAttr", commonAbAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
|
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
|
||||||
@ -2420,16 +2433,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let multiplier = types
|
let multiplier = types
|
||||||
.map(defType => {
|
.map(defenderType => {
|
||||||
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType));
|
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defenderType));
|
||||||
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
||||||
if (move) {
|
if (move) {
|
||||||
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType);
|
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defenderType);
|
||||||
}
|
}
|
||||||
if (source) {
|
if (source) {
|
||||||
const ignoreImmunity = new BooleanHolder(false);
|
const ignoreImmunity = new BooleanHolder(false);
|
||||||
if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
|
if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
|
||||||
applyAbAttrs("IgnoreTypeImmunityAbAttr", source, ignoreImmunity, simulated, moveType, defType);
|
applyAbAttrs("IgnoreTypeImmunityAbAttr", {
|
||||||
|
pokemon: source,
|
||||||
|
cancelled: ignoreImmunity,
|
||||||
|
simulated,
|
||||||
|
moveType,
|
||||||
|
defenderType,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (ignoreImmunity.value) {
|
if (ignoreImmunity.value) {
|
||||||
if (multiplier.value === 0) {
|
if (multiplier.value === 0) {
|
||||||
@ -2438,7 +2457,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
|
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
|
||||||
if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) {
|
if (exposedTags.some(t => t.ignoreImmunity(defenderType, moveType))) {
|
||||||
if (multiplier.value === 0) {
|
if (multiplier.value === 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -2498,14 +2517,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
defScore *=
|
defScore *=
|
||||||
1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25);
|
1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25);
|
||||||
}
|
}
|
||||||
|
atkScore *= 1.25; //give more value for the pokemon's typing
|
||||||
|
const moveset = this.moveset;
|
||||||
|
let moveAtkScoreLength = 0;
|
||||||
|
for (const move of moveset) {
|
||||||
|
if (move.getMove().category === MoveCategory.SPECIAL || move.getMove().category === MoveCategory.PHYSICAL) {
|
||||||
|
atkScore += opponent.getAttackTypeEffectiveness(move.getMove().type, this, false, true, undefined, true);
|
||||||
|
moveAtkScoreLength++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atkScore = atkScore / (moveAtkScoreLength + 1); //calculate the median for the attack score
|
||||||
/**
|
/**
|
||||||
* Based on this Pokemon's HP ratio compared to that of the opponent.
|
* Based on this Pokemon's HP ratio compared to that of the opponent.
|
||||||
* This ratio is multiplied by 1.5 if this Pokemon outspeeds the opponent;
|
* This ratio is multiplied by 1.5 if this Pokemon outspeeds the opponent;
|
||||||
* however, the final ratio cannot be higher than 1.
|
* however, the final ratio cannot be higher than 1.
|
||||||
*/
|
*/
|
||||||
let hpDiffRatio = this.getHpRatio() + (1 - opponent.getHpRatio());
|
const hpRatio = this.getHpRatio();
|
||||||
if (outspeed) {
|
const oppHpRatio = opponent.getHpRatio();
|
||||||
hpDiffRatio = Math.min(hpDiffRatio * 1.5, 1);
|
const isDying = hpRatio <= 0.2;
|
||||||
|
let hpDiffRatio = hpRatio + (1 - oppHpRatio);
|
||||||
|
if (isDying && this.isActive(true)) {
|
||||||
|
//It might be a sacrifice candidate if hp under 20%
|
||||||
|
const badMatchup = atkScore < 1.5 && defScore < 1.5;
|
||||||
|
if (!outspeed && badMatchup) {
|
||||||
|
//It might not be a worthy sacrifice if it doesn't outspeed or doesn't do enough damage
|
||||||
|
hpDiffRatio *= 0.85;
|
||||||
|
} else {
|
||||||
|
hpDiffRatio = Math.min(1 - hpRatio + (outspeed ? 0.2 : 0.1), 1);
|
||||||
|
}
|
||||||
|
} else if (outspeed) {
|
||||||
|
hpDiffRatio = Math.min(hpDiffRatio * 1.25, 1);
|
||||||
|
} else if (hpRatio > 0.2 && hpRatio <= 0.4) {
|
||||||
|
//Might be considered to be switched because it's not in low enough health
|
||||||
|
hpDiffRatio = Math.min(hpDiffRatio * 0.5, 1);
|
||||||
}
|
}
|
||||||
return (atkScore + defScore) * hpDiffRatio;
|
return (atkScore + defScore) * hpDiffRatio;
|
||||||
}
|
}
|
||||||
@ -2880,7 +2924,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let fusionOverride: PokemonSpecies | undefined = undefined;
|
let fusionOverride: PokemonSpecies | undefined;
|
||||||
|
|
||||||
if (forStarter && this.isPlayer() && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) {
|
if (forStarter && this.isPlayer() && Overrides.STARTER_FUSION_SPECIES_OVERRIDE) {
|
||||||
fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE);
|
fusionOverride = getPokemonSpecies(Overrides.STARTER_FUSION_SPECIES_OVERRIDE);
|
||||||
@ -3358,7 +3402,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ignoreOppAbility) {
|
if (!ignoreOppAbility) {
|
||||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", {
|
||||||
|
pokemon: opponent,
|
||||||
|
ignored: ignoreStatStage,
|
||||||
|
stat,
|
||||||
|
simulated,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (move) {
|
if (move) {
|
||||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
|
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
|
||||||
@ -3397,8 +3446,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const ignoreAccStatStage = new BooleanHolder(false);
|
const ignoreAccStatStage = new BooleanHolder(false);
|
||||||
const ignoreEvaStatStage = new BooleanHolder(false);
|
const ignoreEvaStatStage = new BooleanHolder(false);
|
||||||
|
|
||||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage);
|
// TODO: consider refactoring this method to accept `simulated` and then pass simulated to these applyAbAttrs
|
||||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: target, stat: Stat.ACC, ignored: ignoreAccStatStage });
|
||||||
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage });
|
||||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
||||||
|
|
||||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
||||||
@ -3418,33 +3468,40 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
|
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove);
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
|
pokemon: this,
|
||||||
|
stat: Stat.ACC,
|
||||||
|
statVal: accuracyMultiplier,
|
||||||
|
move: sourceMove,
|
||||||
|
});
|
||||||
|
|
||||||
const evasionMultiplier = new NumberHolder(1);
|
const evasionMultiplier = new NumberHolder(1);
|
||||||
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", target, Stat.EVA, evasionMultiplier);
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
|
pokemon: target,
|
||||||
|
stat: Stat.EVA,
|
||||||
|
statVal: evasionMultiplier,
|
||||||
|
move: sourceMove,
|
||||||
|
});
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
const ignore =
|
const ignore =
|
||||||
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
|
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
|
||||||
applyAllyStatMultiplierAbAttrs(
|
applyAbAttrs("AllyStatMultiplierAbAttr", {
|
||||||
"AllyStatMultiplierAbAttr",
|
pokemon: ally,
|
||||||
ally,
|
stat: Stat.ACC,
|
||||||
Stat.ACC,
|
statVal: accuracyMultiplier,
|
||||||
accuracyMultiplier,
|
ignoreAbility: ignore,
|
||||||
false,
|
move: sourceMove,
|
||||||
this,
|
});
|
||||||
ignore,
|
|
||||||
);
|
applyAbAttrs("AllyStatMultiplierAbAttr", {
|
||||||
applyAllyStatMultiplierAbAttrs(
|
pokemon: ally,
|
||||||
"AllyStatMultiplierAbAttr",
|
stat: Stat.EVA,
|
||||||
ally,
|
statVal: evasionMultiplier,
|
||||||
Stat.EVA,
|
ignoreAbility: ignore,
|
||||||
evasionMultiplier,
|
move: sourceMove,
|
||||||
false,
|
});
|
||||||
this,
|
|
||||||
ignore,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accuracyMultiplier.value / evasionMultiplier.value;
|
return accuracyMultiplier.value / evasionMultiplier.value;
|
||||||
@ -3559,7 +3616,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
|
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
|
||||||
|
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier);
|
applyAbAttrs("StabBoostAbAttr", { pokemon: source, simulated, multiplier: stabMultiplier });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
|
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
|
||||||
@ -3706,16 +3763,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
null,
|
null,
|
||||||
multiStrikeEnhancementMultiplier,
|
multiStrikeEnhancementMultiplier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyPreAttackAbAttrs(
|
applyAbAttrs("AddSecondStrikeAbAttr", {
|
||||||
"AddSecondStrikeAbAttr",
|
pokemon: source,
|
||||||
source,
|
|
||||||
this,
|
|
||||||
move,
|
move,
|
||||||
simulated,
|
simulated,
|
||||||
null,
|
multiplier: multiStrikeEnhancementMultiplier,
|
||||||
multiStrikeEnhancementMultiplier,
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Doubles damage if this Pokemon's last move was Glaive Rush */
|
/** Doubles damage if this Pokemon's last move was Glaive Rush */
|
||||||
@ -3726,7 +3781,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** The damage multiplier when the given move critically hits */
|
/** The damage multiplier when the given move critically hits */
|
||||||
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
|
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
|
||||||
applyAbAttrs("MultCritAbAttr", source, null, simulated, criticalMultiplier);
|
applyAbAttrs("MultCritAbAttr", { pokemon: source, simulated, critMult: criticalMultiplier });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multiplier for random damage spread in the range [0.85, 1]
|
* A multiplier for random damage spread in the range [0.85, 1]
|
||||||
@ -3747,7 +3802,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
) {
|
) {
|
||||||
const burnDamageReductionCancelled = new BooleanHolder(false);
|
const burnDamageReductionCancelled = new BooleanHolder(false);
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated);
|
applyAbAttrs("BypassBurnDamageReductionAbAttr", {
|
||||||
|
pokemon: source,
|
||||||
|
cancelled: burnDamageReductionCancelled,
|
||||||
|
simulated,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!burnDamageReductionCancelled.value) {
|
if (!burnDamageReductionCancelled.value) {
|
||||||
burnMultiplier = 0.5;
|
burnMultiplier = 0.5;
|
||||||
@ -3811,7 +3870,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyPreAttackAbAttrs("DamageBoostAbAttr", source, this, move, simulated, damage);
|
applyAbAttrs("DamageBoostAbAttr", {
|
||||||
|
pokemon: source,
|
||||||
|
opponent: this,
|
||||||
|
move,
|
||||||
|
simulated,
|
||||||
|
damage,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply the enemy's Damage and Resistance tokens */
|
/** Apply the enemy's Damage and Resistance tokens */
|
||||||
@ -3822,14 +3887,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
|
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const abAttrParams: PreAttackModifyDamageAbAttrParams = {
|
||||||
|
pokemon: this,
|
||||||
|
opponent: source,
|
||||||
|
move,
|
||||||
|
simulated,
|
||||||
|
damage,
|
||||||
|
};
|
||||||
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage);
|
applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams);
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
/** Additionally apply friend guard damage reduction if ally has it. */
|
/** Additionally apply friend guard damage reduction if ally has it. */
|
||||||
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
|
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
|
||||||
applyPreDefendAbAttrs("AlliedFieldDamageReductionAbAttr", ally, source, move, cancelled, simulated, damage);
|
applyAbAttrs("AlliedFieldDamageReductionAbAttr", {
|
||||||
|
...abAttrParams,
|
||||||
|
// Same parameters as before, except we are applying the ally's ability
|
||||||
|
pokemon: ally,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3837,7 +3913,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
|
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
|
||||||
|
|
||||||
if (this.isFullHp() && !ignoreAbility) {
|
if (this.isFullHp() && !ignoreAbility) {
|
||||||
applyPreDefendAbAttrs("PreDefendFullHpEndureAbAttr", this, source, move, cancelled, false, damage);
|
applyAbAttrs("PreDefendFullHpEndureAbAttr", abAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug message for when damage is applied (i.e. not simulated)
|
// debug message for when damage is applied (i.e. not simulated)
|
||||||
@ -3875,7 +3951,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
const alwaysCrit = new BooleanHolder(false);
|
const alwaysCrit = new BooleanHolder(false);
|
||||||
applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
|
applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
|
||||||
applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move);
|
applyAbAttrs("ConditionalCritAbAttr", { pokemon: source, isCritical: alwaysCrit, target: this, move });
|
||||||
const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
|
const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
|
||||||
const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
|
const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
|
||||||
|
|
||||||
@ -3886,7 +3962,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// apply crit block effects from lucky chant & co., overriding previous effects
|
// apply crit block effects from lucky chant & co., overriding previous effects
|
||||||
const blockCrit = new BooleanHolder(false);
|
const blockCrit = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit);
|
applyAbAttrs("BlockCritAbAttr", { pokemon: this, blockCrit });
|
||||||
const blockCritTag = globalScene.arena.getTagOnSide(
|
const blockCritTag = globalScene.arena.getTagOnSide(
|
||||||
NoCritTag,
|
NoCritTag,
|
||||||
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
|
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
|
||||||
@ -3998,7 +4074,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
||||||
*/
|
*/
|
||||||
if (!source || source.turnData.hitCount <= 1) {
|
if (!source || source.turnData.hitCount <= 1) {
|
||||||
applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source);
|
applyAbAttrs("PostDamageAbAttr", { pokemon: this, damage, source });
|
||||||
}
|
}
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
@ -4046,11 +4122,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const stubTag = new BattlerTag(tagType, 0, 0);
|
const stubTag = new BattlerTag(tagType, 0, 0);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true);
|
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: stubTag, cancelled, simulated: true });
|
||||||
|
|
||||||
const userField = this.getAlliedField();
|
const userField = this.getAlliedField();
|
||||||
userField.forEach(pokemon =>
|
userField.forEach(pokemon =>
|
||||||
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this),
|
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
tag: stubTag,
|
||||||
|
cancelled,
|
||||||
|
simulated: true,
|
||||||
|
target: this,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
@ -4066,13 +4148,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
|
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled);
|
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: newTag, cancelled });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.getAlliedField()) {
|
for (const pokemon of this.getAlliedField()) {
|
||||||
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this);
|
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", { pokemon, tag: newTag, cancelled, target: this });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4100,7 +4182,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
getTag<T extends BattlerTag>(tagType: Constructor<T>): T | undefined;
|
getTag<T extends BattlerTag>(tagType: Constructor<T>): T | undefined;
|
||||||
|
|
||||||
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag | undefined {
|
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag | undefined {
|
||||||
return tagType instanceof Function
|
return typeof tagType === "function"
|
||||||
? this.summonData.tags.find(t => t instanceof tagType)
|
? this.summonData.tags.find(t => t instanceof tagType)
|
||||||
: this.summonData.tags.find(t => t.tagType === tagType);
|
: this.summonData.tags.find(t => t.tagType === tagType);
|
||||||
}
|
}
|
||||||
@ -4373,9 +4455,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => {
|
scene.time.delayedCall(fixedInt(Math.ceil(duration * 0.4)), () => {
|
||||||
try {
|
try {
|
||||||
SoundFade.fadeOut(scene, cry, fixedInt(Math.ceil(duration * 0.2)));
|
SoundFade.fadeOut(scene, cry, fixedInt(Math.ceil(duration * 0.2)));
|
||||||
fusionCry = this.getFusionSpeciesForm(undefined, true).cry(
|
fusionCry = this.getFusionSpeciesForm(undefined, true).cry({
|
||||||
Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig),
|
seek: Math.max(fusionCry.totalDuration * 0.4, 0),
|
||||||
);
|
...soundConfig,
|
||||||
|
});
|
||||||
SoundFade.fadeIn(
|
SoundFade.fadeIn(
|
||||||
scene,
|
scene,
|
||||||
fusionCry,
|
fusionCry,
|
||||||
@ -4517,13 +4600,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
if (i === transitionIndex && fusionCryKey) {
|
if (i === transitionIndex && fusionCryKey) {
|
||||||
SoundFade.fadeOut(globalScene, cry, fixedInt(Math.ceil((duration / rate) * 0.2)));
|
SoundFade.fadeOut(globalScene, cry, fixedInt(Math.ceil((duration / rate) * 0.2)));
|
||||||
fusionCry = globalScene.playSound(
|
fusionCry = globalScene.playSound(fusionCryKey, {
|
||||||
fusionCryKey,
|
seek: Math.max(fusionCry.totalDuration * 0.4, 0),
|
||||||
Object.assign({
|
rate: rate,
|
||||||
seek: Math.max(fusionCry.totalDuration * 0.4, 0),
|
});
|
||||||
rate: rate,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
SoundFade.fadeIn(
|
SoundFade.fadeIn(
|
||||||
globalScene,
|
globalScene,
|
||||||
fusionCry,
|
fusionCry,
|
||||||
@ -4597,7 +4677,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
||||||
*/
|
*/
|
||||||
canSetStatus(
|
canSetStatus(
|
||||||
effect: StatusEffect | undefined,
|
effect: StatusEffect,
|
||||||
quiet = false,
|
quiet = false,
|
||||||
overrideStatus = false,
|
overrideStatus = false,
|
||||||
sourcePokemon: Pokemon | null = null,
|
sourcePokemon: Pokemon | null = null,
|
||||||
@ -4628,8 +4708,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
||||||
const cancelImmunity = new BooleanHolder(false);
|
const cancelImmunity = new BooleanHolder(false);
|
||||||
|
// TODO: Determine if we need to pass `quiet` as the value for simulated in this call
|
||||||
if (sourcePokemon) {
|
if (sourcePokemon) {
|
||||||
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType);
|
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", {
|
||||||
|
pokemon: sourcePokemon,
|
||||||
|
cancelled: cancelImmunity,
|
||||||
|
statusEffect: effect,
|
||||||
|
defenderType: defType,
|
||||||
|
});
|
||||||
if (cancelImmunity.value) {
|
if (cancelImmunity.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4678,21 +4764,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet);
|
applyAbAttrs("StatusEffectImmunityAbAttr", { pokemon: this, effect, cancelled, simulated: quiet });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.getAlliedField()) {
|
for (const pokemon of this.getAlliedField()) {
|
||||||
applyPreSetStatusAbAttrs(
|
applyAbAttrs("UserFieldStatusEffectImmunityAbAttr", {
|
||||||
"UserFieldStatusEffectImmunityAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
effect,
|
effect,
|
||||||
cancelled,
|
cancelled,
|
||||||
quiet,
|
simulated: quiet,
|
||||||
this,
|
target: this,
|
||||||
sourcePokemon,
|
source: sourcePokemon,
|
||||||
);
|
});
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4723,6 +4808,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
overrideStatus?: boolean,
|
overrideStatus?: boolean,
|
||||||
quiet = true,
|
quiet = true,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
if (!effect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4781,7 +4869,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
||||||
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
|
||||||
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -4842,7 +4929,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
if (attacker) {
|
if (attacker) {
|
||||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
|
||||||
}
|
}
|
||||||
return !bypassed.value;
|
return !bypassed.value;
|
||||||
}
|
}
|
||||||
@ -5316,10 +5403,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
for (let sc = 0; sc < spriteColors.length; sc++) {
|
for (let sc = 0; sc < spriteColors.length; sc++) {
|
||||||
const delta = Math.min(...paletteDeltas[sc]);
|
const delta = Math.min(...paletteDeltas[sc]);
|
||||||
const paletteIndex = Math.min(
|
const paletteIndex = Math.min(paletteDeltas[sc].indexOf(delta), fusionPalette.length - 1);
|
||||||
paletteDeltas[sc].findIndex(pd => pd === delta),
|
|
||||||
fusionPalette.length - 1,
|
|
||||||
);
|
|
||||||
if (delta < 255) {
|
if (delta < 255) {
|
||||||
const ratio = easeFunc(delta / 255);
|
const ratio = easeFunc(delta / 255);
|
||||||
const color = [0, 0, 0, fusionSpriteColors[sc][3]];
|
const color = [0, 0, 0, fusionSpriteColors[sc][3]];
|
||||||
@ -5391,7 +5475,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.hideInfo();
|
this.hideInfo();
|
||||||
}
|
}
|
||||||
// Trigger abilities that activate upon leaving the field
|
// Trigger abilities that activate upon leaving the field
|
||||||
applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this);
|
applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: this });
|
||||||
this.setSwitchOutStatus(true);
|
this.setSwitchOutStatus(true);
|
||||||
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
||||||
globalScene.field.remove(this, destroy);
|
globalScene.field.remove(this, destroy);
|
||||||
@ -5451,7 +5535,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
globalScene.removeModifier(heldItem, this.isEnemy());
|
globalScene.removeModifier(heldItem, this.isEnemy());
|
||||||
}
|
}
|
||||||
if (forBattle) {
|
if (forBattle) {
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon: this });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -158,7 +158,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
|
|||||||
* @param {TrainerSlot} trainerSlot - The slot to determine which name to use. Defaults to TrainerSlot.NONE.
|
* @param {TrainerSlot} trainerSlot - The slot to determine which name to use. Defaults to TrainerSlot.NONE.
|
||||||
* @param {boolean} includeTitle - Whether to include the title in the returned name. Defaults to false.
|
* @param {boolean} includeTitle - Whether to include the title in the returned name. Defaults to false.
|
||||||
* @returns {string} - The formatted name of the trainer.
|
* @returns {string} - The formatted name of the trainer.
|
||||||
**/
|
*/
|
||||||
getName(trainerSlot: TrainerSlot = TrainerSlot.NONE, includeTitle = false): string {
|
getName(trainerSlot: TrainerSlot = TrainerSlot.NONE, includeTitle = false): string {
|
||||||
// Get the base title based on the trainer slot and variant.
|
// Get the base title based on the trainer slot and variant.
|
||||||
let name = this.config.getTitle(trainerSlot, this.variant);
|
let name = this.config.getTitle(trainerSlot, this.variant);
|
||||||
|
@ -70,20 +70,20 @@ const repeatInputDelayMillis = 250;
|
|||||||
* providing a unified interface for all input-related interactions.
|
* providing a unified interface for all input-related interactions.
|
||||||
*/
|
*/
|
||||||
export class InputsController {
|
export class InputsController {
|
||||||
private gamepads: Array<Phaser.Input.Gamepad.Gamepad> = new Array();
|
private gamepads: Array<Phaser.Input.Gamepad.Gamepad> = [];
|
||||||
public events: Phaser.Events.EventEmitter;
|
public events: Phaser.Events.EventEmitter;
|
||||||
|
|
||||||
private buttonLock: Button[] = new Array();
|
private buttonLock: Button[] = [];
|
||||||
private interactions: Map<Button, Map<string, boolean>> = new Map();
|
private interactions: Map<Button, Map<string, boolean>> = new Map();
|
||||||
private configs: Map<string, InterfaceConfig> = new Map();
|
private configs: Map<string, InterfaceConfig> = new Map();
|
||||||
|
|
||||||
public gamepadSupport = true;
|
public gamepadSupport = true;
|
||||||
public selectedDevice;
|
public selectedDevice;
|
||||||
|
|
||||||
private disconnectedGamepads: Array<string> = new Array();
|
private disconnectedGamepads: Array<string> = [];
|
||||||
|
|
||||||
public lastSource = "keyboard";
|
public lastSource = "keyboard";
|
||||||
private inputInterval: NodeJS.Timeout[] = new Array();
|
private inputInterval: NodeJS.Timeout[] = [];
|
||||||
private touchControls: TouchControl;
|
private touchControls: TouchControl;
|
||||||
public moveTouchControlsHandler: MoveTouchControlsHandler;
|
public moveTouchControlsHandler: MoveTouchControlsHandler;
|
||||||
|
|
||||||
|
@ -1585,7 +1585,9 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
|
|||||||
pokemonEvolutions.hasOwnProperty(p.species.speciesId) &&
|
pokemonEvolutions.hasOwnProperty(p.species.speciesId) &&
|
||||||
(!p.pauseEvolutions ||
|
(!p.pauseEvolutions ||
|
||||||
p.species.speciesId === SpeciesId.SLOWPOKE ||
|
p.species.speciesId === SpeciesId.SLOWPOKE ||
|
||||||
p.species.speciesId === SpeciesId.EEVEE),
|
p.species.speciesId === SpeciesId.EEVEE ||
|
||||||
|
p.species.speciesId === SpeciesId.KIRLIA ||
|
||||||
|
p.species.speciesId === SpeciesId.SNORUNT),
|
||||||
)
|
)
|
||||||
.flatMap(p => {
|
.flatMap(p => {
|
||||||
const evolutions = pokemonEvolutions[p.species.speciesId];
|
const evolutions = pokemonEvolutions[p.species.speciesId];
|
||||||
@ -1599,7 +1601,9 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator {
|
|||||||
pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) &&
|
pokemonEvolutions.hasOwnProperty(p.fusionSpecies.speciesId) &&
|
||||||
(!p.pauseEvolutions ||
|
(!p.pauseEvolutions ||
|
||||||
p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE ||
|
p.fusionSpecies.speciesId === SpeciesId.SLOWPOKE ||
|
||||||
p.fusionSpecies.speciesId === SpeciesId.EEVEE),
|
p.fusionSpecies.speciesId === SpeciesId.EEVEE ||
|
||||||
|
p.fusionSpecies.speciesId === SpeciesId.KIRLIA ||
|
||||||
|
p.fusionSpecies.speciesId === SpeciesId.SNORUNT),
|
||||||
)
|
)
|
||||||
.flatMap(p => {
|
.flatMap(p => {
|
||||||
const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
|
const evolutions = pokemonEvolutions[p.fusionSpecies!.speciesId];
|
||||||
|
@ -42,7 +42,7 @@ import type {
|
|||||||
import { getModifierType } from "#app/utils/modifier-utils";
|
import { getModifierType } from "#app/utils/modifier-utils";
|
||||||
import { Color, ShadowColor } from "#enums/color";
|
import { Color, ShadowColor } from "#enums/color";
|
||||||
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
||||||
import { applyAbAttrs, applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types";
|
import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types";
|
||||||
|
|
||||||
@ -751,7 +751,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPokemon(): Pokemon | undefined {
|
getPokemon(): Pokemon | undefined {
|
||||||
return this.pokemonId ? (globalScene.getPokemonById(this.pokemonId) ?? undefined) : undefined;
|
return globalScene.getPokemonById(this.pokemonId) ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScoreMultiplier(): number {
|
getScoreMultiplier(): number {
|
||||||
@ -1879,7 +1879,7 @@ export class BerryModifier extends PokemonHeldItemModifier {
|
|||||||
|
|
||||||
// munch the berry and trigger unburden-like effects
|
// munch the berry and trigger unburden-like effects
|
||||||
getBerryEffectFunc(this.berryType)(pokemon);
|
getBerryEffectFunc(this.berryType)(pokemon);
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon });
|
||||||
|
|
||||||
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
||||||
// Don't recover it if we proc berry pouch (no item duplication)
|
// Don't recover it if we proc berry pouch (no item duplication)
|
||||||
@ -1967,7 +1967,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
|
|||||||
// Reapply Commander on the Pokemon's side of the field, if applicable
|
// Reapply Commander on the Pokemon's side of the field, if applicable
|
||||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
for (const p of field) {
|
for (const p of field) {
|
||||||
applyAbAttrs("CommanderAbAttr", p, null, false);
|
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ import { UnlockPhase } from "#app/phases/unlock-phase";
|
|||||||
import { VictoryPhase } from "#app/phases/victory-phase";
|
import { VictoryPhase } from "#app/phases/victory-phase";
|
||||||
import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
|
import { WeatherEffectPhase } from "#app/phases/weather-effect-phase";
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Manager for phases used by battle scene.
|
* Manager for phases used by battle scene.
|
||||||
*
|
*
|
||||||
* *This file must not be imported or used directly. The manager is exclusively used by the battle scene and is not intended for external use.*
|
* *This file must not be imported or used directly. The manager is exclusively used by the battle scene and is not intended for external use.*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||||
@ -25,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.attemptRunAway(playerField, enemyField, escapeChance);
|
this.attemptRunAway(playerField, enemyField, escapeChance);
|
||||||
|
|
||||||
applyAbAttrs("RunSuccessAbAttr", playerPokemon, null, false, escapeChance);
|
applyAbAttrs("RunSuccessAbAttr", { pokemon: playerPokemon, chance: escapeChance });
|
||||||
|
|
||||||
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
||||||
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
|
enemyField.forEach(enemyPokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: enemyPokemon }));
|
||||||
|
|
||||||
globalScene.playSound("se/flee");
|
globalScene.playSound("se/flee");
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||||
@ -38,14 +38,11 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
alpha: 0,
|
alpha: 0,
|
||||||
duration: 250,
|
duration: 250,
|
||||||
ease: "Sine.easeIn",
|
ease: "Sine.easeIn",
|
||||||
onComplete: () =>
|
onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()),
|
||||||
// biome-ignore lint/complexity/noForEach: TODO
|
|
||||||
enemyField.forEach(enemyPokemon => enemyPokemon.destroy()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
globalScene.clearEnemyHeldItemModifiers();
|
globalScene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noForEach: TODO
|
|
||||||
enemyField.forEach(enemyPokemon => {
|
enemyField.forEach(enemyPokemon => {
|
||||||
enemyPokemon.hideInfo().then(() => enemyPokemon.destroy());
|
enemyPokemon.hideInfo().then(() => enemyPokemon.destroy());
|
||||||
enemyPokemon.hp = 0;
|
enemyPokemon.hp = 0;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
|
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
|
||||||
applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory);
|
applyAbAttrs("PostBattleAbAttr", { pokemon, victory: this.isVictory });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalScene.currentBattle.moneyScattered) {
|
if (globalScene.currentBattle.moneyScattered) {
|
||||||
|
@ -20,7 +20,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
this.executeForAll(pokemon => {
|
this.executeForAll(pokemon => {
|
||||||
this.eatBerries(pokemon);
|
this.eatBerries(pokemon);
|
||||||
applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null);
|
applyAbAttrs("CudChewConsumeBerryAbAttr", { pokemon });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
@ -42,7 +42,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
// TODO: If both opponents on field have unnerve, which one displays its message?
|
// TODO: If both opponents on field have unnerve, which one displays its message?
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", opp, cancelled));
|
pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", { pokemon: opp, cancelled }));
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("abilityTriggers:preventBerryUse", {
|
i18next.t("abilityTriggers:preventBerryUse", {
|
||||||
@ -70,6 +70,6 @@ export class BerryPhase extends FieldPhase {
|
|||||||
globalScene.updateModifiers(pokemon.isPlayer());
|
globalScene.updateModifiers(pokemon.isPlayer());
|
||||||
|
|
||||||
// AbilityId.CHEEK_POUCH only works once per round of nom noms
|
// AbilityId.CHEEK_POUCH only works once per round of nom noms
|
||||||
applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false));
|
applyAbAttrs("HealFromBerryUseAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { BattlerIndex } from "#enums/battler-index";
|
|||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||||
import { applyAbAttrs, applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
||||||
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
@ -128,7 +128,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
.slice(0, !battle.double ? 1 : 2)
|
.slice(0, !battle.double ? 1 : 2)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach(playerPokemon => {
|
.forEach(playerPokemon => {
|
||||||
applyAbAttrs("SyncEncounterNatureAbAttr", playerPokemon, null, false, battle.enemyParty[e]);
|
applyAbAttrs("SyncEncounterNatureAbAttr", { pokemon: playerPokemon, target: battle.enemyParty[e] });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
if (e < (battle.double ? 2 : 1)) {
|
if (e < (battle.double ? 2 : 1)) {
|
||||||
if (battle.battleType === BattleType.WILD) {
|
if (battle.battleType === BattleType.WILD) {
|
||||||
for (const pokemon of globalScene.getField()) {
|
for (const pokemon of globalScene.getField()) {
|
||||||
applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []);
|
applyAbAttrs("PreSummonAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
globalScene.field.add(enemyPokemon);
|
globalScene.field.add(enemyPokemon);
|
||||||
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
||||||
|
@ -23,6 +23,8 @@ export class EvolutionPhase extends Phase {
|
|||||||
protected pokemon: PlayerPokemon;
|
protected pokemon: PlayerPokemon;
|
||||||
protected lastLevel: number;
|
protected lastLevel: number;
|
||||||
|
|
||||||
|
protected evoChain: Phaser.Tweens.TweenChain | null = null;
|
||||||
|
|
||||||
private preEvolvedPokemonName: string;
|
private preEvolvedPokemonName: string;
|
||||||
|
|
||||||
private evolution: SpeciesFormEvolution | null;
|
private evolution: SpeciesFormEvolution | null;
|
||||||
@ -40,13 +42,23 @@ export class EvolutionPhase extends Phase {
|
|||||||
protected pokemonEvoSprite: Phaser.GameObjects.Sprite;
|
protected pokemonEvoSprite: Phaser.GameObjects.Sprite;
|
||||||
protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
|
protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
|
||||||
|
|
||||||
constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number) {
|
/** Whether the evolution can be cancelled by the player */
|
||||||
|
protected canCancel: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pokemon - The Pokemon that is evolving
|
||||||
|
* @param evolution - The form being evolved into
|
||||||
|
* @param lastLevel - The level at which the Pokemon is evolving
|
||||||
|
* @param canCancel - Whether the evolution can be cancelled by the player
|
||||||
|
*/
|
||||||
|
constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number, canCancel = true) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.pokemon = pokemon;
|
this.pokemon = pokemon;
|
||||||
this.evolution = evolution;
|
this.evolution = evolution;
|
||||||
this.lastLevel = lastLevel;
|
this.lastLevel = lastLevel;
|
||||||
this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution;
|
this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution;
|
||||||
|
this.canCancel = canCancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(): boolean {
|
validate(): boolean {
|
||||||
@ -57,198 +69,227 @@ export class EvolutionPhase extends Phase {
|
|||||||
return globalScene.ui.setModeForceTransition(UiMode.EVOLUTION_SCENE);
|
return globalScene.ui.setModeForceTransition(UiMode.EVOLUTION_SCENE);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
/**
|
||||||
super.start();
|
* Set up the following evolution assets
|
||||||
|
* - {@linkcode evolutionContainer}
|
||||||
|
* - {@linkcode evolutionBaseBg}
|
||||||
|
* - {@linkcode evolutionBg}
|
||||||
|
* - {@linkcode evolutionBgOverlay}
|
||||||
|
* - {@linkcode evolutionOverlay}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private setupEvolutionAssets(): void {
|
||||||
|
this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler;
|
||||||
|
this.evolutionContainer = this.evolutionHandler.evolutionContainer;
|
||||||
|
this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg").setOrigin(0);
|
||||||
|
|
||||||
this.setMode().then(() => {
|
this.evolutionBg = globalScene.add
|
||||||
if (!this.validate()) {
|
.video(0, 0, "evo_bg")
|
||||||
return this.end();
|
.stop()
|
||||||
}
|
.setOrigin(0)
|
||||||
|
.setScale(0.4359673025)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
globalScene.fadeOutBgm(undefined, false);
|
this.evolutionBgOverlay = globalScene.add
|
||||||
|
.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x262626)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setAlpha(0);
|
||||||
|
this.evolutionContainer.add([this.evolutionBaseBg, this.evolutionBgOverlay, this.evolutionBg]);
|
||||||
|
|
||||||
this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler;
|
this.evolutionOverlay = globalScene.add.rectangle(
|
||||||
|
0,
|
||||||
|
-globalScene.game.canvas.height / 6,
|
||||||
|
globalScene.game.canvas.width / 6,
|
||||||
|
globalScene.game.canvas.height / 6 - 48,
|
||||||
|
0xffffff,
|
||||||
|
);
|
||||||
|
this.evolutionOverlay.setOrigin(0).setAlpha(0);
|
||||||
|
globalScene.ui.add(this.evolutionOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
this.evolutionContainer = this.evolutionHandler.evolutionContainer;
|
/**
|
||||||
|
* Configure the sprite, setting its pipeline data
|
||||||
|
* @param pokemon - The pokemon object that the sprite information is configured from
|
||||||
|
* @param sprite - The sprite object to configure
|
||||||
|
* @param setPipeline - Whether to also set the pipeline; should be false
|
||||||
|
* if the sprite is only being updated with new sprite assets
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns The sprite object that was passed in
|
||||||
|
*/
|
||||||
|
protected configureSprite(pokemon: Pokemon, sprite: Phaser.GameObjects.Sprite, setPipeline = true): typeof sprite {
|
||||||
|
const spriteKey = pokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg");
|
if (setPipeline) {
|
||||||
this.evolutionBaseBg.setOrigin(0, 0);
|
sprite.setPipeline(globalScene.spritePipeline, {
|
||||||
this.evolutionContainer.add(this.evolutionBaseBg);
|
tone: [0.0, 0.0, 0.0, 0.0],
|
||||||
|
hasShadow: false,
|
||||||
this.evolutionBg = globalScene.add.video(0, 0, "evo_bg").stop();
|
teraColor: getTypeRgb(pokemon.getTeraType()),
|
||||||
this.evolutionBg.setOrigin(0, 0);
|
isTerastallized: pokemon.isTerastallized,
|
||||||
this.evolutionBg.setScale(0.4359673025);
|
});
|
||||||
this.evolutionBg.setVisible(false);
|
}
|
||||||
this.evolutionContainer.add(this.evolutionBg);
|
|
||||||
|
sprite
|
||||||
this.evolutionBgOverlay = globalScene.add.rectangle(
|
.setPipelineData("ignoreTimeTint", true)
|
||||||
0,
|
.setPipelineData("spriteKey", pokemon.getSpriteKey())
|
||||||
0,
|
.setPipelineData("shiny", pokemon.shiny)
|
||||||
globalScene.game.canvas.width / 6,
|
.setPipelineData("variant", pokemon.variant);
|
||||||
globalScene.game.canvas.height / 6,
|
|
||||||
0x262626,
|
for (let k of ["spriteColors", "fusionSpriteColors"]) {
|
||||||
);
|
if (pokemon.summonData.speciesForm) {
|
||||||
this.evolutionBgOverlay.setOrigin(0, 0);
|
k += "Base";
|
||||||
this.evolutionBgOverlay.setAlpha(0);
|
}
|
||||||
this.evolutionContainer.add(this.evolutionBgOverlay);
|
sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k];
|
||||||
|
}
|
||||||
const getPokemonSprite = () => {
|
|
||||||
const ret = globalScene.addPokemonSprite(
|
return sprite;
|
||||||
this.pokemon,
|
}
|
||||||
this.evolutionBaseBg.displayWidth / 2,
|
|
||||||
this.evolutionBaseBg.displayHeight / 2,
|
private getPokemonSprite(): Phaser.GameObjects.Sprite {
|
||||||
"pkmn__sub",
|
const sprite = globalScene.addPokemonSprite(
|
||||||
);
|
this.pokemon,
|
||||||
ret.setPipeline(globalScene.spritePipeline, {
|
this.evolutionBaseBg.displayWidth / 2,
|
||||||
tone: [0.0, 0.0, 0.0, 0.0],
|
this.evolutionBaseBg.displayHeight / 2,
|
||||||
ignoreTimeTint: true,
|
"pkmn__sub",
|
||||||
});
|
);
|
||||||
return ret;
|
sprite.setPipeline(globalScene.spritePipeline, {
|
||||||
};
|
tone: [0.0, 0.0, 0.0, 0.0],
|
||||||
|
ignoreTimeTint: true,
|
||||||
this.evolutionContainer.add((this.pokemonSprite = getPokemonSprite()));
|
});
|
||||||
this.evolutionContainer.add((this.pokemonTintSprite = getPokemonSprite()));
|
return sprite;
|
||||||
this.evolutionContainer.add((this.pokemonEvoSprite = getPokemonSprite()));
|
}
|
||||||
this.evolutionContainer.add((this.pokemonEvoTintSprite = getPokemonSprite()));
|
|
||||||
|
/**
|
||||||
this.pokemonTintSprite.setAlpha(0);
|
* Initialize {@linkcode pokemonSprite}, {@linkcode pokemonTintSprite}, {@linkcode pokemonEvoSprite}, and {@linkcode pokemonEvoTintSprite}
|
||||||
this.pokemonTintSprite.setTintFill(0xffffff);
|
* and add them to the {@linkcode evolutionContainer}
|
||||||
this.pokemonEvoSprite.setVisible(false);
|
*/
|
||||||
this.pokemonEvoTintSprite.setVisible(false);
|
private setupPokemonSprites(): void {
|
||||||
this.pokemonEvoTintSprite.setTintFill(0xffffff);
|
this.pokemonSprite = this.configureSprite(this.pokemon, this.getPokemonSprite());
|
||||||
|
this.pokemonTintSprite = this.configureSprite(
|
||||||
this.evolutionOverlay = globalScene.add.rectangle(
|
this.pokemon,
|
||||||
0,
|
this.getPokemonSprite().setAlpha(0).setTintFill(0xffffff),
|
||||||
-globalScene.game.canvas.height / 6,
|
);
|
||||||
globalScene.game.canvas.width / 6,
|
this.pokemonEvoSprite = this.configureSprite(this.pokemon, this.getPokemonSprite().setVisible(false));
|
||||||
globalScene.game.canvas.height / 6 - 48,
|
this.pokemonEvoTintSprite = this.configureSprite(
|
||||||
0xffffff,
|
this.pokemon,
|
||||||
);
|
this.getPokemonSprite().setVisible(false).setTintFill(0xffffff),
|
||||||
this.evolutionOverlay.setOrigin(0, 0);
|
);
|
||||||
this.evolutionOverlay.setAlpha(0);
|
|
||||||
globalScene.ui.add(this.evolutionOverlay);
|
this.evolutionContainer.add([
|
||||||
|
this.pokemonSprite,
|
||||||
[this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => {
|
this.pokemonTintSprite,
|
||||||
const spriteKey = this.pokemon.getSpriteKey(true);
|
this.pokemonEvoSprite,
|
||||||
try {
|
this.pokemonEvoTintSprite,
|
||||||
sprite.play(spriteKey);
|
]);
|
||||||
} catch (err: unknown) {
|
}
|
||||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
|
||||||
}
|
async start() {
|
||||||
|
super.start();
|
||||||
sprite.setPipeline(globalScene.spritePipeline, {
|
await this.setMode();
|
||||||
tone: [0.0, 0.0, 0.0, 0.0],
|
|
||||||
hasShadow: false,
|
if (!this.validate()) {
|
||||||
teraColor: getTypeRgb(this.pokemon.getTeraType()),
|
return this.end();
|
||||||
isTerastallized: this.pokemon.isTerastallized,
|
}
|
||||||
});
|
this.setupEvolutionAssets();
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
this.setupPokemonSprites();
|
||||||
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon);
|
||||||
sprite.setPipelineData("shiny", this.pokemon.shiny);
|
this.doEvolution();
|
||||||
sprite.setPipelineData("variant", this.pokemon.variant);
|
}
|
||||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
|
||||||
if (this.pokemon.summonData.speciesForm) {
|
/**
|
||||||
k += "Base";
|
* Update the sprites depicting the evolved Pokemon
|
||||||
}
|
* @param evolvedPokemon - The evolved Pokemon
|
||||||
sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k];
|
*/
|
||||||
});
|
private updateEvolvedPokemonSprites(evolvedPokemon: Pokemon): void {
|
||||||
|
this.configureSprite(evolvedPokemon, this.pokemonEvoSprite, false);
|
||||||
|
this.configureSprite(evolvedPokemon, this.pokemonEvoTintSprite, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the evolution tween and begins playing it
|
||||||
|
*/
|
||||||
|
private playEvolutionAnimation(evolvedPokemon: Pokemon): void {
|
||||||
|
globalScene.time.delayedCall(1000, () => {
|
||||||
|
this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution");
|
||||||
|
globalScene.tweens.add({
|
||||||
|
targets: this.evolutionBgOverlay,
|
||||||
|
alpha: 1,
|
||||||
|
delay: 500,
|
||||||
|
duration: 1500,
|
||||||
|
ease: "Sine.easeOut",
|
||||||
|
onComplete: () => {
|
||||||
|
globalScene.time.delayedCall(1000, () => {
|
||||||
|
this.evolutionBg.setVisible(true).play();
|
||||||
|
});
|
||||||
|
globalScene.playSound("se/charge");
|
||||||
|
this.doSpiralUpward();
|
||||||
|
this.fadeOutPokemonSprite(evolvedPokemon);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon);
|
|
||||||
this.doEvolution();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fadeOutPokemonSprite(evolvedPokemon: Pokemon): void {
|
||||||
|
globalScene.tweens.addCounter({
|
||||||
|
from: 0,
|
||||||
|
to: 1,
|
||||||
|
duration: 2000,
|
||||||
|
onUpdate: t => {
|
||||||
|
this.pokemonTintSprite.setAlpha(t.getValue());
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.pokemonSprite.setVisible(false);
|
||||||
|
globalScene.time.delayedCall(1100, () => {
|
||||||
|
globalScene.playSound("se/beam");
|
||||||
|
this.doArcDownward();
|
||||||
|
this.prepareForCycle(evolvedPokemon);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the evolution cycle by setting up the tint sprites and starting the cycle
|
||||||
|
*/
|
||||||
|
private prepareForCycle(evolvedPokemon: Pokemon): void {
|
||||||
|
globalScene.time.delayedCall(1500, () => {
|
||||||
|
this.pokemonEvoTintSprite.setScale(0.25).setVisible(true);
|
||||||
|
this.evolutionHandler.canCancel = this.canCancel;
|
||||||
|
this.doCycle(1, undefined, () => {
|
||||||
|
if (this.evolutionHandler.cancelled) {
|
||||||
|
this.handleFailedEvolution(evolvedPokemon);
|
||||||
|
} else {
|
||||||
|
this.handleSuccessEvolution(evolvedPokemon);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the evolution text and then commence the evolution animation
|
||||||
|
*/
|
||||||
doEvolution(): void {
|
doEvolution(): void {
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }),
|
i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }),
|
||||||
null,
|
null,
|
||||||
() => {
|
() => {
|
||||||
this.pokemon.cry();
|
this.pokemon.cry();
|
||||||
|
|
||||||
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
||||||
[this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => {
|
this.updateEvolvedPokemonSprites(evolvedPokemon);
|
||||||
const spriteKey = evolvedPokemon.getSpriteKey(true);
|
this.playEvolutionAnimation(evolvedPokemon);
|
||||||
try {
|
|
||||||
sprite.play(spriteKey);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
|
||||||
sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey());
|
|
||||||
sprite.setPipelineData("shiny", evolvedPokemon.shiny);
|
|
||||||
sprite.setPipelineData("variant", evolvedPokemon.variant);
|
|
||||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
|
||||||
if (evolvedPokemon.summonData.speciesForm) {
|
|
||||||
k += "Base";
|
|
||||||
}
|
|
||||||
sprite.pipelineData[k] = evolvedPokemon.getSprite().pipelineData[k];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
globalScene.time.delayedCall(1000, () => {
|
|
||||||
this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution");
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 1,
|
|
||||||
delay: 500,
|
|
||||||
duration: 1500,
|
|
||||||
ease: "Sine.easeOut",
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.time.delayedCall(1000, () => {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 0,
|
|
||||||
duration: 250,
|
|
||||||
});
|
|
||||||
this.evolutionBg.setVisible(true);
|
|
||||||
this.evolutionBg.play();
|
|
||||||
});
|
|
||||||
globalScene.playSound("se/charge");
|
|
||||||
this.doSpiralUpward();
|
|
||||||
globalScene.tweens.addCounter({
|
|
||||||
from: 0,
|
|
||||||
to: 1,
|
|
||||||
duration: 2000,
|
|
||||||
onUpdate: t => {
|
|
||||||
this.pokemonTintSprite.setAlpha(t.getValue());
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
this.pokemonSprite.setVisible(false);
|
|
||||||
globalScene.time.delayedCall(1100, () => {
|
|
||||||
globalScene.playSound("se/beam");
|
|
||||||
this.doArcDownward();
|
|
||||||
globalScene.time.delayedCall(1500, () => {
|
|
||||||
this.pokemonEvoTintSprite.setScale(0.25);
|
|
||||||
this.pokemonEvoTintSprite.setVisible(true);
|
|
||||||
this.evolutionHandler.canCancel = true;
|
|
||||||
this.doCycle(1).then(success => {
|
|
||||||
if (success) {
|
|
||||||
this.handleSuccessEvolution(evolvedPokemon);
|
|
||||||
} else {
|
|
||||||
this.handleFailedEvolution(evolvedPokemon);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Used exclusively by {@linkcode handleFailedEvolution} to fade out the evolution sprites and music */
|
||||||
* Handles a failed/stopped evolution
|
private fadeOutEvolutionAssets(): void {
|
||||||
* @param evolvedPokemon - The evolved Pokemon
|
|
||||||
*/
|
|
||||||
private handleFailedEvolution(evolvedPokemon: Pokemon): void {
|
|
||||||
this.pokemonSprite.setVisible(true);
|
|
||||||
this.pokemonTintSprite.setScale(1);
|
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
targets: [this.evolutionBg, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite],
|
targets: [this.evolutionBg, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite],
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
@ -257,9 +298,40 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.evolutionBg.setVisible(false);
|
this.evolutionBg.setVisible(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the confirmation prompt for pausing evolutions
|
||||||
|
* @param endCallback - The callback to call after either option is selected.
|
||||||
|
* This should end the evolution phase
|
||||||
|
*/
|
||||||
|
private showPauseEvolutionConfirmation(endCallback: () => void): void {
|
||||||
|
globalScene.ui.setOverlayMode(
|
||||||
|
UiMode.CONFIRM,
|
||||||
|
() => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
this.pokemon.pauseEvolutions = true;
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t("menu:evolutionsPaused", {
|
||||||
|
pokemonName: this.preEvolvedPokemonName,
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
endCallback,
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
globalScene.time.delayedCall(3000, endCallback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used exclusively by {@linkcode handleFailedEvolution} to show the failed evolution UI messages
|
||||||
|
*/
|
||||||
|
private showFailedEvolutionUI(evolvedPokemon: Pokemon): void {
|
||||||
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
@ -280,25 +352,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
evolvedPokemon.destroy();
|
evolvedPokemon.destroy();
|
||||||
this.end();
|
this.end();
|
||||||
};
|
};
|
||||||
globalScene.ui.setOverlayMode(
|
this.showPauseEvolutionConfirmation(end);
|
||||||
UiMode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.revertMode();
|
|
||||||
this.pokemon.pauseEvolutions = true;
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("menu:evolutionsPaused", {
|
|
||||||
pokemonName: this.preEvolvedPokemonName,
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
end,
|
|
||||||
3000,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
globalScene.ui.revertMode();
|
|
||||||
globalScene.time.delayedCall(3000, end);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -307,6 +361,93 @@ export class EvolutionPhase extends Phase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fade out the evolution assets, show the failed evolution UI messages, and enqueue the EndEvolutionPhase
|
||||||
|
* @param evolvedPokemon - The evolved Pokemon
|
||||||
|
*/
|
||||||
|
private handleFailedEvolution(evolvedPokemon: Pokemon): void {
|
||||||
|
this.pokemonSprite.setVisible(true);
|
||||||
|
this.pokemonTintSprite.setScale(1);
|
||||||
|
this.fadeOutEvolutionAssets();
|
||||||
|
|
||||||
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
this.showFailedEvolutionUI(evolvedPokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fadeout evolution music, play the cry, show the evolution completed text, and end the phase
|
||||||
|
*/
|
||||||
|
private onEvolutionComplete(evolvedPokemon: Pokemon) {
|
||||||
|
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
||||||
|
globalScene.time.delayedCall(250, () => {
|
||||||
|
this.pokemon.cry();
|
||||||
|
globalScene.time.delayedCall(1250, () => {
|
||||||
|
globalScene.playSoundWithoutBgm("evolution_fanfare");
|
||||||
|
|
||||||
|
evolvedPokemon.destroy();
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t("menu:evolutionDone", {
|
||||||
|
pokemonName: this.preEvolvedPokemonName,
|
||||||
|
evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(),
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
() => this.end(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
fixedInt(4000),
|
||||||
|
);
|
||||||
|
globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private postEvolve(evolvedPokemon: Pokemon): void {
|
||||||
|
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved
|
||||||
|
? LearnMoveSituation.EVOLUTION_FUSED
|
||||||
|
: this.pokemon.fusionSpecies
|
||||||
|
? LearnMoveSituation.EVOLUTION_FUSED_BASE
|
||||||
|
: LearnMoveSituation.EVOLUTION;
|
||||||
|
const levelMoves = this.pokemon
|
||||||
|
.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation)
|
||||||
|
.filter(lm => lm[0] === EVOLVE_MOVE);
|
||||||
|
for (const lm of levelMoves) {
|
||||||
|
globalScene.phaseManager.unshiftNew("LearnMovePhase", globalScene.getPlayerParty().indexOf(this.pokemon), lm[1]);
|
||||||
|
}
|
||||||
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
|
||||||
|
globalScene.playSound("se/shine");
|
||||||
|
this.doSpray();
|
||||||
|
|
||||||
|
globalScene.tweens.chain({
|
||||||
|
targets: null,
|
||||||
|
tweens: [
|
||||||
|
{
|
||||||
|
targets: this.evolutionOverlay,
|
||||||
|
alpha: 1,
|
||||||
|
duration: 250,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
onComplete: () => {
|
||||||
|
this.evolutionBgOverlay.setAlpha(1);
|
||||||
|
this.evolutionBg.setVisible(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
||||||
|
alpha: 0,
|
||||||
|
duration: 2000,
|
||||||
|
delay: 150,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: this.evolutionBgOverlay,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 250,
|
||||||
|
onComplete: () => this.onEvolutionComplete(evolvedPokemon),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a successful evolution
|
* Handles a successful evolution
|
||||||
* @param evolvedPokemon - The evolved Pokemon
|
* @param evolvedPokemon - The evolved Pokemon
|
||||||
@ -316,85 +457,15 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.pokemonEvoSprite.setVisible(true);
|
this.pokemonEvoSprite.setVisible(true);
|
||||||
this.doCircleInward();
|
this.doCircleInward();
|
||||||
|
|
||||||
const onEvolutionComplete = () => {
|
|
||||||
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
|
||||||
globalScene.time.delayedCall(250, () => {
|
|
||||||
this.pokemon.cry();
|
|
||||||
globalScene.time.delayedCall(1250, () => {
|
|
||||||
globalScene.playSoundWithoutBgm("evolution_fanfare");
|
|
||||||
|
|
||||||
evolvedPokemon.destroy();
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("menu:evolutionDone", {
|
|
||||||
pokemonName: this.preEvolvedPokemonName,
|
|
||||||
evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(),
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
() => this.end(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
fixedInt(4000),
|
|
||||||
);
|
|
||||||
globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
globalScene.time.delayedCall(900, () => {
|
globalScene.time.delayedCall(900, () => {
|
||||||
this.evolutionHandler.canCancel = false;
|
this.evolutionHandler.canCancel = this.canCancel;
|
||||||
|
|
||||||
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
|
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => this.postEvolve(evolvedPokemon));
|
||||||
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved
|
|
||||||
? LearnMoveSituation.EVOLUTION_FUSED
|
|
||||||
: this.pokemon.fusionSpecies
|
|
||||||
? LearnMoveSituation.EVOLUTION_FUSED_BASE
|
|
||||||
: LearnMoveSituation.EVOLUTION;
|
|
||||||
const levelMoves = this.pokemon
|
|
||||||
.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation)
|
|
||||||
.filter(lm => lm[0] === EVOLVE_MOVE);
|
|
||||||
for (const lm of levelMoves) {
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
|
||||||
"LearnMovePhase",
|
|
||||||
globalScene.getPlayerParty().indexOf(this.pokemon),
|
|
||||||
lm[1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
|
||||||
|
|
||||||
globalScene.playSound("se/shine");
|
|
||||||
this.doSpray();
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionOverlay,
|
|
||||||
alpha: 1,
|
|
||||||
duration: 250,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
this.evolutionBgOverlay.setAlpha(1);
|
|
||||||
this.evolutionBg.setVisible(false);
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
|
||||||
alpha: 0,
|
|
||||||
duration: 2000,
|
|
||||||
delay: 150,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 0,
|
|
||||||
duration: 250,
|
|
||||||
onComplete: onEvolutionComplete,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doSpiralUpward() {
|
doSpiralUpward() {
|
||||||
let f = 0;
|
let f = 0;
|
||||||
|
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: 64,
|
repeat: 64,
|
||||||
duration: getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
@ -430,34 +501,41 @@ export class EvolutionPhase extends Phase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doCycle(l: number, lastCycle = 15): Promise<boolean> {
|
/**
|
||||||
return new Promise(resolve => {
|
* Return a tween chain that cycles the evolution sprites
|
||||||
const isLastCycle = l === lastCycle;
|
*/
|
||||||
globalScene.tweens.add({
|
doCycle(cycles: number, lastCycle = 15, onComplete = () => {}): void {
|
||||||
targets: this.pokemonTintSprite,
|
// Make our tween start both at the same time
|
||||||
scale: 0.25,
|
const tweens: Phaser.Types.Tweens.TweenBuilderConfig[] = [];
|
||||||
|
for (let i = cycles; i <= lastCycle; i += 0.5) {
|
||||||
|
tweens.push({
|
||||||
|
targets: [this.pokemonTintSprite, this.pokemonEvoTintSprite],
|
||||||
|
scale: (_target, _key, _value, targetIndex: number, _totalTargets, _tween) => (targetIndex === 0 ? 0.25 : 1),
|
||||||
ease: "Cubic.easeInOut",
|
ease: "Cubic.easeInOut",
|
||||||
duration: 500 / l,
|
duration: 500 / i,
|
||||||
yoyo: !isLastCycle,
|
yoyo: i !== lastCycle,
|
||||||
});
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.pokemonEvoTintSprite,
|
|
||||||
scale: 1,
|
|
||||||
ease: "Cubic.easeInOut",
|
|
||||||
duration: 500 / l,
|
|
||||||
yoyo: !isLastCycle,
|
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
if (this.evolutionHandler.cancelled) {
|
if (this.evolutionHandler.cancelled) {
|
||||||
return resolve(false);
|
// cause the tween chain to complete instantly, skipping the remaining tweens.
|
||||||
|
this.pokemonEvoTintSprite.setScale(1);
|
||||||
|
this.pokemonEvoTintSprite.setVisible(false);
|
||||||
|
this.evoChain?.complete?.();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (l < lastCycle) {
|
if (i === lastCycle) {
|
||||||
this.doCycle(l + 0.5, lastCycle).then(success => resolve(success));
|
this.pokemonEvoTintSprite.setScale(1);
|
||||||
} else {
|
|
||||||
this.pokemonTintSprite.setVisible(false);
|
|
||||||
resolve(true);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.evoChain = globalScene.tweens.chain({
|
||||||
|
targets: null,
|
||||||
|
tweens,
|
||||||
|
onComplete: () => {
|
||||||
|
this.evoChain = null;
|
||||||
|
onComplete();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyPostFaintAbAttrs,
|
|
||||||
applyPostKnockOutAbAttrs,
|
|
||||||
applyPostVictoryAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { battleSpecDialogue } from "#app/data/dialogue";
|
import { battleSpecDialogue } from "#app/data/dialogue";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
@ -117,29 +113,31 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
|
|
||||||
pokemon.resetTera();
|
pokemon.resetTera();
|
||||||
|
|
||||||
|
// TODO: this can be simplified by just checking whether lastAttack is defined
|
||||||
if (pokemon.turnData.attacksReceived?.length) {
|
if (pokemon.turnData.attacksReceived?.length) {
|
||||||
const lastAttack = pokemon.turnData.attacksReceived[0];
|
const lastAttack = pokemon.turnData.attacksReceived[0];
|
||||||
applyPostFaintAbAttrs(
|
applyAbAttrs("PostFaintAbAttr", {
|
||||||
"PostFaintAbAttr",
|
pokemon: pokemon,
|
||||||
pokemon,
|
// TODO: We should refactor lastAttack's sourceId to forbid null and just use undefined
|
||||||
globalScene.getPokemonById(lastAttack.sourceId)!,
|
attacker: globalScene.getPokemonById(lastAttack.sourceId) ?? undefined,
|
||||||
new PokemonMove(lastAttack.move).getMove(),
|
// TODO: improve the way that we provide the move that knocked out the pokemon...
|
||||||
lastAttack.result,
|
move: new PokemonMove(lastAttack.move).getMove(),
|
||||||
); // TODO: is this bang correct?
|
hitResult: lastAttack.result,
|
||||||
|
}); // TODO: is this bang correct?
|
||||||
} else {
|
} else {
|
||||||
//If killed by indirect damage, apply post-faint abilities without providing a last move
|
//If killed by indirect damage, apply post-faint abilities without providing a last move
|
||||||
applyPostFaintAbAttrs("PostFaintAbAttr", pokemon);
|
applyAbAttrs("PostFaintAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
|
|
||||||
const alivePlayField = globalScene.getField(true);
|
const alivePlayField = globalScene.getField(true);
|
||||||
for (const p of alivePlayField) {
|
for (const p of alivePlayField) {
|
||||||
applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon);
|
applyAbAttrs("PostKnockOutAbAttr", { pokemon: p, victim: pokemon });
|
||||||
}
|
}
|
||||||
if (pokemon.turnData.attacksReceived?.length) {
|
if (pokemon.turnData.attacksReceived?.length) {
|
||||||
const defeatSource = this.source;
|
const defeatSource = this.source;
|
||||||
|
|
||||||
if (defeatSource?.isOnField()) {
|
if (defeatSource?.isOnField()) {
|
||||||
applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource);
|
applyAbAttrs("PostVictoryAbAttr", { pokemon: defeatSource });
|
||||||
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
||||||
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
|
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
|
||||||
if (pvattrs.length) {
|
if (pvattrs.length) {
|
||||||
|
@ -3,7 +3,7 @@ import { fixedInt } from "#app/utils/common";
|
|||||||
import { achvs } from "../system/achv";
|
import { achvs } from "../system/achv";
|
||||||
import type { SpeciesFormChange } from "../data/pokemon-forms";
|
import type { SpeciesFormChange } from "../data/pokemon-forms";
|
||||||
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers";
|
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import type { PlayerPokemon } from "../field/pokemon";
|
import type { default as Pokemon, PlayerPokemon } from "../field/pokemon";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import type PartyUiHandler from "../ui/party-ui-handler";
|
import type PartyUiHandler from "../ui/party-ui-handler";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
@ -34,146 +34,158 @@ export class FormChangePhase extends EvolutionPhase {
|
|||||||
return globalScene.ui.setOverlayMode(UiMode.EVOLUTION_SCENE);
|
return globalScene.ui.setOverlayMode(UiMode.EVOLUTION_SCENE);
|
||||||
}
|
}
|
||||||
|
|
||||||
doEvolution(): void {
|
/**
|
||||||
const preName = getPokemonNameWithAffix(this.pokemon);
|
* Commence the tweens that play after the form change animation finishes
|
||||||
|
* @param transformedPokemon - The Pokemon after the evolution
|
||||||
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
* @param preName - The name of the Pokemon before the evolution
|
||||||
[this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => {
|
*/
|
||||||
const spriteKey = transformedPokemon.getSpriteKey(true);
|
private postFormChangeTweens(transformedPokemon: Pokemon, preName: string): void {
|
||||||
try {
|
globalScene.tweens.chain({
|
||||||
sprite.play(spriteKey);
|
targets: null,
|
||||||
} catch (err: unknown) {
|
tweens: [
|
||||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
{
|
||||||
|
targets: this.evolutionOverlay,
|
||||||
|
alpha: 1,
|
||||||
|
duration: 250,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
onComplete: () => {
|
||||||
|
this.evolutionBgOverlay.setAlpha(1);
|
||||||
|
this.evolutionBg.setVisible(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
||||||
|
alpha: 0,
|
||||||
|
duration: 2000,
|
||||||
|
delay: 150,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: this.evolutionBgOverlay,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 250,
|
||||||
|
completeDelay: 250,
|
||||||
|
onComplete: () => this.pokemon.cry(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 1.25 seconds after the pokemon cry
|
||||||
|
completeDelay: 1250,
|
||||||
|
onComplete: () => {
|
||||||
|
let playEvolutionFanfare = false;
|
||||||
|
if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) {
|
||||||
|
globalScene.validateAchv(achvs.MEGA_EVOLVE);
|
||||||
|
playEvolutionFanfare = true;
|
||||||
|
} else if (
|
||||||
|
this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 ||
|
||||||
|
this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1
|
||||||
|
) {
|
||||||
|
globalScene.validateAchv(achvs.GIGANTAMAX);
|
||||||
|
playEvolutionFanfare = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
const delay = playEvolutionFanfare ? 4000 : 1750;
|
||||||
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
|
globalScene.playSoundWithoutBgm(playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare");
|
||||||
sprite.setPipelineData("shiny", transformedPokemon.shiny);
|
transformedPokemon.destroy();
|
||||||
sprite.setPipelineData("variant", transformedPokemon.variant);
|
globalScene.ui.showText(
|
||||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName),
|
||||||
if (transformedPokemon.summonData.speciesForm) {
|
null,
|
||||||
k += "Base";
|
() => this.end(),
|
||||||
}
|
null,
|
||||||
sprite.pipelineData[k] = transformedPokemon.getSprite().pipelineData[k];
|
true,
|
||||||
});
|
fixedInt(delay),
|
||||||
});
|
);
|
||||||
|
globalScene.time.delayedCall(fixedInt(delay + 250), () => globalScene.playBgm());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.time.delayedCall(250, () => {
|
/**
|
||||||
globalScene.tweens.add({
|
* Commence the animations that occur once the form change evolution cycle ({@linkcode doCycle}) is complete
|
||||||
|
*
|
||||||
|
* @privateRemarks
|
||||||
|
* This would prefer {@linkcode doCycle} to be refactored and de-promisified so this can be moved into {@linkcode beginTweens}
|
||||||
|
* @param preName - The name of the Pokemon before the evolution
|
||||||
|
* @param transformedPokemon - The Pokemon being transformed into
|
||||||
|
*/
|
||||||
|
private afterCycle(preName: string, transformedPokemon: Pokemon): void {
|
||||||
|
globalScene.playSound("se/sparkle");
|
||||||
|
this.pokemonEvoSprite.setVisible(true);
|
||||||
|
this.doCircleInward();
|
||||||
|
globalScene.time.delayedCall(900, () => {
|
||||||
|
this.pokemon.changeForm(this.formChange).then(() => {
|
||||||
|
if (!this.modal) {
|
||||||
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
}
|
||||||
|
globalScene.playSound("se/shine");
|
||||||
|
this.doSpray();
|
||||||
|
this.postFormChangeTweens(transformedPokemon, preName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commence the sequence of tweens and events that occur during the evolution animation
|
||||||
|
* @param preName The name of the Pokemon before the evolution
|
||||||
|
* @param transformedPokemon The Pokemon after the evolution
|
||||||
|
*/
|
||||||
|
private beginTweens(preName: string, transformedPokemon: Pokemon): void {
|
||||||
|
globalScene.tweens.chain({
|
||||||
|
// Starts 250ms after sprites have been configured
|
||||||
|
targets: null,
|
||||||
|
tweens: [
|
||||||
|
// Step 1: Fade in the background overlay
|
||||||
|
{
|
||||||
|
delay: 250,
|
||||||
targets: this.evolutionBgOverlay,
|
targets: this.evolutionBgOverlay,
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
delay: 500,
|
|
||||||
duration: 1500,
|
duration: 1500,
|
||||||
ease: "Sine.easeOut",
|
ease: "Sine.easeOut",
|
||||||
|
// We want the backkground overlay to fade out after it fades in
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
globalScene.time.delayedCall(1000, () => {
|
globalScene.tweens.add({
|
||||||
globalScene.tweens.add({
|
targets: this.evolutionBgOverlay,
|
||||||
targets: this.evolutionBgOverlay,
|
alpha: 0,
|
||||||
alpha: 0,
|
duration: 250,
|
||||||
duration: 250,
|
delay: 1000,
|
||||||
});
|
|
||||||
this.evolutionBg.setVisible(true);
|
|
||||||
this.evolutionBg.play();
|
|
||||||
});
|
});
|
||||||
|
this.evolutionBg.setVisible(true).play();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Step 2: Play the sounds and fade in the tint sprite
|
||||||
|
{
|
||||||
|
targets: this.pokemonTintSprite,
|
||||||
|
alpha: { from: 0, to: 1 },
|
||||||
|
duration: 2000,
|
||||||
|
onStart: () => {
|
||||||
globalScene.playSound("se/charge");
|
globalScene.playSound("se/charge");
|
||||||
this.doSpiralUpward();
|
this.doSpiralUpward();
|
||||||
globalScene.tweens.addCounter({
|
|
||||||
from: 0,
|
|
||||||
to: 1,
|
|
||||||
duration: 2000,
|
|
||||||
onUpdate: t => {
|
|
||||||
this.pokemonTintSprite.setAlpha(t.getValue());
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
this.pokemonSprite.setVisible(false);
|
|
||||||
globalScene.time.delayedCall(1100, () => {
|
|
||||||
globalScene.playSound("se/beam");
|
|
||||||
this.doArcDownward();
|
|
||||||
globalScene.time.delayedCall(1000, () => {
|
|
||||||
this.pokemonEvoTintSprite.setScale(0.25);
|
|
||||||
this.pokemonEvoTintSprite.setVisible(true);
|
|
||||||
this.doCycle(1, 1).then(_success => {
|
|
||||||
globalScene.playSound("se/sparkle");
|
|
||||||
this.pokemonEvoSprite.setVisible(true);
|
|
||||||
this.doCircleInward();
|
|
||||||
globalScene.time.delayedCall(900, () => {
|
|
||||||
this.pokemon.changeForm(this.formChange).then(() => {
|
|
||||||
if (!this.modal) {
|
|
||||||
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
|
||||||
}
|
|
||||||
|
|
||||||
globalScene.playSound("se/shine");
|
|
||||||
this.doSpray();
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionOverlay,
|
|
||||||
alpha: 1,
|
|
||||||
duration: 250,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
this.evolutionBgOverlay.setAlpha(1);
|
|
||||||
this.evolutionBg.setVisible(false);
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
|
||||||
alpha: 0,
|
|
||||||
duration: 2000,
|
|
||||||
delay: 150,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 0,
|
|
||||||
duration: 250,
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.time.delayedCall(250, () => {
|
|
||||||
this.pokemon.cry();
|
|
||||||
globalScene.time.delayedCall(1250, () => {
|
|
||||||
let playEvolutionFanfare = false;
|
|
||||||
if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) {
|
|
||||||
globalScene.validateAchv(achvs.MEGA_EVOLVE);
|
|
||||||
playEvolutionFanfare = true;
|
|
||||||
} else if (
|
|
||||||
this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 ||
|
|
||||||
this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1
|
|
||||||
) {
|
|
||||||
globalScene.validateAchv(achvs.GIGANTAMAX);
|
|
||||||
playEvolutionFanfare = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = playEvolutionFanfare ? 4000 : 1750;
|
|
||||||
globalScene.playSoundWithoutBgm(
|
|
||||||
playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare",
|
|
||||||
);
|
|
||||||
|
|
||||||
transformedPokemon.destroy();
|
|
||||||
globalScene.ui.showText(
|
|
||||||
getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName),
|
|
||||||
null,
|
|
||||||
() => this.end(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
fixedInt(delay),
|
|
||||||
);
|
|
||||||
globalScene.time.delayedCall(fixedInt(delay + 250), () =>
|
|
||||||
globalScene.playBgm(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.pokemonSprite.setVisible(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// Step 3: Commence the form change animation via doCycle then continue the animation chain with afterCycle
|
||||||
|
completeDelay: 1100,
|
||||||
|
onComplete: () => {
|
||||||
|
globalScene.playSound("se/beam");
|
||||||
|
this.doArcDownward();
|
||||||
|
globalScene.time.delayedCall(1000, () => {
|
||||||
|
this.pokemonEvoTintSprite.setScale(0.25).setVisible(true);
|
||||||
|
this.doCycle(1, 1, () => this.afterCycle(preName, transformedPokemon));
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doEvolution(): void {
|
||||||
|
const preName = getPokemonNameWithAffix(this.pokemon, false);
|
||||||
|
|
||||||
|
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
||||||
|
this.configureSprite(transformedPokemon, this.pokemonEvoSprite, false);
|
||||||
|
this.configureSprite(transformedPokemon, this.pokemonEvoTintSprite, false);
|
||||||
|
this.beginTweens(preName, transformedPokemon);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ export class GameOverPhase extends BattlePhase {
|
|||||||
battleType: globalScene.currentBattle.battleType,
|
battleType: globalScene.currentBattle.battleType,
|
||||||
trainer: globalScene.currentBattle.trainer ? new TrainerData(globalScene.currentBattle.trainer) : null,
|
trainer: globalScene.currentBattle.trainer ? new TrainerData(globalScene.currentBattle.trainer) : null,
|
||||||
gameVersion: globalScene.game.config.gameVersion,
|
gameVersion: globalScene.game.config.gameVersion,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: Date.now(),
|
||||||
challenges: globalScene.gameMode.challenges.map(c => new ChallengeData(c)),
|
challenges: globalScene.gameMode.challenges.map(c => new ChallengeData(c)),
|
||||||
mysteryEncounterType: globalScene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
mysteryEncounterType: globalScene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
||||||
mysteryEncounterSaveData: globalScene.mysteryEncounterSaveData,
|
mysteryEncounterSaveData: globalScene.mysteryEncounterSaveData,
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyExecutedMoveAbAttrs,
|
|
||||||
applyPostAttackAbAttrs,
|
|
||||||
applyPostDamageAbAttrs,
|
|
||||||
applyPostDefendAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { ConditionalProtectTag } from "#app/data/arena-tag";
|
import { ConditionalProtectTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import { MoveAnim } from "#app/data/battle-anims";
|
import { MoveAnim } from "#app/data/battle-anims";
|
||||||
@ -322,7 +316,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Assume single target for multi hit
|
// Assume single target for multi hit
|
||||||
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
||||||
// If Parental Bond is applicable, add another hit
|
// If Parental Bond is applicable, add another hit
|
||||||
applyPreAttackAbAttrs("AddSecondStrikeAbAttr", user, null, move, false, hitCount, null);
|
applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount });
|
||||||
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
||||||
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
||||||
// Set the user's relevant turnData fields to reflect the final hit count
|
// Set the user's relevant turnData fields to reflect the final hit count
|
||||||
@ -370,7 +364,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Add to the move history entry
|
// Add to the move history entry
|
||||||
if (this.firstHit) {
|
if (this.firstHit) {
|
||||||
user.pushMoveHistory(this.moveHistoryEntry);
|
user.pushMoveHistory(this.moveHistoryEntry);
|
||||||
applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user);
|
applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -439,7 +433,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||||
*/
|
*/
|
||||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
||||||
applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult);
|
applyAbAttrs("PostDefendAbAttr", { pokemon: target, opponent: user, move: this.move, hitResult });
|
||||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,12 +690,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param target - The {@linkcode Pokemon} to be removed
|
* @param target - The {@linkcode Pokemon} to be removed
|
||||||
*/
|
*/
|
||||||
protected removeTarget(target: Pokemon): void {
|
protected removeTarget(target: Pokemon): void {
|
||||||
const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex());
|
const targetIndex = this.targets.indexOf(target.getBattlerIndex());
|
||||||
if (targetIndex !== -1) {
|
if (targetIndex !== -1) {
|
||||||
this.targets.splice(
|
this.targets.splice(this.targets.indexOf(target.getBattlerIndex()), 1);
|
||||||
this.targets.findIndex(ind => ind === target.getBattlerIndex()),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -808,7 +799,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
// Multi-hit check for Wimp Out/Emergency Exit
|
// Multi-hit check for Wimp Out/Emergency Exit
|
||||||
if (user.turnData.hitCount > 1) {
|
if (user.turnData.hitCount > 1) {
|
||||||
applyPostDamageAbAttrs("PostDamageAbAttr", target, 0, target.hasPassive(), false, [], user);
|
// TODO: Investigate why 0 is being passed for damage amount here
|
||||||
|
// and then determing if refactoring `applyMove` to return the damage dealt is appropriate.
|
||||||
|
applyAbAttrs("PostDamageAbAttr", { pokemon: target, damage: 0, source: user });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1002,7 +995,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||||
this.applyOnGetHitAbEffects(user, target, hitResult);
|
this.applyOnGetHitAbEffects(user, target, hitResult);
|
||||||
applyPostAttackAbAttrs("PostAttackAbAttr", user, target, this.move, hitResult);
|
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult });
|
||||||
|
|
||||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
||||||
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
||||||
|
@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
|
|
||||||
export class MoveEndPhase extends PokemonPhase {
|
export class MoveEndPhase extends PokemonPhase {
|
||||||
public readonly phaseName = "MoveEndPhase";
|
public readonly phaseName = "MoveEndPhase";
|
||||||
@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase {
|
|||||||
globalScene.arena.setIgnoreAbilities(false);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
for (const target of this.targets) {
|
for (const target of this.targets) {
|
||||||
if (target) {
|
if (target) {
|
||||||
applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target);
|
applyAbAttrs("PostSummonRemoveEffectAbAttr", { pokemon: target });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import { CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
@ -228,14 +228,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
case StatusEffect.SLEEP: {
|
case StatusEffect.SLEEP: {
|
||||||
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
|
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
|
||||||
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
||||||
applyAbAttrs(
|
applyAbAttrs("ReduceStatusEffectDurationAbAttr", {
|
||||||
"ReduceStatusEffectDurationAbAttr",
|
pokemon: this.pokemon,
|
||||||
this.pokemon,
|
statusEffect: this.pokemon.status.effect,
|
||||||
null,
|
duration: turnsRemaining,
|
||||||
false,
|
});
|
||||||
this.pokemon.status.effect,
|
|
||||||
turnsRemaining,
|
|
||||||
);
|
|
||||||
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
||||||
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
||||||
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
||||||
@ -363,16 +360,19 @@ export class MovePhase extends BattlePhase {
|
|||||||
this.executeMove();
|
this.executeMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Execute the current move and apply its effects. */
|
||||||
private executeMove() {
|
private executeMove() {
|
||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
|
const targets = this.getActiveTargetPokemon();
|
||||||
|
|
||||||
|
// Update the battle's "last move" pointer unless we're currently mimicking a move or triggering Dancer.
|
||||||
if (!move.hasAttr("CopyMoveAttr") && !isReflected(this.useMode)) {
|
if (!move.hasAttr("CopyMoveAttr") && !isReflected(this.useMode)) {
|
||||||
// Update the battle's "last move" pointer unless we're currently mimicking a move or triggering Dancer.
|
|
||||||
// TODO: Research how Copycat interacts with the final attacking turn of Future Sight and co.
|
|
||||||
globalScene.currentBattle.lastMove = move.id;
|
globalScene.currentBattle.lastMove = move.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger ability-based user type changes, display move text and then execute move effects.
|
// Trigger ability-based user type changes, display move text and then execute move effects.
|
||||||
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move);
|
// TODO: Investigate whether PokemonTypeChangeAbAttr can drop the "opponent" parameter
|
||||||
|
applyAbAttrs("PokemonTypeChangeAbAttr", { pokemon: this.pokemon, move, opponent: targets[0] });
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"MoveEffectPhase",
|
"MoveEffectPhase",
|
||||||
@ -384,10 +384,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
|
|
||||||
// Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`).
|
// Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`).
|
||||||
// Note the MoveUseMode check here prevents an infinite Dancer loop.
|
// Note the MoveUseMode check here prevents an infinite Dancer loop.
|
||||||
|
// TODO: This needs to go at the end of `MoveEffectPhase` to check move results
|
||||||
const dancerModes: MoveUseMode[] = [MoveUseMode.INDIRECT, MoveUseMode.REFLECTED] as const;
|
const dancerModes: MoveUseMode[] = [MoveUseMode.INDIRECT, MoveUseMode.REFLECTED] as const;
|
||||||
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
|
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
|
||||||
globalScene.getField(true).forEach(pokemon => {
|
globalScene.getField(true).forEach(pokemon => {
|
||||||
applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets);
|
applyAbAttrs("PostMoveUsedAbAttr", { pokemon, move: this.move, source: this.pokemon, targets: this.targets });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,18 +404,22 @@ export class MovePhase extends BattlePhase {
|
|||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
const targets = this.getActiveTargetPokemon();
|
const targets = this.getActiveTargetPokemon();
|
||||||
|
|
||||||
// NOTE: DO NOT CHANGE THE ORDER OF OPERATIONS HERE.
|
// DO NOT CHANGE THE ORDER OF OPERATIONS HERE!
|
||||||
// Protean is supposed to trigger its effects first, _then_ move text is displayed,
|
// Protean is supposed to trigger its effects first, _then_ move text is displayed,
|
||||||
// _then_ any blockage messages are shown.
|
// _then_ any blockage messages are shown.
|
||||||
|
|
||||||
// Roar, Whirlwind, Trick-or-Treat, and Forest's Curse will trigger Protean/Libero
|
// Roar, Whirlwind, Trick-or-Treat, and Forest's Curse will trigger Protean/Libero
|
||||||
// even on failure, as will all moves blocked by terrain.
|
// even on failure, as will all moves blocked by terrain.
|
||||||
// TODO: Verify if this also applies to primal weather failures?
|
// TODO: Verify if this also applies to primal weather failures
|
||||||
if (
|
if (
|
||||||
failedDueToTerrain ||
|
failedDueToTerrain ||
|
||||||
[MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)
|
[MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)
|
||||||
) {
|
) {
|
||||||
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move);
|
applyAbAttrs("PokemonTypeChangeAbAttr", {
|
||||||
|
pokemon: this.pokemon,
|
||||||
|
move: this.move.getMove(),
|
||||||
|
opponent: targets[0],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showText) {
|
if (showText) {
|
||||||
@ -431,7 +436,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
// Use move-specific failure messages if present before checking terrain/weather blockage
|
// Use move-specific failure messages if present before checking terrain/weather blockage
|
||||||
// and falling back to the classic "But it failed!".
|
// and falling back to the classic "But it failed!".
|
||||||
const failureMessage =
|
const failureMessage =
|
||||||
move.getFailedText(this.pokemon, targets[0], move) ??
|
move.getFailedText(this.pokemon, targets[0], move) ||
|
||||||
(failedDueToTerrain
|
(failedDueToTerrain
|
||||||
? getTerrainBlockMessage(targets[0], globalScene.arena.getTerrainType())
|
? getTerrainBlockMessage(targets[0], globalScene.arena.getTerrainType())
|
||||||
: failedDueToWeather
|
: failedDueToWeather
|
||||||
@ -458,7 +463,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Protean and Libero apply on the charging turn of charge moves, even before showing usage text
|
// Protean and Libero apply on the charging turn of charge moves, even before showing usage text
|
||||||
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
|
applyAbAttrs("PokemonTypeChangeAbAttr", {
|
||||||
|
pokemon: this.pokemon,
|
||||||
|
move: this.move.getMove(),
|
||||||
|
opponent: targets[0],
|
||||||
|
});
|
||||||
|
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
@ -492,8 +501,8 @@ export class MovePhase extends BattlePhase {
|
|||||||
*/
|
*/
|
||||||
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
|
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
|
||||||
const foesWithPressure = this.pokemon
|
const foesWithPressure = this.pokemon
|
||||||
.getOpponents()
|
.getOpponents(true)
|
||||||
.filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr("IncreasePpAbAttr"));
|
.filter(o => targets.includes(o) && o.hasAbilityWithAttr("IncreasePpAbAttr"));
|
||||||
return foesWithPressure.length;
|
return foesWithPressure.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,62 +512,71 @@ export class MovePhase extends BattlePhase {
|
|||||||
* - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`).
|
* - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`).
|
||||||
*/
|
*/
|
||||||
protected resolveRedirectTarget(): void {
|
protected resolveRedirectTarget(): void {
|
||||||
if (this.targets.length === 1) {
|
if (this.targets.length !== 1) {
|
||||||
const currentTarget = this.targets[0];
|
// Spread moves cannot be redirected
|
||||||
const redirectTarget = new NumberHolder(currentTarget);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// check move redirection abilities of every pokemon *except* the user.
|
const currentTarget = this.targets[0];
|
||||||
globalScene
|
const redirectTarget = new NumberHolder(currentTarget);
|
||||||
.getField(true)
|
|
||||||
.filter(p => p !== this.pokemon)
|
|
||||||
.forEach(p =>
|
|
||||||
applyAbAttrs("RedirectMoveAbAttr", p, null, false, this.move.moveId, redirectTarget, this.pokemon),
|
|
||||||
);
|
|
||||||
|
|
||||||
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
|
// check move redirection abilities of every pokemon *except* the user.
|
||||||
let redirectedByAbility = currentTarget !== redirectTarget.value;
|
globalScene
|
||||||
|
.getField(true)
|
||||||
|
.filter(p => p !== this.pokemon)
|
||||||
|
.forEach(pokemon => {
|
||||||
|
applyAbAttrs("RedirectMoveAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
moveId: this.move.moveId,
|
||||||
|
targetIndex: redirectTarget,
|
||||||
|
sourcePokemon: this.pokemon,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// check for center-of-attention tags (note that this will override redirect abilities)
|
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
|
||||||
this.pokemon.getOpponents().forEach(p => {
|
let redirectedByAbility = currentTarget !== redirectTarget.value;
|
||||||
const redirectTag = p.getTag(CenterOfAttentionTag);
|
|
||||||
|
|
||||||
// TODO: don't hardcode this interaction.
|
// check for center-of-attention tags (note that this will override redirect abilities)
|
||||||
// Handle interaction between the rage powder center-of-attention tag and moves used by grass types/overcoat-havers (which are immune to RP's redirect)
|
this.pokemon.getOpponents(true).forEach(p => {
|
||||||
if (
|
const redirectTag = p.getTag(CenterOfAttentionTag);
|
||||||
redirectTag &&
|
|
||||||
(!redirectTag.powder ||
|
// TODO: don't hardcode this interaction.
|
||||||
(!this.pokemon.isOfType(PokemonType.GRASS) && !this.pokemon.hasAbility(AbilityId.OVERCOAT)))
|
// Handle interaction between the rage powder center-of-attention tag and moves used by grass types/overcoat-havers (which are immune to RP's redirect)
|
||||||
) {
|
if (
|
||||||
redirectTarget.value = p.getBattlerIndex();
|
redirectTag &&
|
||||||
redirectedByAbility = false;
|
(!redirectTag.powder ||
|
||||||
|
(!this.pokemon.isOfType(PokemonType.GRASS) && !this.pokemon.hasAbility(AbilityId.OVERCOAT)))
|
||||||
|
) {
|
||||||
|
redirectTarget.value = p.getBattlerIndex();
|
||||||
|
redirectedByAbility = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Don't hardcode these ability interactions
|
||||||
|
if (currentTarget !== redirectTarget.value) {
|
||||||
|
const bypassRedirectAttrs = this.move.getMove().getAttrs("BypassRedirectAttr");
|
||||||
|
bypassRedirectAttrs.forEach(attr => {
|
||||||
|
if (!attr.abilitiesOnly || redirectedByAbility) {
|
||||||
|
redirectTarget.value = currentTarget;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentTarget !== redirectTarget.value) {
|
if (this.pokemon.hasAbilityWithAttr("BlockRedirectAbAttr")) {
|
||||||
const bypassRedirectAttrs = this.move.getMove().getAttrs("BypassRedirectAttr");
|
redirectTarget.value = currentTarget;
|
||||||
bypassRedirectAttrs.forEach(attr => {
|
// TODO: Ability displays should be handled by the ability
|
||||||
if (!attr.abilitiesOnly || redirectedByAbility) {
|
globalScene.phaseManager.queueAbilityDisplay(
|
||||||
redirectTarget.value = currentTarget;
|
this.pokemon,
|
||||||
}
|
this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
|
||||||
});
|
true,
|
||||||
|
);
|
||||||
if (this.pokemon.hasAbilityWithAttr("BlockRedirectAbAttr")) {
|
globalScene.phaseManager.queueAbilityDisplay(
|
||||||
redirectTarget.value = currentTarget;
|
this.pokemon,
|
||||||
// TODO: Ability displays should be handled by the ability
|
this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
|
||||||
globalScene.phaseManager.queueAbilityDisplay(
|
false,
|
||||||
this.pokemon,
|
);
|
||||||
this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
globalScene.phaseManager.queueAbilityDisplay(
|
|
||||||
this.pokemon,
|
|
||||||
this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.targets[0] = redirectTarget.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.targets[0] = redirectTarget.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,7 +592,8 @@ export class MovePhase extends BattlePhase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pokemon.turnData.attacksReceived.length) {
|
// TODO: This should be covered in move conditions
|
||||||
|
if (this.pokemon.turnData.attacksReceived.length === 0) {
|
||||||
this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex;
|
this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex;
|
||||||
|
|
||||||
// account for metal burst and comeuppance hitting remaining targets in double battles
|
// account for metal burst and comeuppance hitting remaining targets in double battles
|
||||||
@ -616,12 +635,13 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.failed) {
|
if (this.failed) {
|
||||||
|
// TODO: should this consider struggle?
|
||||||
const ppUsed = isIgnorePP(this.useMode) ? 0 : 1;
|
const ppUsed = isIgnorePP(this.useMode) ? 0 : 1;
|
||||||
this.move.usePp(ppUsed);
|
this.move.usePp(ppUsed);
|
||||||
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.cancelled && this.pokemon.summonData.tags?.find(t => t.tagType === BattlerTagType.FRENZY)) {
|
if (this.cancelled && this.pokemon.summonData.tags.some(t => t.tagType === BattlerTagType.FRENZY)) {
|
||||||
frenzyMissFunc(this.pokemon, this.move.getMove());
|
frenzyMissFunc(this.pokemon, this.move.getMove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
|||||||
if (pokemon) {
|
if (pokemon) {
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
if (pokemon.isOnField()) {
|
if (pokemon.isOnField()) {
|
||||||
applyAbAttrs("PostBiomeChangeAbAttr", pokemon, null);
|
applyAbAttrs("PostBiomeChangeAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import { applyPostSetStatusAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
@ -53,7 +53,11 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
||||||
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
|
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
|
||||||
globalScene.arena.setIgnoreAbilities(false);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
applyPostSetStatusAbAttrs("PostSetStatusAbAttr", pokemon, this.statusEffect, this.sourcePokemon);
|
applyAbAttrs("PostSetStatusAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
effect: this.statusEffect,
|
||||||
|
sourcePokemon: this.sourcePokemon ?? undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
|
|
||||||
@ -16,7 +16,8 @@ export class PostSummonActivateAbilityPhase extends PostSummonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
applyPostSummonAbAttrs("PostSummonAbAttr", this.getPokemon(), this.passive, false);
|
// TODO: Check with Dean on whether or not passive must be provided to `this.passive`
|
||||||
|
applyAbAttrs("PostSummonAbAttr", { pokemon: this.getPokemon(), passive: this.passive });
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export class PostSummonPhase extends PokemonPhase {
|
|||||||
|
|
||||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
for (const p of field) {
|
for (const p of field) {
|
||||||
applyAbAttrs("CommanderAbAttr", p, null, false);
|
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { applyAbAttrs, applyPostDamageAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { CommonBattleAnim } from "#app/data/battle-anims";
|
import { CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
||||||
@ -22,8 +22,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
||||||
pokemon.status.incrementTurn();
|
pokemon.status.incrementTurn();
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
applyAbAttrs("BlockStatusDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockStatusDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -39,14 +39,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
break;
|
break;
|
||||||
case StatusEffect.BURN:
|
case StatusEffect.BURN:
|
||||||
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
|
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
|
||||||
applyAbAttrs("ReduceBurnDamageAbAttr", pokemon, null, false, damage);
|
applyAbAttrs("ReduceBurnDamageAbAttr", { pokemon, burnDamage: damage });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (damage.value) {
|
if (damage.value) {
|
||||||
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
||||||
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
|
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
applyPostDamageAbAttrs("PostDamageAbAttr", pokemon, damage.value, pokemon.hasPassive(), false, []);
|
applyAbAttrs("PostDamageAbAttr", { pokemon, damage: damage.value });
|
||||||
}
|
}
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end());
|
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end());
|
||||||
} else {
|
} else {
|
||||||
|
@ -181,9 +181,10 @@ export class QuietFormChangePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
|
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
|
||||||
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null);
|
const params = { pokemon: this.pokemon };
|
||||||
applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null);
|
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", params);
|
||||||
applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null);
|
applyAbAttrs("ClearWeatherAbAttr", params);
|
||||||
|
applyAbAttrs("ClearTerrainAbAttr", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostStatStageChangeAbAttrs,
|
|
||||||
applyPreStatStageChangeAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { MistTag } from "#app/data/arena-tag";
|
import { MistTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { ArenaTag } from "#app/data/arena-tag";
|
import type { ArenaTag } from "#app/data/arena-tag";
|
||||||
@ -18,6 +14,10 @@ import { PokemonPhase } from "./pokemon-phase";
|
|||||||
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
||||||
import { OctolockTag } from "#app/data/battler-tags";
|
import { OctolockTag } from "#app/data/battler-tags";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
|
import type {
|
||||||
|
ConditionalUserFieldProtectStatAbAttrParams,
|
||||||
|
PreStatStageChangeAbAttrParams,
|
||||||
|
} from "#app/@types/ability-types";
|
||||||
|
|
||||||
export type StatStageChangeCallback = (
|
export type StatStageChangeCallback = (
|
||||||
target: Pokemon | null,
|
target: Pokemon | null,
|
||||||
@ -126,7 +126,7 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
const stages = new NumberHolder(this.stages);
|
const stages = new NumberHolder(this.stages);
|
||||||
|
|
||||||
if (!this.ignoreAbilities) {
|
if (!this.ignoreAbilities) {
|
||||||
applyAbAttrs("StatStageChangeMultiplierAbAttr", pokemon, null, false, stages);
|
applyAbAttrs("StatStageChangeMultiplierAbAttr", { pokemon, numStages: stages });
|
||||||
}
|
}
|
||||||
|
|
||||||
let simulate = false;
|
let simulate = false;
|
||||||
@ -146,42 +146,38 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
||||||
applyPreStatStageChangeAbAttrs("ProtectStatAbAttr", pokemon, stat, cancelled, simulate);
|
const abAttrParams: PreStatStageChangeAbAttrParams & ConditionalUserFieldProtectStatAbAttrParams = {
|
||||||
applyPreStatStageChangeAbAttrs(
|
|
||||||
"ConditionalUserFieldProtectStatAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
cancelled,
|
cancelled,
|
||||||
simulate,
|
simulated: simulate,
|
||||||
pokemon,
|
target: pokemon,
|
||||||
);
|
stages: this.stages,
|
||||||
|
};
|
||||||
|
applyAbAttrs("ProtectStatAbAttr", abAttrParams);
|
||||||
|
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", abAttrParams);
|
||||||
|
// TODO: Consider skipping this call if `cancelled` is false.
|
||||||
const ally = pokemon.getAlly();
|
const ally = pokemon.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyPreStatStageChangeAbAttrs(
|
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", { ...abAttrParams, pokemon: ally });
|
||||||
"ConditionalUserFieldProtectStatAbAttr",
|
|
||||||
ally,
|
|
||||||
stat,
|
|
||||||
cancelled,
|
|
||||||
simulate,
|
|
||||||
pokemon,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */
|
/** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */
|
||||||
if (
|
if (
|
||||||
opponentPokemon !== undefined &&
|
opponentPokemon !== undefined &&
|
||||||
|
// TODO: investigate whether this is stoping mirror armor from applying to non-octolock
|
||||||
|
// reasons for stat drops if the user has the Octolock tag
|
||||||
!pokemon.findTag(t => t instanceof OctolockTag) &&
|
!pokemon.findTag(t => t instanceof OctolockTag) &&
|
||||||
!this.comingFromMirrorArmorUser
|
!this.comingFromMirrorArmorUser
|
||||||
) {
|
) {
|
||||||
applyPreStatStageChangeAbAttrs(
|
applyAbAttrs("ReflectStatStageChangeAbAttr", {
|
||||||
"ReflectStatStageChangeAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
cancelled,
|
cancelled,
|
||||||
simulate,
|
simulated: simulate,
|
||||||
opponentPokemon,
|
source: opponentPokemon,
|
||||||
this.stages,
|
stages: this.stages,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,17 +218,16 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
|
|
||||||
if (stages.value > 0 && this.canBeCopied) {
|
if (stages.value > 0 && this.canBeCopied) {
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
for (const opponent of pokemon.getOpponents()) {
|
||||||
applyAbAttrs("StatStageChangeCopyAbAttr", opponent, null, false, this.stats, stages.value);
|
applyAbAttrs("StatStageChangeCopyAbAttr", { pokemon: opponent, stats: this.stats, numStages: stages.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostStatStageChangeAbAttrs(
|
applyAbAttrs("PostStatStageChangeAbAttr", {
|
||||||
"PostStatStageChangeAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
filteredStats,
|
stats: filteredStats,
|
||||||
this.stages,
|
stages: this.stages,
|
||||||
this.selfTarget,
|
selfTarget: this.selfTarget,
|
||||||
);
|
});
|
||||||
|
|
||||||
// Look for any other stat change phases; if this is the last one, do White Herb check
|
// Look for any other stat change phases; if this is the last one, do White Herb check
|
||||||
const existingPhase = globalScene.phaseManager.findPhase(
|
const existingPhase = globalScene.phaseManager.findPhase(
|
||||||
|
@ -10,7 +10,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase";
|
import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import { applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class SummonPhase extends PartyMemberPokemonPhase {
|
export class SummonPhase extends PartyMemberPokemonPhase {
|
||||||
@ -27,7 +27,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
applyPreSummonAbAttrs("PreSummonAbAttr", this.getPokemon());
|
applyAbAttrs("PreSummonAbAttr", { pokemon: this.getPokemon() });
|
||||||
this.preSummon();
|
this.preSummon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyPreSummonAbAttrs, applyPreSwitchOutAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
@ -44,7 +44,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
preSummon(): void {
|
preSummon(): void {
|
||||||
if (!this.player) {
|
if (!this.player) {
|
||||||
if (this.slotIndex === -1) {
|
if (this.slotIndex === -1) {
|
||||||
//@ts-ignore
|
//@ts-expect-error
|
||||||
this.slotIndex = globalScene.currentBattle.trainer?.getNextSummonIndex(
|
this.slotIndex = globalScene.currentBattle.trainer?.getNextSummonIndex(
|
||||||
!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER,
|
!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER,
|
||||||
); // TODO: what would be the default trainer-slot fallback?
|
); // TODO: what would be the default trainer-slot fallback?
|
||||||
@ -124,8 +124,8 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
switchedInPokemon.resetSummonData();
|
switchedInPokemon.resetSummonData();
|
||||||
switchedInPokemon.loadAssets(true);
|
switchedInPokemon.loadAssets(true);
|
||||||
|
|
||||||
applyPreSummonAbAttrs("PreSummonAbAttr", switchedInPokemon);
|
applyAbAttrs("PreSummonAbAttr", { pokemon: switchedInPokemon });
|
||||||
applyPreSwitchOutAbAttrs("PreSwitchOutAbAttr", this.lastPokemon);
|
applyAbAttrs("PreSwitchOutAbAttr", { pokemon: this.lastPokemon });
|
||||||
if (!switchedInPokemon) {
|
if (!switchedInPokemon) {
|
||||||
this.end();
|
this.end();
|
||||||
return;
|
return;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyPostTurnAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { TerrainType } from "#app/data/terrain";
|
import { TerrainType } from "#app/data/terrain";
|
||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
@ -49,7 +49,7 @@ export class TurnEndPhase extends FieldPhase {
|
|||||||
globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostTurnAbAttrs("PostTurnAbAttr", pokemon);
|
applyAbAttrs("PostTurnAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
||||||
|
@ -66,8 +66,12 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
globalScene.getField(true).forEach(p => {
|
globalScene.getField(true).forEach(p => {
|
||||||
const bypassSpeed = new BooleanHolder(false);
|
const bypassSpeed = new BooleanHolder(false);
|
||||||
const canCheckHeldItems = new BooleanHolder(true);
|
const canCheckHeldItems = new BooleanHolder(true);
|
||||||
applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed);
|
applyAbAttrs("BypassSpeedChanceAbAttr", { pokemon: p, bypass: bypassSpeed });
|
||||||
applyAbAttrs("PreventBypassSpeedChanceAbAttr", p, null, false, bypassSpeed, canCheckHeldItems);
|
applyAbAttrs("PreventBypassSpeedChanceAbAttr", {
|
||||||
|
pokemon: p,
|
||||||
|
bypass: bypassSpeed,
|
||||||
|
canCheckHeldItems: canCheckHeldItems,
|
||||||
|
});
|
||||||
if (canCheckHeldItems.value) {
|
if (canCheckHeldItems.value) {
|
||||||
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyPreWeatherEffectAbAttrs,
|
|
||||||
applyAbAttrs,
|
|
||||||
applyPostWeatherLapseAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import type { Weather } from "#app/data/weather";
|
import type { Weather } from "#app/data/weather";
|
||||||
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
||||||
@ -41,15 +37,15 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
this.executeForAll((pokemon: Pokemon) =>
|
this.executeForAll((pokemon: Pokemon) =>
|
||||||
applyPreWeatherEffectAbAttrs("SuppressWeatherEffectAbAttr", pokemon, this.weather, cancelled),
|
applyAbAttrs("SuppressWeatherEffectAbAttr", { pokemon, weather: this.weather, cancelled }),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const inflictDamage = (pokemon: Pokemon) => {
|
const inflictDamage = (pokemon: Pokemon) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
applyPreWeatherEffectAbAttrs("PreWeatherDamageAbAttr", pokemon, this.weather, cancelled);
|
applyAbAttrs("PreWeatherDamageAbAttr", { pokemon, weather: this.weather, cancelled });
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cancelled.value ||
|
cancelled.value ||
|
||||||
@ -80,7 +76,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
|
globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
|
||||||
this.executeForAll((pokemon: Pokemon) => {
|
this.executeForAll((pokemon: Pokemon) => {
|
||||||
if (!pokemon.switchOutStatus) {
|
if (!pokemon.switchOutStatus) {
|
||||||
applyPostWeatherLapseAbAttrs("PostWeatherLapseAbAttr", pokemon, this.weather);
|
applyAbAttrs("PostWeatherLapseAbAttr", { pokemon, weather: this.weather });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export abstract class ApiBase {
|
|||||||
* @param dataType The data-type of the {@linkcode bodyData}.
|
* @param dataType The data-type of the {@linkcode bodyData}.
|
||||||
*/
|
*/
|
||||||
protected async doPost<D = undefined>(path: string, bodyData?: D, dataType: DataType = "json") {
|
protected async doPost<D = undefined>(path: string, bodyData?: D, dataType: DataType = "json") {
|
||||||
let body: string | undefined = undefined;
|
let body: string | undefined;
|
||||||
const headers: HeadersInit = {};
|
const headers: HeadersInit = {};
|
||||||
|
|
||||||
if (bodyData) {
|
if (bodyData) {
|
||||||
|
@ -9,7 +9,7 @@ import type BattleScene from "#app/battle-scene";
|
|||||||
// Regex patterns
|
// Regex patterns
|
||||||
|
|
||||||
/** Regex matching double underscores */
|
/** Regex matching double underscores */
|
||||||
const DUNDER_REGEX = /\_{2}/g;
|
const DUNDER_REGEX = /_{2}/g;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the sprite ID from a pokemon form.
|
* Calculate the sprite ID from a pokemon form.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { expSpriteKeys } from "#app/sprites/sprite-keys";
|
import { expSpriteKeys } from "#app/sprites/sprite-keys";
|
||||||
|
|
||||||
const expKeyRegex = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(\-.*?)?(?:_[1-3])?$/;
|
const expKeyRegex = /^pkmn__?(back__)?(shiny__)?(female__)?(\d+)(-.*?)?(?:_[1-3])?$/;
|
||||||
|
|
||||||
export function hasExpSprite(key: string): boolean {
|
export function hasExpSprite(key: string): boolean {
|
||||||
const keyMatch = expKeyRegex.exec(key);
|
const keyMatch = expKeyRegex.exec(key);
|
||||||
|
@ -39,7 +39,7 @@ import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/
|
|||||||
import type { SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
import type { SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||||
import { setSettingKeyboard } from "#app/system/settings/settings-keyboard";
|
import { setSettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||||
import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
// biome-ignore lint/style/noNamespaceImport: Something weird is going on here and I don't want to touch it
|
// biome-ignore lint/performance/noNamespaceImport: Something weird is going on here and I don't want to touch it
|
||||||
import * as Modifier from "#app/modifier/modifier";
|
import * as Modifier from "#app/modifier/modifier";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import ChallengeData from "#app/system/challenge-data";
|
import ChallengeData from "#app/system/challenge-data";
|
||||||
@ -300,7 +300,7 @@ export class GameData {
|
|||||||
voucherCounts: this.voucherCounts,
|
voucherCounts: this.voucherCounts,
|
||||||
eggs: this.eggs.map(e => new EggData(e)),
|
eggs: this.eggs.map(e => new EggData(e)),
|
||||||
gameVersion: globalScene.game.config.gameVersion,
|
gameVersion: globalScene.game.config.gameVersion,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: Date.now(),
|
||||||
eggPity: this.eggPity.slice(0),
|
eggPity: this.eggPity.slice(0),
|
||||||
unlockPity: this.unlockPity.slice(0),
|
unlockPity: this.unlockPity.slice(0),
|
||||||
};
|
};
|
||||||
@ -930,7 +930,7 @@ export class GameData {
|
|||||||
? new TrainerData(globalScene.currentBattle.trainer)
|
? new TrainerData(globalScene.currentBattle.trainer)
|
||||||
: null,
|
: null,
|
||||||
gameVersion: globalScene.game.config.gameVersion,
|
gameVersion: globalScene.game.config.gameVersion,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: Date.now(),
|
||||||
challenges: globalScene.gameMode.challenges.map(c => new ChallengeData(c)),
|
challenges: globalScene.gameMode.challenges.map(c => new ChallengeData(c)),
|
||||||
mysteryEncounterType: globalScene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
mysteryEncounterType: globalScene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
||||||
mysteryEncounterSaveData: globalScene.mysteryEncounterSaveData,
|
mysteryEncounterSaveData: globalScene.mysteryEncounterSaveData,
|
||||||
@ -939,7 +939,7 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSession(slotId: number): Promise<SessionSaveData | null> {
|
getSession(slotId: number): Promise<SessionSaveData | null> {
|
||||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: <explanation>
|
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (slotId < 0) {
|
if (slotId < 0) {
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
@ -980,7 +980,7 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
|
loadSession(slotId: number, sessionData?: SessionSaveData): Promise<boolean> {
|
||||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: <explanation>
|
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO: fix this
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const initSessionFromData = async (sessionData: SessionSaveData) => {
|
const initSessionFromData = async (sessionData: SessionSaveData) => {
|
||||||
@ -1610,7 +1610,7 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.defaultDexData = Object.assign({}, data);
|
this.defaultDexData = { ...data };
|
||||||
this.dexData = data;
|
this.dexData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,13 @@ import type BattleScene from "#app/battle-scene";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { FixedInt } from "#app/utils/common";
|
import { FixedInt } from "#app/utils/common";
|
||||||
|
|
||||||
|
type TweenManager = typeof Phaser.Tweens.TweenManager.prototype;
|
||||||
|
|
||||||
|
/** The set of properties to mutate */
|
||||||
|
const PROPERTIES = ["delay", "completeDelay", "loopDelay", "duration", "repeatDelay", "hold", "startDelay"];
|
||||||
|
|
||||||
type FadeInType = typeof FadeIn;
|
type FadeInType = typeof FadeIn;
|
||||||
type FadeOutType = typeof FadeOut;
|
type FadeOutType = typeof FadeOut;
|
||||||
|
|
||||||
export function initGameSpeed() {
|
export function initGameSpeed() {
|
||||||
const thisArg = this as BattleScene;
|
const thisArg = this as BattleScene;
|
||||||
|
|
||||||
@ -18,14 +22,44 @@ export function initGameSpeed() {
|
|||||||
return thisArg.gameSpeed === 1 ? value : Math.ceil((value /= thisArg.gameSpeed));
|
return thisArg.gameSpeed === 1 ? value : Math.ceil((value /= thisArg.gameSpeed));
|
||||||
};
|
};
|
||||||
|
|
||||||
const originalAddEvent = this.time.addEvent;
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Complexity is necessary here
|
||||||
|
const mutateProperties = (obj: any, allowArray = false) => {
|
||||||
|
// We do not mutate Tweens or TweenChain objects themselves.
|
||||||
|
if (obj instanceof Phaser.Tweens.Tween || obj instanceof Phaser.Tweens.TweenChain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If allowArray is true then check if first obj is an array and if so, mutate the tweens inside
|
||||||
|
if (allowArray && Array.isArray(obj)) {
|
||||||
|
for (const tween of obj) {
|
||||||
|
mutateProperties(tween);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const prop of PROPERTIES) {
|
||||||
|
const objProp = obj[prop];
|
||||||
|
if (typeof objProp === "number" || objProp instanceof FixedInt) {
|
||||||
|
obj[prop] = transformValue(objProp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the object has a 'tweens' property that is an array, then it is a tween chain
|
||||||
|
// and we need to mutate its properties as well
|
||||||
|
if (obj.tweens && Array.isArray(obj.tweens)) {
|
||||||
|
for (const tween of obj.tweens) {
|
||||||
|
mutateProperties(tween);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalAddEvent: typeof Phaser.Time.Clock.prototype.addEvent = this.time.addEvent;
|
||||||
this.time.addEvent = function (config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig) {
|
this.time.addEvent = function (config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig) {
|
||||||
if (!(config instanceof Phaser.Time.TimerEvent) && config.delay) {
|
if (!(config instanceof Phaser.Time.TimerEvent) && config.delay) {
|
||||||
config.delay = transformValue(config.delay);
|
config.delay = transformValue(config.delay);
|
||||||
}
|
}
|
||||||
return originalAddEvent.apply(this, [config]);
|
return originalAddEvent.apply(this, [config]);
|
||||||
};
|
};
|
||||||
const originalTweensAdd = this.tweens.add;
|
const originalTweensAdd: TweenManager["add"] = this.tweens.add;
|
||||||
|
|
||||||
this.tweens.add = function (
|
this.tweens.add = function (
|
||||||
config:
|
config:
|
||||||
| Phaser.Types.Tweens.TweenBuilderConfig
|
| Phaser.Types.Tweens.TweenBuilderConfig
|
||||||
@ -33,71 +67,33 @@ export function initGameSpeed() {
|
|||||||
| Phaser.Tweens.Tween
|
| Phaser.Tweens.Tween
|
||||||
| Phaser.Tweens.TweenChain,
|
| Phaser.Tweens.TweenChain,
|
||||||
) {
|
) {
|
||||||
if (config.loopDelay) {
|
mutateProperties(config);
|
||||||
config.loopDelay = transformValue(config.loopDelay as number);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(config instanceof Phaser.Tweens.TweenChain)) {
|
|
||||||
if (config.duration) {
|
|
||||||
config.duration = transformValue(config.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(config instanceof Phaser.Tweens.Tween)) {
|
|
||||||
if (config.delay) {
|
|
||||||
config.delay = transformValue(config.delay as number);
|
|
||||||
}
|
|
||||||
if (config.repeatDelay) {
|
|
||||||
config.repeatDelay = transformValue(config.repeatDelay);
|
|
||||||
}
|
|
||||||
if (config.hold) {
|
|
||||||
config.hold = transformValue(config.hold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalTweensAdd.apply(this, [config]);
|
return originalTweensAdd.apply(this, [config]);
|
||||||
};
|
} as typeof originalTweensAdd;
|
||||||
const originalTweensChain = this.tweens.chain;
|
|
||||||
|
const originalTweensChain: TweenManager["chain"] = this.tweens.chain;
|
||||||
this.tweens.chain = function (config: Phaser.Types.Tweens.TweenChainBuilderConfig): Phaser.Tweens.TweenChain {
|
this.tweens.chain = function (config: Phaser.Types.Tweens.TweenChainBuilderConfig): Phaser.Tweens.TweenChain {
|
||||||
if (config.tweens) {
|
mutateProperties(config);
|
||||||
for (const t of config.tweens) {
|
|
||||||
if (t.duration) {
|
|
||||||
t.duration = transformValue(t.duration);
|
|
||||||
}
|
|
||||||
if (t.delay) {
|
|
||||||
t.delay = transformValue(t.delay as number);
|
|
||||||
}
|
|
||||||
if (t.repeatDelay) {
|
|
||||||
t.repeatDelay = transformValue(t.repeatDelay);
|
|
||||||
}
|
|
||||||
if (t.loopDelay) {
|
|
||||||
t.loopDelay = transformValue(t.loopDelay as number);
|
|
||||||
}
|
|
||||||
if (t.hold) {
|
|
||||||
t.hold = transformValue(t.hold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalTweensChain.apply(this, [config]);
|
return originalTweensChain.apply(this, [config]);
|
||||||
};
|
} as typeof originalTweensChain;
|
||||||
const originalAddCounter = this.tweens.addCounter;
|
const originalAddCounter: TweenManager["addCounter"] = this.tweens.addCounter;
|
||||||
|
|
||||||
this.tweens.addCounter = function (config: Phaser.Types.Tweens.NumberTweenBuilderConfig) {
|
this.tweens.addCounter = function (config: Phaser.Types.Tweens.NumberTweenBuilderConfig) {
|
||||||
if (config.duration) {
|
mutateProperties(config);
|
||||||
config.duration = transformValue(config.duration);
|
|
||||||
}
|
|
||||||
if (config.delay) {
|
|
||||||
config.delay = transformValue(config.delay);
|
|
||||||
}
|
|
||||||
if (config.repeatDelay) {
|
|
||||||
config.repeatDelay = transformValue(config.repeatDelay);
|
|
||||||
}
|
|
||||||
if (config.loopDelay) {
|
|
||||||
config.loopDelay = transformValue(config.loopDelay as number);
|
|
||||||
}
|
|
||||||
if (config.hold) {
|
|
||||||
config.hold = transformValue(config.hold);
|
|
||||||
}
|
|
||||||
return originalAddCounter.apply(this, [config]);
|
return originalAddCounter.apply(this, [config]);
|
||||||
};
|
} as typeof originalAddCounter;
|
||||||
|
|
||||||
|
const originalCreate: TweenManager["create"] = this.tweens.create;
|
||||||
|
this.tweens.create = function (config: Phaser.Types.Tweens.TweenBuilderConfig) {
|
||||||
|
mutateProperties(config, true);
|
||||||
|
return originalCreate.apply(this, [config]);
|
||||||
|
} as typeof originalCreate;
|
||||||
|
|
||||||
|
const originalAddMultiple: TweenManager["addMultiple"] = this.tweens.addMultiple;
|
||||||
|
this.tweens.addMultiple = function (config: Phaser.Types.Tweens.TweenBuilderConfig[]) {
|
||||||
|
mutateProperties(config, true);
|
||||||
|
return originalAddMultiple.apply(this, [config]);
|
||||||
|
} as typeof originalAddMultiple;
|
||||||
|
|
||||||
const originalFadeOut = SoundFade.fadeOut;
|
const originalFadeOut = SoundFade.fadeOut;
|
||||||
SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) =>
|
SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) =>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/** biome-ignore-all lint/performance/noNamespaceImport: Convenience */
|
||||||
|
|
||||||
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
|
import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator";
|
||||||
import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator";
|
import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator";
|
||||||
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator";
|
||||||
@ -48,23 +50,18 @@ export const settingsMigrators: Readonly<SettingsSaveMigrator[]> = [settingsMigr
|
|||||||
// import * as vA_B_C from "./versions/vA_B_C";
|
// import * as vA_B_C from "./versions/vA_B_C";
|
||||||
|
|
||||||
// --- v1.0.4 (and below) PATCHES --- //
|
// --- v1.0.4 (and below) PATCHES --- //
|
||||||
// biome-ignore lint/style/noNamespaceImport: Convenience (TODO: make this a file-wide ignore when Biome supports those)
|
|
||||||
import * as v1_0_4 from "./versions/v1_0_4";
|
import * as v1_0_4 from "./versions/v1_0_4";
|
||||||
|
|
||||||
// --- v1.7.0 PATCHES --- //
|
// --- v1.7.0 PATCHES --- //
|
||||||
// biome-ignore lint/style/noNamespaceImport: Convenience
|
|
||||||
import * as v1_7_0 from "./versions/v1_7_0";
|
import * as v1_7_0 from "./versions/v1_7_0";
|
||||||
|
|
||||||
// --- v1.8.3 PATCHES --- //
|
// --- v1.8.3 PATCHES --- //
|
||||||
// biome-ignore lint/style/noNamespaceImport: Convenience
|
|
||||||
import * as v1_8_3 from "./versions/v1_8_3";
|
import * as v1_8_3 from "./versions/v1_8_3";
|
||||||
|
|
||||||
// --- v1.9.0 PATCHES --- //
|
// --- v1.9.0 PATCHES --- //
|
||||||
// biome-ignore lint/style/noNamespaceImport: Convenience
|
|
||||||
import * as v1_9_0 from "./versions/v1_9_0";
|
import * as v1_9_0 from "./versions/v1_9_0";
|
||||||
|
|
||||||
// --- v1.10.0 PATCHES --- //
|
// --- v1.10.0 PATCHES --- //
|
||||||
// biome-ignore lint/style/noNamespaceImport: Convenience
|
|
||||||
import * as v1_10_0 from "./versions/v1_10_0";
|
import * as v1_10_0 from "./versions/v1_10_0";
|
||||||
|
|
||||||
/** Current game version */
|
/** Current game version */
|
||||||
|
@ -6,8 +6,8 @@ const repeatInputDelayMillis = 250;
|
|||||||
|
|
||||||
export default class TouchControl {
|
export default class TouchControl {
|
||||||
events: EventEmitter;
|
events: EventEmitter;
|
||||||
private buttonLock: string[] = new Array();
|
private buttonLock: string[] = [];
|
||||||
private inputInterval: NodeJS.Timeout[] = new Array();
|
private inputInterval: NodeJS.Timeout[] = [];
|
||||||
/** Whether touch controls are disabled */
|
/** Whether touch controls are disabled */
|
||||||
private disabled = false;
|
private disabled = false;
|
||||||
/** Whether the last touch event has finished before disabling */
|
/** Whether the last touch event has finished before disabling */
|
||||||
@ -42,7 +42,7 @@ export default class TouchControl {
|
|||||||
document.querySelectorAll(".apad-button").forEach(element => this.preventElementZoom(element as HTMLElement));
|
document.querySelectorAll(".apad-button").forEach(element => this.preventElementZoom(element as HTMLElement));
|
||||||
// Select all elements with the 'data-key' attribute and bind keys to them
|
// Select all elements with the 'data-key' attribute and bind keys to them
|
||||||
for (const button of document.querySelectorAll("[data-key]")) {
|
for (const button of document.querySelectorAll("[data-key]")) {
|
||||||
// @ts-ignore - Bind the key to the button using the dataset key
|
// @ts-expect-error - Bind the key to the button using the dataset key
|
||||||
this.bindKey(button, button.dataset.key);
|
this.bindKey(button, button.dataset.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ export function isMobile(): boolean {
|
|||||||
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(
|
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(
|
||||||
a,
|
a,
|
||||||
) ||
|
) ||
|
||||||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
|
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
|
||||||
a.substr(0, 4),
|
a.substr(0, 4),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -176,12 +176,12 @@ export class UiInputs {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (globalScene.ui?.getMode()) {
|
switch (globalScene.ui?.getMode()) {
|
||||||
|
// biome-ignore lint/suspicious/noFallthroughSwitchClause: falls through to show menu overlay
|
||||||
case UiMode.MESSAGE: {
|
case UiMode.MESSAGE: {
|
||||||
const messageHandler = globalScene.ui.getHandler<MessageUiHandler>();
|
const messageHandler = globalScene.ui.getHandler<MessageUiHandler>();
|
||||||
if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) {
|
if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: falls through to show menu overlay
|
|
||||||
}
|
}
|
||||||
case UiMode.TITLE:
|
case UiMode.TITLE:
|
||||||
case UiMode.COMMAND:
|
case UiMode.COMMAND:
|
||||||
|
@ -52,7 +52,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container {
|
|||||||
/** The array of {@linkcode Phaser.GameObjects.Text} objects which are drawn on the flyout */
|
/** The array of {@linkcode Phaser.GameObjects.Text} objects which are drawn on the flyout */
|
||||||
private flyoutText: Phaser.GameObjects.Text[] = new Array(4);
|
private flyoutText: Phaser.GameObjects.Text[] = new Array(4);
|
||||||
/** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */
|
/** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */
|
||||||
private moveInfo: MoveInfo[] = new Array();
|
private moveInfo: MoveInfo[] = [];
|
||||||
|
|
||||||
/** Current state of the flyout's visibility */
|
/** Current state of the flyout's visibility */
|
||||||
public flyoutVisible = false;
|
public flyoutVisible = false;
|
||||||
|
@ -41,24 +41,15 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container {
|
|||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** When set to `true`, disables the buttons; when set to `false`, enables the buttons. */
|
||||||
* Sets the updating state and updates button states accordingly.
|
get isUpdating(): boolean {
|
||||||
* If value is true (updating), disables the buttons; if false, enables the buttons.
|
return this._isUpdating;
|
||||||
* @param {boolean} value - The new updating state.
|
}
|
||||||
*/
|
set isUpdating(value: boolean) {
|
||||||
set isUpdating(value) {
|
|
||||||
this._isUpdating = value;
|
this._isUpdating = value;
|
||||||
this.setButtonsState(!value);
|
this.setButtonsState(!value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current updating state.
|
|
||||||
* @returns {boolean} - The current updating state.
|
|
||||||
*/
|
|
||||||
get isUpdating() {
|
|
||||||
return this._isUpdating;
|
|
||||||
}
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const titleWindow = addWindow(0, 0, 114, 18, false, false, undefined, undefined, WindowVariant.THIN);
|
const titleWindow = addWindow(0, 0, 114, 18, false, false, undefined, undefined, WindowVariant.THIN);
|
||||||
this.add(titleWindow);
|
this.add(titleWindow);
|
||||||
|
@ -625,7 +625,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
|
|||||||
const infoContainer = this.gachaInfoContainers[gachaType];
|
const infoContainer = this.gachaInfoContainers[gachaType];
|
||||||
switch (gachaType as GachaType) {
|
switch (gachaType as GachaType) {
|
||||||
case GachaType.LEGENDARY: {
|
case GachaType.LEGENDARY: {
|
||||||
const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(new Date().getTime()));
|
const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(Date.now()));
|
||||||
const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite;
|
const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite;
|
||||||
pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false));
|
pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false));
|
||||||
break;
|
break;
|
||||||
|
@ -591,9 +591,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||||||
|
|
||||||
// Auto-color options green/blue for good/bad by looking for (+)/(-)
|
// Auto-color options green/blue for good/bad by looking for (+)/(-)
|
||||||
if (text) {
|
if (text) {
|
||||||
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))!][0];
|
const primaryStyleString = [...text.match(new RegExp(/\[color=[^[]*\]\[shadow=[^[]*\]/i))!][0];
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
/(\(\+\)[^\(\[]*)/gi,
|
/(\(\+\)[^([]*)/gi,
|
||||||
substring =>
|
substring =>
|
||||||
"[/color][/shadow]" +
|
"[/color][/shadow]" +
|
||||||
getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) +
|
getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) +
|
||||||
@ -601,7 +601,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||||||
primaryStyleString,
|
primaryStyleString,
|
||||||
);
|
);
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
/(\(\-\)[^\(\[]*)/gi,
|
/(\(-\)[^([]*)/gi,
|
||||||
substring =>
|
substring =>
|
||||||
"[/color][/shadow]" +
|
"[/color][/shadow]" +
|
||||||
getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) +
|
getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) +
|
||||||
|
@ -2057,7 +2057,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
let newSpecies: PokemonSpecies;
|
let newSpecies: PokemonSpecies;
|
||||||
if (this.filteredIndices) {
|
if (this.filteredIndices) {
|
||||||
const index = this.filteredIndices.findIndex(id => id === this.species.speciesId);
|
const index = this.filteredIndices.indexOf(this.species.speciesId);
|
||||||
const newIndex = index <= 0 ? this.filteredIndices.length - 1 : index - 1;
|
const newIndex = index <= 0 ? this.filteredIndices.length - 1 : index - 1;
|
||||||
newSpecies = getPokemonSpecies(this.filteredIndices[newIndex]);
|
newSpecies = getPokemonSpecies(this.filteredIndices[newIndex]);
|
||||||
} else {
|
} else {
|
||||||
@ -2096,7 +2096,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
let newSpecies: PokemonSpecies;
|
let newSpecies: PokemonSpecies;
|
||||||
if (this.filteredIndices) {
|
if (this.filteredIndices) {
|
||||||
const index = this.filteredIndices.findIndex(id => id === this.species.speciesId);
|
const index = this.filteredIndices.indexOf(this.species.speciesId);
|
||||||
const newIndex = index >= this.filteredIndices.length - 1 ? 0 : index + 1;
|
const newIndex = index >= this.filteredIndices.length - 1 ? 0 : index + 1;
|
||||||
newSpecies = getPokemonSpecies(this.filteredIndices[newIndex]);
|
newSpecies = getPokemonSpecies(this.filteredIndices[newIndex]);
|
||||||
} else {
|
} else {
|
||||||
@ -2321,7 +2321,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.showStats();
|
this.showStats();
|
||||||
} else {
|
} else {
|
||||||
this.statsContainer.setVisible(false);
|
this.statsContainer.setVisible(false);
|
||||||
//@ts-ignore
|
//@ts-expect-error
|
||||||
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. what. how? huh?
|
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. what. how? huh?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2786,7 +2786,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.statsMode = false;
|
this.statsMode = false;
|
||||||
this.statsContainer.setVisible(false);
|
this.statsContainer.setVisible(false);
|
||||||
this.pokemonSprite.setVisible(true);
|
this.pokemonSprite.setVisible(true);
|
||||||
//@ts-ignore
|
//@ts-expect-error
|
||||||
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
|
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1389,7 +1389,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
|||||||
const fitsMoves = fitsMove1 && fitsMove2;
|
const fitsMoves = fitsMove1 && fitsMove2;
|
||||||
|
|
||||||
if (fitsEggMove1 && !fitsLevelMove1) {
|
if (fitsEggMove1 && !fitsLevelMove1) {
|
||||||
const em1 = eggMoves.findIndex(name => name === selectedMove1);
|
const em1 = eggMoves.indexOf(selectedMove1);
|
||||||
if ((starterData.eggMoves & (1 << em1)) === 0) {
|
if ((starterData.eggMoves & (1 << em1)) === 0) {
|
||||||
data.eggMove1 = false;
|
data.eggMove1 = false;
|
||||||
} else {
|
} else {
|
||||||
@ -1399,7 +1399,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
|
|||||||
data.tmMove1 = true;
|
data.tmMove1 = true;
|
||||||
}
|
}
|
||||||
if (fitsEggMove2 && !fitsLevelMove2) {
|
if (fitsEggMove2 && !fitsLevelMove2) {
|
||||||
const em2 = eggMoves.findIndex(name => name === selectedMove2);
|
const em2 = eggMoves.indexOf(selectedMove2);
|
||||||
if ((starterData.eggMoves & (1 << em2)) === 0) {
|
if ((starterData.eggMoves & (1 << em2)) === 0) {
|
||||||
data.eggMove2 = false;
|
data.eggMove2 = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,7 +19,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import { TypeColor, TypeShadow } from "#app/enums/color";
|
import { TypeColor, TypeShadow } from "#app/enums/color";
|
||||||
import { getNatureStatMultiplier, getNatureName } from "../data/nature";
|
import { getNatureStatMultiplier, getNatureName } from "../data/nature";
|
||||||
import { getVariantTint } from "#app/sprites/variant";
|
import { getVariantTint } from "#app/sprites/variant";
|
||||||
// biome-ignore lint/style/noNamespaceImport: See `src/system/game-data.ts`
|
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
|
||||||
import * as Modifier from "#app/modifier/modifier";
|
import * as Modifier from "#app/modifier/modifier";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
|
@ -2,7 +2,7 @@ import i18next from "i18next";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { GameMode } from "../game-mode";
|
import { GameMode } from "../game-mode";
|
||||||
// biome-ignore lint/style/noNamespaceImport: See `src/system/game-data.ts`
|
// biome-ignore lint/performance/noNamespaceImport: See `src/system/game-data.ts`
|
||||||
import * as Modifier from "#app/modifier/modifier";
|
import * as Modifier from "#app/modifier/modifier";
|
||||||
import type { SessionSaveData } from "../system/game-data";
|
import type { SessionSaveData } from "../system/game-data";
|
||||||
import type PokemonData from "../system/pokemon-data";
|
import type PokemonData from "../system/pokemon-data";
|
||||||
|
@ -209,7 +209,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
|
|||||||
|
|
||||||
settingFiltered.forEach((setting, s) => {
|
settingFiltered.forEach((setting, s) => {
|
||||||
// Convert the setting key from format 'Key_Name' to 'Key name' for display.
|
// Convert the setting key from format 'Key_Name' to 'Key name' for display.
|
||||||
const settingName = setting.replace(/\_/g, " ");
|
const settingName = setting.replace(/_/g, " ");
|
||||||
|
|
||||||
// Create and add a text object for the setting name to the scene.
|
// Create and add a text object for the setting name to the scene.
|
||||||
const isLock = this.settingBlacklisted.includes(this.setting[setting]);
|
const isLock = this.settingBlacklisted.includes(this.setting[setting]);
|
||||||
|
@ -16,7 +16,7 @@ export class NavigationManager {
|
|||||||
private static instance: NavigationManager;
|
private static instance: NavigationManager;
|
||||||
public modes: UiMode[];
|
public modes: UiMode[];
|
||||||
public selectedMode: UiMode = UiMode.SETTINGS;
|
public selectedMode: UiMode = UiMode.SETTINGS;
|
||||||
public navigationMenus: NavigationMenu[] = new Array<NavigationMenu>();
|
public navigationMenus: NavigationMenu[] = [];
|
||||||
public labels: string[];
|
public labels: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,7 +105,7 @@ export class NavigationManager {
|
|||||||
|
|
||||||
export default class NavigationMenu extends Phaser.GameObjects.Container {
|
export default class NavigationMenu extends Phaser.GameObjects.Container {
|
||||||
private navigationIcons: InputsIcons;
|
private navigationIcons: InputsIcons;
|
||||||
protected headerTitles: Phaser.GameObjects.Text[] = new Array<Phaser.GameObjects.Text>();
|
protected headerTitles: Phaser.GameObjects.Text[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of NavigationMenu.
|
* Creates an instance of NavigationMenu.
|
||||||
|
@ -2822,7 +2822,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
iconElement: GameObjects.Sprite,
|
iconElement: GameObjects.Sprite,
|
||||||
controlLabel: GameObjects.Text,
|
controlLabel: GameObjects.Text,
|
||||||
): void {
|
): void {
|
||||||
// biome-ignore lint/suspicious/noImplicitAnyLet: TODO
|
|
||||||
let iconPath: string;
|
let iconPath: string;
|
||||||
// touch controls cannot be rebound as is, and are just emulating a keyboard event.
|
// touch controls cannot be rebound as is, and are just emulating a keyboard event.
|
||||||
// Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls
|
// Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls
|
||||||
@ -2856,7 +2855,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
} else {
|
} else {
|
||||||
iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting);
|
iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting);
|
||||||
}
|
}
|
||||||
// @ts-ignore: TODO can iconPath actually be undefined?
|
// @ts-expect-error: TODO can iconPath actually be undefined?
|
||||||
iconElement.setTexture(gamepadType, iconPath);
|
iconElement.setTexture(gamepadType, iconPath);
|
||||||
iconElement.setPosition(this.instructionRowX, this.instructionRowY);
|
iconElement.setPosition(this.instructionRowX, this.instructionRowY);
|
||||||
controlLabel.setPosition(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY);
|
controlLabel.setPosition(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY);
|
||||||
@ -3481,7 +3480,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.showStats();
|
this.showStats();
|
||||||
} else {
|
} else {
|
||||||
this.statsContainer.setVisible(false);
|
this.statsContainer.setVisible(false);
|
||||||
//@ts-ignore
|
//@ts-expect-error
|
||||||
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. what. how? huh?
|
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. what. how? huh?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4489,7 +4488,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.statsMode = false;
|
this.statsMode = false;
|
||||||
this.statsContainer.setVisible(false);
|
this.statsContainer.setVisible(false);
|
||||||
this.pokemonSprite.setVisible(!!this.speciesStarterDexEntry?.caughtAttr);
|
this.pokemonSprite.setVisible(!!this.speciesStarterDexEntry?.caughtAttr);
|
||||||
//@ts-ignore
|
//@ts-expect-error
|
||||||
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
|
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
|
||||||
this.teraIcon.setVisible(globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id));
|
this.teraIcon.setVisible(globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id));
|
||||||
const props = globalScene.gameData.getSpeciesDexAttrProps(
|
const props = globalScene.gameData.getSpeciesDexAttrProps(
|
||||||
|
@ -117,7 +117,7 @@ export default class SummaryUiHandler extends UiHandler {
|
|||||||
|
|
||||||
private pokemon: PlayerPokemon | null;
|
private pokemon: PlayerPokemon | null;
|
||||||
private playerParty: boolean;
|
private playerParty: boolean;
|
||||||
/**This is set to false when checking the summary of a freshly caught Pokemon as it is not part of a player's party yet but still needs to display its items**/
|
/**This is set to false when checking the summary of a freshly caught Pokemon as it is not part of a player's party yet but still needs to display its items*/
|
||||||
private newMove: Move | null;
|
private newMove: Move | null;
|
||||||
private moveSelectFunction: Function | null;
|
private moveSelectFunction: Function | null;
|
||||||
private transitioning: boolean;
|
private transitioning: boolean;
|
||||||
|
@ -300,7 +300,7 @@ export function getTextWithColors(
|
|||||||
): string {
|
): string {
|
||||||
// Apply primary styling before anything else
|
// Apply primary styling before anything else
|
||||||
let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]";
|
let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]";
|
||||||
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))!][0];
|
const primaryStyleString = [...text.match(new RegExp(/\[color=[^[]*\]\[shadow=[^[]*\]/i))!][0];
|
||||||
|
|
||||||
/* For money text displayed in game windows, we can't use the default {@linkcode TextStyle.MONEY}
|
/* For money text displayed in game windows, we can't use the default {@linkcode TextStyle.MONEY}
|
||||||
* or it will look wrong in legacy mode because of the different window background color
|
* or it will look wrong in legacy mode because of the different window background color
|
||||||
@ -320,7 +320,7 @@ export function getTextWithColors(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Remove extra style block at the end
|
// Remove extra style block at the end
|
||||||
return text.replace(/\[color=[^\[]*\]\[shadow=[^\[]*\]\[\/color\]\[\/shadow\]/gi, "");
|
return text.replace(/\[color=[^[]*\]\[shadow=[^[]*\]\[\/color\]\[\/shadow\]/gi, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a giant switch which is the best option.
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a giant switch which is the best option.
|
||||||
|
@ -10,7 +10,7 @@ export const MissingTextureKey = "__MISSING";
|
|||||||
|
|
||||||
export function toReadableString(str: string): string {
|
export function toReadableString(str: string): string {
|
||||||
return str
|
return str
|
||||||
.replace(/\_/g, " ")
|
.replace(/_/g, " ")
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map(s => `${s.slice(0, 1)}${s.slice(1).toLowerCase()}`)
|
.map(s => `${s.slice(0, 1)}${s.slice(1).toLowerCase()}`)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
@ -201,19 +201,19 @@ export function formatLargeNumber(count: number, threshold: number): string {
|
|||||||
let suffix = "";
|
let suffix = "";
|
||||||
switch (Math.ceil(ret.length / 3) - 1) {
|
switch (Math.ceil(ret.length / 3) - 1) {
|
||||||
case 1:
|
case 1:
|
||||||
suffix = "K";
|
suffix = i18next.t("common:abrThousand");
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
suffix = "M";
|
suffix = i18next.t("common:abrMillion");
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
suffix = "B";
|
suffix = i18next.t("common:abrBillion");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
suffix = "T";
|
suffix = i18next.t("common:abrTrillion");
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
suffix = "q";
|
suffix = i18next.t("common:abrQuadrillion");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return "?";
|
return "?";
|
||||||
@ -227,15 +227,31 @@ export function formatLargeNumber(count: number, threshold: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Abbreviations from 10^0 to 10^33
|
// Abbreviations from 10^0 to 10^33
|
||||||
const AbbreviationsLargeNumber: string[] = ["", "K", "M", "B", "t", "q", "Q", "s", "S", "o", "n", "d"];
|
function getAbbreviationsLargeNumber(): string[] {
|
||||||
|
return [
|
||||||
|
"",
|
||||||
|
i18next.t("common:abrThousand"),
|
||||||
|
i18next.t("common:abrMillion"),
|
||||||
|
i18next.t("common:abrBillion"),
|
||||||
|
i18next.t("common:abrTrillion"),
|
||||||
|
i18next.t("common:abrQuadrillion"),
|
||||||
|
i18next.t("common:abrQuintillion"),
|
||||||
|
i18next.t("common:abrSextillion"),
|
||||||
|
i18next.t("common:abrSeptillion"),
|
||||||
|
i18next.t("common:abrOctillion"),
|
||||||
|
i18next.t("common:abrNonillion"),
|
||||||
|
i18next.t("common:abrDecillion"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export function formatFancyLargeNumber(number: number, rounded = 3): string {
|
export function formatFancyLargeNumber(number: number, rounded = 3): string {
|
||||||
|
const abbreviations = getAbbreviationsLargeNumber();
|
||||||
let exponent: number;
|
let exponent: number;
|
||||||
|
|
||||||
if (number < 1000) {
|
if (number < 1000) {
|
||||||
exponent = 0;
|
exponent = 0;
|
||||||
} else {
|
} else {
|
||||||
const maxExp = AbbreviationsLargeNumber.length - 1;
|
const maxExp = abbreviations.length - 1;
|
||||||
|
|
||||||
exponent = Math.floor(Math.log(number) / Math.log(1000));
|
exponent = Math.floor(Math.log(number) / Math.log(1000));
|
||||||
exponent = Math.min(exponent, maxExp);
|
exponent = Math.min(exponent, maxExp);
|
||||||
@ -243,7 +259,7 @@ export function formatFancyLargeNumber(number: number, rounded = 3): string {
|
|||||||
number /= Math.pow(1000, exponent);
|
number /= Math.pow(1000, exponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${(exponent === 0) || number % 1 === 0 ? number : number.toFixed(rounded)}${AbbreviationsLargeNumber[exponent]}`;
|
return `${exponent === 0 || number % 1 === 0 ? number : number.toFixed(rounded)}${abbreviations[exponent]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMoney(format: MoneyFormat, amount: number) {
|
export function formatMoney(format: MoneyFormat, amount: number) {
|
||||||
@ -583,7 +599,7 @@ export function isBetween(num: number, min: number, max: number): boolean {
|
|||||||
* @param move the move for which the animation filename is needed
|
* @param move the move for which the animation filename is needed
|
||||||
*/
|
*/
|
||||||
export function animationFileName(move: MoveId): string {
|
export function animationFileName(move: MoveId): string {
|
||||||
return MoveId[move].toLowerCase().replace(/\_/g, "-");
|
return MoveId[move].toLowerCase().replace(/_/g, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,7 +2,7 @@ import { isBeta } from "./utility-vars";
|
|||||||
|
|
||||||
export function setCookie(cName: string, cValue: string): void {
|
export function setCookie(cName: string, cValue: string): void {
|
||||||
const expiration = new Date();
|
const expiration = new Date();
|
||||||
expiration.setTime(new Date().getTime() + 3600000 * 24 * 30 * 3 /*7*/);
|
expiration.setTime(Date.now() + 3600000 * 24 * 30 * 3 /*7*/);
|
||||||
document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Expires=${expiration.toUTCString()}`;
|
document.cookie = `${cName}=${cValue};Secure;SameSite=Strict;Domain=${window.location.hostname};Path=/;Expires=${expiration.toUTCString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { RepeatBerryNextTurnAbAttr } from "#app/data/abilities/ability";
|
import { CudChewConsumeBerryAbAttr } from "#app/data/abilities/ability";
|
||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
@ -196,7 +196,7 @@ describe("Abilities - Cud Chew", () => {
|
|||||||
|
|
||||||
describe("regurgiates berries", () => {
|
describe("regurgiates berries", () => {
|
||||||
it("re-triggers effects on eater without pushing to array", async () => {
|
it("re-triggers effects on eater without pushing to array", async () => {
|
||||||
const apply = vi.spyOn(RepeatBerryNextTurnAbAttr.prototype, "apply");
|
const apply = vi.spyOn(CudChewConsumeBerryAbAttr.prototype, "apply");
|
||||||
await game.classicMode.startBattle([SpeciesId.FARIGIRAF]);
|
await game.classicMode.startBattle([SpeciesId.FARIGIRAF]);
|
||||||
|
|
||||||
const farigiraf = game.scene.getPlayerPokemon()!;
|
const farigiraf = game.scene.getPlayerPokemon()!;
|
||||||
|
@ -91,7 +91,7 @@ describe("Abilities - Gorilla Tactics", () => {
|
|||||||
game.move.select(MoveId.METRONOME);
|
game.move.select(MoveId.METRONOME);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
// Gorilla Tactics should bypass dancer and instruct
|
// Gorilla Tactics should lock into Metronome, not tackle
|
||||||
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true);
|
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true);
|
||||||
expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false);
|
expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false);
|
||||||
expect(darmanitan.getLastXMoves(-1)).toEqual([
|
expect(darmanitan.getLastXMoves(-1)).toEqual([
|
||||||
|
@ -95,7 +95,7 @@ describe("Abilities - Harvest", () => {
|
|||||||
|
|
||||||
// Give ourselves harvest and disable enemy neut gas,
|
// Give ourselves harvest and disable enemy neut gas,
|
||||||
// but force our roll to fail so we don't accidentally recover anything
|
// but force our roll to fail so we don't accidentally recover anything
|
||||||
vi.spyOn(PostTurnRestoreBerryAbAttr.prototype, "canApplyPostTurn").mockReturnValueOnce(false);
|
vi.spyOn(PostTurnRestoreBerryAbAttr.prototype, "canApply").mockReturnValueOnce(false);
|
||||||
game.override.ability(AbilityId.HARVEST);
|
game.override.ability(AbilityId.HARVEST);
|
||||||
game.move.select(MoveId.GASTRO_ACID);
|
game.move.select(MoveId.GASTRO_ACID);
|
||||||
await game.move.selectEnemyMove(MoveId.NUZZLE);
|
await game.move.selectEnemyMove(MoveId.NUZZLE);
|
||||||
|
@ -42,7 +42,7 @@ describe("Abilities - Healer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not queue a message phase for healing if the ally has fainted", async () => {
|
it("should not queue a message phase for healing if the ally has fainted", async () => {
|
||||||
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApplyPostTurn");
|
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApply");
|
||||||
game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]);
|
game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]);
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user